会员管理整改
This commit is contained in:
@@ -3,6 +3,7 @@ package com.budwk.app.sys.controller;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
@@ -119,6 +120,8 @@ public class SysDictController {
|
||||
sysDictService.save(dict, parentId);
|
||||
sysDictService.clearCache();
|
||||
return Result.success("system.success");
|
||||
} catch (BaseException e) {
|
||||
return Result.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return Result.error("system.error");
|
||||
}
|
||||
@@ -142,9 +145,11 @@ public class SysDictController {
|
||||
public Object editDo(@Param("..") Sys_dict dict, @Param("parentId") String parentId, HttpServletRequest req) {
|
||||
try {
|
||||
dict.setUpdatedBy(SecurityUtil.getUserId());
|
||||
sysDictService.updateIgnoreNull(dict);
|
||||
sysDictService.updateDict(dict);
|
||||
sysDictService.clearCache();
|
||||
return Result.success();
|
||||
} catch (BaseException e) {
|
||||
return Result.error(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return Result.error();
|
||||
}
|
||||
|
||||
@@ -11,26 +11,21 @@ import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_home_activity;
|
||||
import com.budwk.app.sys.models.Sys_home_template;
|
||||
import com.budwk.app.sys.models.Sys_menu;
|
||||
import com.budwk.app.sys.services.SysHomeNewsService;
|
||||
import com.budwk.app.sys.services.SysMenuService;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.FieldFilter;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.Daos;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
@@ -42,7 +37,6 @@ import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -58,9 +52,9 @@ public class SysHomeController {
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
@Inject
|
||||
private ActivityBasicScopeService activityBasicScopeService;
|
||||
@Inject
|
||||
private SysHomeNewsService sysHomeNewsService;
|
||||
|
||||
@At("")
|
||||
@Ok("re")
|
||||
@@ -293,34 +287,17 @@ public class SysHomeController {
|
||||
@SaCheckLogin
|
||||
@ApiOperation("获取新闻")
|
||||
@Ok("json")
|
||||
// @CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "web_news", isHash = true)
|
||||
// @CacheResult(cacheKey = "news", ignoreNull = true, cacheLiveTime = 60 * 60 * 24)
|
||||
public Result getNews() {
|
||||
try {
|
||||
String url = "https://xgh.cug.edu.cn/";
|
||||
Document document = Jsoup.connect(url).get();
|
||||
Elements homeElements = document.select(".gh,.jc,.fc,.jg");
|
||||
return Result.success(sysHomeNewsService.getWebsiteNews());
|
||||
}
|
||||
|
||||
List<Map<String, Object>> news = homeElements.stream().map(homeElement -> {
|
||||
//获取板块名称
|
||||
String name = homeElement.selectFirst(".title").text();
|
||||
Elements liElements = homeElement.select("li");
|
||||
List<NutMap> ele = liElements.stream().map(liElement -> {
|
||||
NutMap row = NutMap.NEW();
|
||||
String href = liElement.selectFirst("a").attr("href");
|
||||
String time = liElement.selectFirst("span").text();
|
||||
String title = liElement.selectFirst("a").attr("title");
|
||||
row.put("href", href);
|
||||
row.put("time", time);
|
||||
row.put("text", title);
|
||||
return row;
|
||||
}).toList();
|
||||
return Map.of("label", name, "value", ele);
|
||||
}).collect(Collectors.toList());
|
||||
redisService.setex("gonghui_news", 60 * 60 * 24, Json.toJson(news));
|
||||
return Result.success(news);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
/**
|
||||
* href 传首页新闻项原始链接;成功结果 data 为图片地址字符串,空字符串表示没有可用图片。
|
||||
*/
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@Ok("json")
|
||||
public Result getNewsImage(@Param("href") String href) {
|
||||
return Result.success(sysHomeNewsService.getNewsImage(href));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,16 @@ public class Sys_dict extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String code;
|
||||
|
||||
@Column
|
||||
@Comment("关联人员类型编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String personType;
|
||||
|
||||
@Column
|
||||
@Comment("人员属性业务编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String userAttributeCode;
|
||||
|
||||
@Column
|
||||
@Comment("启用状态")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
|
||||
@@ -79,6 +79,13 @@ public interface SysDictService extends BaseService<Sys_dict> {
|
||||
*/
|
||||
void save(Sys_dict dict, String pid);
|
||||
|
||||
/**
|
||||
* 更新数据字典,并对人员属性直属子项的人员类型和业务编码进行校验。
|
||||
*
|
||||
* @param dict 待更新字典;id 为必填项,personType 与 userAttributeCode 可为空
|
||||
*/
|
||||
void updateDict(Sys_dict dict);
|
||||
|
||||
/**
|
||||
* 保存数据字典
|
||||
*
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.budwk.app.sys.services;
|
||||
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 首页工会网站新闻服务。
|
||||
*/
|
||||
public interface SysHomeNewsService {
|
||||
|
||||
/**
|
||||
* 获取学校工会网站首页新闻。
|
||||
*
|
||||
* @return 新闻栏目集合;每个栏目包含 label 栏目名称和 value 新闻列表,
|
||||
* 新闻项包含 href 跳转地址、time 发布时间、text 新闻标题和 thumbUrl 缩略图绝对地址(无图时为空)
|
||||
*/
|
||||
List<NutMap> getWebsiteNews();
|
||||
|
||||
/**
|
||||
* href 必须是工会首页返回的新闻链接;返回正文首图的绝对地址,无图或不可访问时为空字符串。
|
||||
*/
|
||||
String getNewsImage(String href);
|
||||
}
|
||||
@@ -27,6 +27,9 @@ import java.util.Map;
|
||||
@IocBean(args = {"refer:dao"})
|
||||
@CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "sys_dict", isHash = true)
|
||||
public class SysDictServiceImpl extends BaseServiceImpl<Sys_dict> implements SysDictService {
|
||||
private static final String USER_ATTRIBUTE_DICT_CODE = "USER_ATTRIBUTE";
|
||||
private static final String USER_PERSON_TYPE_DICT_CODE = "USER_PERSON_TYPE";
|
||||
|
||||
public SysDictServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
@@ -132,18 +135,76 @@ public class SysDictServiceImpl extends BaseServiceImpl<Sys_dict> implements Sys
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void save(Sys_dict dict, String pid) {
|
||||
String path = "";
|
||||
Sys_dict parentDict = null;
|
||||
if (!Strings.isEmpty(pid)) {
|
||||
Sys_dict pp = this.fetch(pid);
|
||||
path = pp.getPath();
|
||||
parentDict = this.fetch(pid);
|
||||
path = parentDict.getPath();
|
||||
}
|
||||
dict.setPath(getSubPath("sys_dict", "path", path));
|
||||
dict.setParentId(pid);
|
||||
validateUserAttribute(dict, parentDict);
|
||||
dao().insert(dict);
|
||||
if (!Strings.isEmpty(pid)) {
|
||||
this.update(Chain.make("hasChildren", true), Cnd.where("id", "=", pid));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字典记录。人员属性关联字段允许为空;填写后校验人员类型有效性和业务编码唯一性。
|
||||
*
|
||||
* @param dict 待更新字典,id 用于定位原记录并在唯一性校验中排除自身
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateDict(Sys_dict dict) {
|
||||
Sys_dict current = this.fetch(dict.getId());
|
||||
if (current == null) {
|
||||
throw new BaseException("字典数据不存在");
|
||||
}
|
||||
Sys_dict parentDict = Strings.isBlank(current.getParentId()) ? null : this.fetch(current.getParentId());
|
||||
dict.setParentId(current.getParentId());
|
||||
validateUserAttribute(dict, parentDict);
|
||||
this.updateIgnoreNull(dict);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 USER_ATTRIBUTE 直属子项的扩展关系。
|
||||
* personType 为空时允许保存;有值时必须属于 USER_PERSON_TYPE 的启用子项。
|
||||
* userAttributeCode 为空时允许保存;有值时在 USER_ATTRIBUTE 直属子项中必须唯一。
|
||||
*/
|
||||
private void validateUserAttribute(Sys_dict dict, Sys_dict parentDict) {
|
||||
if (parentDict == null || !USER_ATTRIBUTE_DICT_CODE.equals(parentDict.getCode())) {
|
||||
return;
|
||||
}
|
||||
String personType = Strings.sNull(dict.getPersonType()).trim();
|
||||
String userAttributeCode = Strings.sNull(dict.getUserAttributeCode()).trim();
|
||||
dict.setPersonType(personType);
|
||||
dict.setUserAttributeCode(userAttributeCode);
|
||||
|
||||
if (!Strings.isBlank(personType)) {
|
||||
Sys_dict personTypeRoot = this.fetch(Cnd.where("code", "=", USER_PERSON_TYPE_DICT_CODE));
|
||||
boolean personTypeValid = personTypeRoot != null && this.count(
|
||||
Cnd.where("parentId", "=", personTypeRoot.getId())
|
||||
.and("code", "=", personType)
|
||||
.and("disabled", "=", false)
|
||||
) > 0;
|
||||
if (!personTypeValid) {
|
||||
throw new BaseException("人员类型不存在或已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
if (!Strings.isBlank(userAttributeCode)) {
|
||||
Cnd codeCondition = Cnd.where("parentId", "=", parentDict.getId())
|
||||
.and("userAttributeCode", "=", userAttributeCode);
|
||||
if (!Strings.isBlank(dict.getId())) {
|
||||
codeCondition.and("id", "!=", dict.getId());
|
||||
}
|
||||
if (this.count(codeCondition) > 0) {
|
||||
throw new BaseException("人员属性编码已存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void saveByParentCode(Sys_dict dict, String parentCode) {
|
||||
Sys_dict parentDict = fetch(Cnd.where("code", "=", parentCode));
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.sys.services.SysHomeNewsService;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 首页工会网站新闻服务实现,统一为PC端和移动端提供同一份新闻数据。
|
||||
*/
|
||||
@IocBean
|
||||
public class SysHomeNewsServiceImpl implements SysHomeNewsService {
|
||||
|
||||
private static final String WEBSITE_URL = "https://xgh.cug.edu.cn/";
|
||||
private static final String NEWS_CACHE_KEY = "gonghui_news";
|
||||
private static final int NEWS_CACHE_SECONDS = 60 * 60 * 24;
|
||||
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* 抓取学校工会网站首页的工会要闻、基层动态等栏目。
|
||||
* 外部网站暂时不可用时优先返回最近一次缓存,避免PC端和移动端同时显示为空。
|
||||
*
|
||||
* @return 新闻栏目集合;栏目 value 中的新闻项包含 href、time、text
|
||||
*/
|
||||
@Override
|
||||
public List<NutMap> getWebsiteNews() {
|
||||
try {
|
||||
Document document = Jsoup.connect(WEBSITE_URL).timeout(10000).get();
|
||||
List<NutMap> news = parseWebsiteNews(document.select(".gh,.jc,.fc,.jg"));
|
||||
if (!news.isEmpty()) {
|
||||
redisService.setex(NEWS_CACHE_KEY, NEWS_CACHE_SECONDS, Json.toJson(news));
|
||||
return news;
|
||||
}
|
||||
return getCachedNews();
|
||||
} catch (IOException e) {
|
||||
List<NutMap> cachedNews = getCachedNews();
|
||||
if (!cachedNews.isEmpty()) {
|
||||
return cachedNews;
|
||||
}
|
||||
throw new RuntimeException("学校工会网站新闻获取失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将外部网站栏目节点转换为前端统一使用的栏目和新闻结构。
|
||||
*/
|
||||
private List<NutMap> parseWebsiteNews(Elements homeElements) {
|
||||
List<NutMap> sections = new ArrayList<>();
|
||||
for (Element homeElement : homeElements) {
|
||||
Element titleElement = homeElement.selectFirst(".title");
|
||||
if (titleElement == null || StrUtil.isBlank(titleElement.text())) {
|
||||
continue;
|
||||
}
|
||||
List<NutMap> rows = new ArrayList<>();
|
||||
for (Element itemElement : homeElement.select("li")) {
|
||||
Element linkElement = itemElement.selectFirst("a");
|
||||
if (linkElement == null || StrUtil.isBlank(linkElement.attr("href"))) {
|
||||
continue;
|
||||
}
|
||||
Element timeElement = itemElement.selectFirst("span");
|
||||
// 仅使用当前新闻条目的缩略图,按网页基址补全相对路径。
|
||||
Element imageElement = itemElement.selectFirst("img");
|
||||
String thumbUrl = imageElement == null ? "" : imageElement.absUrl("src");
|
||||
if (!thumbUrl.startsWith("https://") && !thumbUrl.startsWith("http://")) {
|
||||
thumbUrl = "";
|
||||
}
|
||||
String title = StrUtil.blankToDefault(linkElement.attr("title"), linkElement.text());
|
||||
rows.add(NutMap.NEW()
|
||||
.addv("href", linkElement.attr("href"))
|
||||
.addv("time", timeElement == null ? "" : timeElement.text())
|
||||
.addv("text", title)
|
||||
.addv("thumbUrl", thumbUrl));
|
||||
}
|
||||
sections.add(NutMap.NEW().addv("label", titleElement.text()).addv("value", rows));
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取最近一次成功抓取的数据;缓存为空或内容异常时返回空集合。
|
||||
*/
|
||||
private List<NutMap> getCachedNews() {
|
||||
String cached = redisService.get(NEWS_CACHE_KEY);
|
||||
if (StrUtil.isBlank(cached)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(NutMap.class, cached);
|
||||
} catch (RuntimeException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅补取缓存新闻列表中已存在的公开文章,禁止任意地址抓取和自动重定向。
|
||||
* 正文首图独立缓存;无图短暂缓存,防止每次栏目切换重复访问外站。
|
||||
*/
|
||||
@Override
|
||||
public String getNewsImage(String href) {
|
||||
if (StrUtil.isBlank(href) || href.length() > 2048) return "";
|
||||
boolean listed = false;
|
||||
for (NutMap section : getCachedNews()) {
|
||||
Object values = section.get("value");
|
||||
if (!(values instanceof List)) continue;
|
||||
for (Object value : (List<?>) values) {
|
||||
if (value instanceof Map && href.equals(((Map<?, ?>) value).get("href"))) {
|
||||
listed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!listed) return "";
|
||||
try {
|
||||
URI uri = URI.create(WEBSITE_URL).resolve(href);
|
||||
String host = uri.getHost();
|
||||
// 来源限制为学校公开网站和微信公众号,端口及凭据均不接受自定义。
|
||||
if (host == null || !(host.equals("cug.edu.cn") || host.endsWith(".cug.edu.cn")
|
||||
|| host.equals("mp.weixin.qq.com")) || uri.getUserInfo() != null
|
||||
|| uri.getPort() != -1 || !("https".equals(uri.getScheme()) || "http".equals(uri.getScheme()))) return "";
|
||||
String cacheKey = "gonghui_news_image:" + UUID.nameUUIDFromBytes(uri.toString().getBytes(StandardCharsets.UTF_8));
|
||||
String cached = redisService.get(cacheKey);
|
||||
if (cached != null) return "-".equals(cached) ? "" : cached;
|
||||
String image = "";
|
||||
try {
|
||||
Document article = Jsoup.connect(uri.toString()).timeout(5000).maxBodySize(2 * 1024 * 1024)
|
||||
.followRedirects(false).get();
|
||||
Element first = article.selectFirst(".v_news_content img, #js_content img");
|
||||
if (first != null) {
|
||||
image = first.hasAttr("data-src") ? first.absUrl("data-src") : first.absUrl("src");
|
||||
}
|
||||
if (!image.startsWith("https://") && !image.startsWith("http://")) image = "";
|
||||
} catch (IOException ignored) {
|
||||
// 外站失败不影响首页新闻文字和栏目切换,五分钟后允许再次补取。
|
||||
}
|
||||
redisService.setex(cacheKey, image.isEmpty() ? 300 : NEWS_CACHE_SECONDS, image.isEmpty() ? "-" : image);
|
||||
return image;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,8 @@ public class WkTomcatConfiguration implements NbAppEventListener {
|
||||
|
||||
// NutzBoot 使用编程式 Context,必须显式运行官方 WebSocket 初始化器。
|
||||
StandardContext context = (StandardContext) tomcat.getHost().findChild(starter.getContextPath());
|
||||
// 为静态 SVG 补齐响应类型,避免浏览器收到文件后仍无法按图片解码。
|
||||
context.addMimeMapping("svg", "image/svg+xml");
|
||||
context.setUseHttpOnly(conf.getBoolean("tomcat.session.cookie.httponly", true));
|
||||
context.addServletContainerInitializer(new WsSci(), Set.of(WkWebSocket.class));
|
||||
// Tomcat 的错误页再次 forward 时仍保持 ERROR 派发;让 MVC 渲染原业务错误页。
|
||||
|
||||
@@ -323,7 +323,8 @@
|
||||
let h5GlobalLoading = null
|
||||
let h5PendingRequestCount = 0
|
||||
|
||||
function showH5GlobalLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg") {
|
||||
// 更新资源版本,避免继续命中缺少 SVG 响应类型的旧缓存。
|
||||
function showH5GlobalLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg?v=20261014") {
|
||||
if (!h5GlobalLoading) {
|
||||
h5GlobalLoading = vant.Toast.loading({
|
||||
icon: icon,
|
||||
@@ -375,7 +376,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
function createListLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg") {
|
||||
function createListLoading(icon = "/assets/mobile/img/common/smart-union-loading-red.svg?v=20261014") {
|
||||
showH5GlobalLoading(icon)
|
||||
|
||||
const closeLoading = function() {
|
||||
|
||||
@@ -35,6 +35,14 @@ layout("/layouts/platform.html"){
|
||||
|
||||
<el-table-column label="字典代码" header-align="center" prop="code" :show-overflow-tooltip="true"></el-table-column>
|
||||
|
||||
<el-table-column label="人员类型" header-align="center" prop="personType" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
{{personTypeNameMap[scope.row.personType]||scope.row.personType}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="编码" header-align="center" prop="userAttributeCode" :show-overflow-tooltip="true"></el-table-column>
|
||||
|
||||
<el-table-column label="启用状态" header-align="center" prop="disabled" align="center" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<i v-if="scope.row.disabled" class="fa fa-circle text-danger ml5"></i>
|
||||
@@ -50,12 +58,12 @@ layout("/layouts/platform.html"){
|
||||
<span class="ti-angle-down"></span>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="{type:'add',id:scope.row.id,name:scope.row.name}">添加子级</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type:'add',id:scope.row.id,name:scope.row.name,row:scope.row}">添加子级</el-dropdown-item>
|
||||
<el-dropdown-item divided :command="{type:'enable',id:scope.row.id,name:scope.row.name,row:scope.row}">
|
||||
启用
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type:'disable',id:scope.row.id,name:scope.row.name,row:scope.row}">禁用</el-dropdown-item>
|
||||
<el-dropdown-item divided :command="{type:'edit',id:scope.row.id,name:scope.row.name}">修改</el-dropdown-item>
|
||||
<el-dropdown-item divided :command="{type:'edit',id:scope.row.id,name:scope.row.name,row:scope.row}">修改</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type:'delete',id:scope.row.id,name:scope.row.name}">删除</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
@@ -77,12 +85,20 @@ layout("/layouts/platform.html"){
|
||||
></el-cascader>
|
||||
<el-input v-if="isAddFromSub" type="text" v-model="formData.parentName" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showUserAttributeFields" prop="personType" label="人员类型">
|
||||
<el-select v-model="formData.personType" clearable filterable placeholder="请选择人员类型" style="width: 100%">
|
||||
<el-option v-for="item in personTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="字典名称">
|
||||
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="3" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="字典代码">
|
||||
<el-input maxlength="100" placeholder="字典代码" v-model="formData.code" auto-complete="off" tabindex="5" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showUserAttributeFields" prop="userAttributeCode" label="编码">
|
||||
<el-input maxlength="100" placeholder="请输入人员属性编码" v-model="formData.userAttributeCode" auto-complete="off" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="字典备注">
|
||||
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||
</el-form-item>
|
||||
@@ -91,18 +107,26 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="addDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="doAdd">确 定</el-button>
|
||||
<el-button @click="closeAddDialog">取 消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="doAdd">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog title="修改字典" :visible.sync="editDialogVisible" :close-on-click-modal="false" width="50%">
|
||||
<el-form :model="formData" ref="editForm" :rules="formRules" size="small" label-width="80px">
|
||||
<el-form-item v-if="showUserAttributeFields" prop="personType" label="人员类型">
|
||||
<el-select v-model="formData.personType" clearable filterable placeholder="请选择人员类型" style="width: 100%">
|
||||
<el-option v-for="item in personTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="字典名称">
|
||||
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="1" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" label="字典代码">
|
||||
<el-input maxlength="100" placeholder="字典代码" v-model="formData.code" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showUserAttributeFields" prop="userAttributeCode" label="编码">
|
||||
<el-input maxlength="100" placeholder="请输入人员属性编码" v-model="formData.userAttributeCode" auto-complete="off" type="text"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="remark" label="字典备注">
|
||||
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||
</el-form-item>
|
||||
@@ -111,8 +135,8 @@ layout("/layouts/platform.html"){
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="editDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="doEdit">确 定</el-button>
|
||||
<el-button @click="closeEditDialog">取 消</el-button>
|
||||
<el-button type="primary" :loading="formLoading" @click="doEdit">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<el-dialog title="字典排序" :visible.sync="sortDialogVisible" width="50%">
|
||||
@@ -136,12 +160,19 @@ layout("/layouts/platform.html"){
|
||||
editDialogVisible: false,
|
||||
sortDialogVisible: false,
|
||||
isAddFromSub: false,
|
||||
showUserAttributeFields: false,
|
||||
formLoading: false,
|
||||
userAttributeDictId: "",
|
||||
personTypeOptions: [],
|
||||
personTypeNameMap: {},
|
||||
tableData: [], //后台取出的数据
|
||||
tableKey: "",
|
||||
formData: {
|
||||
id: "",
|
||||
parentId: "",
|
||||
parentName: "",
|
||||
personType: "",
|
||||
userAttributeCode: "",
|
||||
type: "menu",
|
||||
target: "data-pjax",
|
||||
children: "false"
|
||||
@@ -244,83 +275,86 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
openAdd: function () {
|
||||
var self = this
|
||||
self.isAddFromSub = false
|
||||
self.addDialogVisible = true
|
||||
self.parentMenu = []
|
||||
self.menuOptions = []
|
||||
self.$set(self, "isAddFromSub", false)
|
||||
self.$set(self, "showUserAttributeFields", false)
|
||||
self.$set(self, "addDialogVisible", true)
|
||||
self.$set(self, "parentMenu", [])
|
||||
self.$set(self, "menuOptions", [])
|
||||
//表单还原为初始值
|
||||
self.formData = {
|
||||
self.$set(self, "formData", {
|
||||
id: "",
|
||||
parentId: "",
|
||||
parentName: "",
|
||||
personType: "",
|
||||
userAttributeCode: "",
|
||||
type: "menu",
|
||||
target: "data-pjax",
|
||||
children: "false"
|
||||
}
|
||||
})
|
||||
if (this.$refs["addForm"]) this.$refs["addForm"].resetFields()
|
||||
},
|
||||
closeAddDialog() {
|
||||
this.$set(this, "addDialogVisible", false)
|
||||
},
|
||||
closeEditDialog() {
|
||||
this.$set(this, "editDialogVisible", false)
|
||||
},
|
||||
// 标记 USER_ATTRIBUTE 的直属子项,供编辑弹窗判断是否展示人员类型和业务编码。
|
||||
markUserAttributeChildren(rows, parentCode) {
|
||||
const children = rows || []
|
||||
children.forEach((item) => {
|
||||
this.$set(item, "isUserAttributeChild", parentCode === "USER_ATTRIBUTE")
|
||||
})
|
||||
return children
|
||||
},
|
||||
doAdd: function () {
|
||||
var self = this
|
||||
var url = base + "/platform/sys/dict/addDo"
|
||||
self.$refs["addForm"].validate(function (valid) {
|
||||
if (valid) {
|
||||
//表单验证通过
|
||||
if (!self.isAddFromSub) {
|
||||
//如果不是添加子菜单,则从级联选择框取父节点ID
|
||||
self.formData.parentId = self.parentMenu[self.parentMenu.length - 1] || ""
|
||||
}
|
||||
$.post(
|
||||
url,
|
||||
self.formData,
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
self.$message({
|
||||
message: data.msg,
|
||||
type: "success"
|
||||
})
|
||||
self.addDialogVisible = false
|
||||
self.loadChildByExpandedKeys()
|
||||
// self.initTreeTable();
|
||||
} else {
|
||||
self.$message({
|
||||
message: data.msg,
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
},
|
||||
"json"
|
||||
)
|
||||
self.$refs["addForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
// 顶级或级联新增时,从级联选择结果获取父节点;子级快捷新增沿用当前父节点。
|
||||
if (!self.isAddFromSub) {
|
||||
self.$set(self.formData, "parentId", self.parentMenu[self.parentMenu.length - 1] || "")
|
||||
}
|
||||
self.$set(self, "formLoading", true)
|
||||
$.post(url, self.formData)
|
||||
.then((data) => {
|
||||
if (data.code === 0) {
|
||||
self.$message({ message: data.msg, type: "success" })
|
||||
self.$set(self, "addDialogVisible", false)
|
||||
self.loadChildByExpandedKeys()
|
||||
} else {
|
||||
self.$message({ message: data.msg, type: "error" })
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
self.$set(self, "formLoading", false)
|
||||
})
|
||||
})
|
||||
},
|
||||
doEdit: function () {
|
||||
var self = this
|
||||
var url = base + "/platform/sys/dict/editDo"
|
||||
self.$refs["editForm"].validate(function (valid) {
|
||||
if (valid) {
|
||||
//表单验证通过
|
||||
$.post(
|
||||
url,
|
||||
self.formData,
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
self.$message({
|
||||
message: data.msg,
|
||||
type: "success"
|
||||
})
|
||||
self.editDialogVisible = false
|
||||
self.loadChildByExpandedKeys()
|
||||
// self.initTreeTable()
|
||||
} else {
|
||||
self.$message({
|
||||
message: data.msg,
|
||||
type: "error"
|
||||
})
|
||||
}
|
||||
},
|
||||
"json"
|
||||
)
|
||||
self.$refs["editForm"].validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
self.$set(self, "formLoading", true)
|
||||
$.post(url, self.formData)
|
||||
.then((data) => {
|
||||
if (data.code === 0) {
|
||||
self.$message({ message: data.msg, type: "success" })
|
||||
self.$set(self, "editDialogVisible", false)
|
||||
self.loadChildByExpandedKeys()
|
||||
} else {
|
||||
self.$message({ message: data.msg, type: "error" })
|
||||
}
|
||||
})
|
||||
.always(() => {
|
||||
self.$set(self, "formLoading", false)
|
||||
})
|
||||
})
|
||||
},
|
||||
initTreeTable: function () {
|
||||
@@ -331,18 +365,22 @@ layout("/layouts/platform.html"){
|
||||
{ pid: "" },
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
self.tableData = data.data
|
||||
self.tableKey = +new Date()
|
||||
self.$set(self, "tableData", data.data)
|
||||
self.$set(self, "tableKey", +new Date())
|
||||
const userAttributeRoot = (data.data || []).find((item) => item.code === "USER_ATTRIBUTE")
|
||||
self.$set(self, "userAttributeDictId", userAttributeRoot ? userAttributeRoot.id : "")
|
||||
}
|
||||
},
|
||||
"json"
|
||||
)
|
||||
},
|
||||
loadChild: function (tree, treeNode, resolve) {
|
||||
var self = this
|
||||
// 在之前声明的全局变量中,增加一个key为 本条数据的id,id可替换为你数据中的任意唯一值
|
||||
this.tableTreeRefreshTool[tree.id] = {}
|
||||
// 重要!保存resolve方法,以便后续使用
|
||||
this.tableTreeRefreshTool[tree.id].resolve = resolve
|
||||
this.tableTreeRefreshTool[tree.id].parentCode = tree.code
|
||||
// 记录展开次数,具体作用后续介绍
|
||||
this.tableTreeRefreshTool[tree.id].expandCount = 0
|
||||
var url = base + "/platform/sys/dict/child"
|
||||
@@ -351,7 +389,8 @@ layout("/layouts/platform.html"){
|
||||
{ pid: tree.id },
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
resolve(data.data)
|
||||
// 直接根据当前展开的父字典识别直属子项,避免依赖顶级字典异步初始化结果。
|
||||
resolve(self.markUserAttributeChildren(data.data, tree.code))
|
||||
}
|
||||
},
|
||||
"json"
|
||||
@@ -362,9 +401,10 @@ layout("/layouts/platform.html"){
|
||||
var self = this
|
||||
if ("add" == command.type) {
|
||||
self.openAdd()
|
||||
self.formData.parentId = command.id
|
||||
self.formData.parentName = command.name
|
||||
self.isAddFromSub = true
|
||||
self.$set(self.formData, "parentId", command.id)
|
||||
self.$set(self.formData, "parentName", command.name)
|
||||
self.$set(self, "isAddFromSub", true)
|
||||
self.$set(self, "showUserAttributeFields", command.row && command.row.code === "USER_ATTRIBUTE")
|
||||
}
|
||||
if ("edit" == command.type) {
|
||||
$.post(
|
||||
@@ -372,8 +412,9 @@ layout("/layouts/platform.html"){
|
||||
{},
|
||||
function (data) {
|
||||
if (data.code == 0) {
|
||||
self.formData = data.data //加载后台表单数据
|
||||
self.editDialogVisible = true //打开编辑窗口
|
||||
self.$set(self, "formData", data.data) //加载后台表单数据
|
||||
self.$set(self, "showUserAttributeFields", Boolean(command.row && command.row.isUserAttributeChild))
|
||||
self.$set(self, "editDialogVisible", true) //打开编辑窗口
|
||||
} else {
|
||||
self.$message({
|
||||
message: data.msg,
|
||||
@@ -460,15 +501,28 @@ layout("/layouts/platform.html"){
|
||||
const curr = this.tableTreeRefreshTool[v]
|
||||
// api请求
|
||||
$.get("/platform/sys/dict/child", { pid: v }).then((res) => {
|
||||
curr.resolve(res.data)
|
||||
curr.resolve(this.markUserAttributeChildren(res.data, curr.parentCode))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.initTreeTable()
|
||||
}
|
||||
},
|
||||
// 加载启用的人员类型,同时生成列表展示使用的编码名称映射。
|
||||
loadPersonTypes() {
|
||||
return this.$businessTool.getDictOptions("USER_PERSON_TYPE").then((options) => {
|
||||
const rows = options || []
|
||||
const nameMap = {}
|
||||
rows.forEach((item) => {
|
||||
nameMap[item.code] = item.name
|
||||
})
|
||||
this.$set(this, "personTypeOptions", rows)
|
||||
this.$set(this, "personTypeNameMap", nameMap)
|
||||
})
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
created() {
|
||||
this.loadPersonTypes()
|
||||
this.initTreeTable()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -70,12 +70,26 @@ const home = {
|
||||
|
||||
<div class="union-news-panel">
|
||||
<div class="union-news-panel__head">
|
||||
<div class="union-news-panel__title">工会动态</div>
|
||||
<button type="button" class="union-news-panel__title" @click="openSpreadMenu"
|
||||
:disabled="spreadSections.length < 2" :aria-expanded="spreadMenuVisible">
|
||||
{{ (spreadSections[spreadActiveIndex] || {}).label || '工会动态' }}
|
||||
<van-icon v-if="spreadSections.length > 1" name="arrow-down"></van-icon>
|
||||
</button>
|
||||
<div class="union-news-panel__more" @click="enterSpreadList">
|
||||
<span>全部动态</span>
|
||||
<van-icon name="arrow"></van-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="spreadMenuVisible" class="union-news-menu-dismiss" @click="closeSpreadMenu"></div>
|
||||
<div v-show="spreadMenuVisible" class="union-news-menu" role="menu" @keydown.esc="closeSpreadMenu">
|
||||
<button type="button" role="menuitem" v-for="(section, index) in spreadSections"
|
||||
:key="section.label + '-' + index" @click="chooseSpreadSection(index)"
|
||||
:class="{'is-selected': spreadActiveIndex === index}" :aria-current="spreadActiveIndex === index ? 'true' : null">
|
||||
<span>{{ section.label }}</span>
|
||||
<van-icon v-if="spreadActiveIndex === index" name="success" color="#1989fa"></van-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="union-news-viewport" ref="spreadViewport">
|
||||
<div v-if="spreadLoading && !spreadDynamics.length" class="union-news-list union-news-list--skeleton">
|
||||
<div class="union-news-item union-news-item--skeleton"
|
||||
v-for="index in 2"
|
||||
@@ -98,12 +112,16 @@ const home = {
|
||||
<div class="union-news-list" v-else-if="spreadDynamics.length">
|
||||
<div class="union-news-item"
|
||||
v-for="item in spreadDynamics"
|
||||
:key="item.id"
|
||||
:key="item.href || item.title"
|
||||
@click="enterSpreadDetail(item)">
|
||||
<img v-if="getSpreadCover(item.thumbUrl)"
|
||||
<img v-if="item.thumbUrl && !item.imageFailed"
|
||||
class="union-news-item__cover"
|
||||
:src="getSpreadCover(item.thumbUrl)"
|
||||
:src="item.thumbUrl" referrerpolicy="no-referrer"
|
||||
@error="$set(item, 'imageFailed', true)"
|
||||
:alt="item.title">
|
||||
<div v-else-if="spreadHasImages" class="union-news-item__cover union-news-item__placeholder">
|
||||
<van-icon name="photo-o"></van-icon>
|
||||
</div>
|
||||
<div class="union-news-item__content">
|
||||
<div class="union-news-item__tags">
|
||||
<span class="union-news-item__tag"
|
||||
@@ -117,9 +135,10 @@ const home = {
|
||||
</div>
|
||||
<div class="union-news-item__meta">
|
||||
<span>{{ formatSpreadDate(item.publishedAt || item.createdAt) }}</span>
|
||||
<span class="union-news-item__views">
|
||||
<span v-if="item.viewCount !== null && item.viewCount !== undefined"
|
||||
class="union-news-item__views">
|
||||
<van-icon name="eye-o"></van-icon>
|
||||
{{ item.viewCount || 0 }}
|
||||
{{ item.viewCount }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,6 +150,7 @@ const home = {
|
||||
alt="暂无工会动态">
|
||||
<span class="union-news-empty__text">暂无工会动态</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="classroom-section">
|
||||
@@ -247,6 +267,10 @@ const home = {
|
||||
classroomRecommendOptions: [],
|
||||
showHomeSummary: false,
|
||||
homeScrollContainer: null,
|
||||
spreadSections: [],
|
||||
spreadMenuVisible: false,
|
||||
spreadHistoryToken: null,
|
||||
spreadActiveIndex: 0,
|
||||
spreadDynamics: [],
|
||||
spreadLoading: true,
|
||||
spreadLoadSeq: 0,
|
||||
@@ -311,6 +335,10 @@ const home = {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
spreadHasImages() {
|
||||
// 按当前栏目统一保留图片列;异步补图及加载失败都会重新计算,全部无图时文字占满行宽。
|
||||
return this.spreadDynamics.some(item => item.thumbUrl && !item.imageFailed)
|
||||
},
|
||||
currentBanner() {
|
||||
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
|
||||
}
|
||||
@@ -516,18 +544,29 @@ const home = {
|
||||
listSpreadHomeContent() {
|
||||
const loadSeq = ++this.spreadLoadSeq
|
||||
this.spreadLoading = true
|
||||
this.$axios.post("/platform/h5/spread/pageData", {
|
||||
sortType: "latest",
|
||||
pageNumber: 1,
|
||||
pageSize: 10
|
||||
}).then((res) => {
|
||||
this.$axios.post("/platform/home/getNews").then((res) => {
|
||||
if (loadSeq === this.spreadLoadSeq && res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.spreadDynamics = data.list || []
|
||||
// 接口按栏目返回 href/time/text,移动端保存全部栏目供切换,并映射为现有动态卡片字段。
|
||||
const sections = (Array.isArray(res.data) ? res.data : []).map(section => ({
|
||||
label: section.label || "工会动态",
|
||||
value: (Array.isArray(section.value) ? section.value : []).slice(0, 10).map(item => ({
|
||||
href: item.href || "",
|
||||
thumbUrl: /^https?:\/\//i.test(item.thumbUrl || "") ? item.thumbUrl : "",
|
||||
imageFailed: false,
|
||||
imageRequested: false,
|
||||
title: item.text || "",
|
||||
publishedAt: item.time || "",
|
||||
categoryName: section.label || "工会动态"
|
||||
})).filter(item => item.title)
|
||||
}))
|
||||
const firstAvailableIndex = sections.findIndex(section => section.value.length)
|
||||
this.$set(this, "spreadSections", sections)
|
||||
this.selectSpreadSection(firstAvailableIndex >= 0 ? firstAvailableIndex : 0)
|
||||
}
|
||||
}).catch(() => {
|
||||
if (loadSeq === this.spreadLoadSeq) {
|
||||
this.spreadDynamics = []
|
||||
this.$set(this, "spreadSections", [])
|
||||
this.$set(this, "spreadDynamics", [])
|
||||
}
|
||||
}).finally(() => {
|
||||
if (loadSeq === this.spreadLoadSeq) {
|
||||
@@ -535,6 +574,46 @@ const home = {
|
||||
}
|
||||
})
|
||||
},
|
||||
selectSpreadSection(index) {
|
||||
// 切换栏目时复位列表滚动位置;固定视口避免不同数据量引发页面跳动。
|
||||
// 新闻样式锚定 home-page 根节点作用域,动态行无需等待 styleMixin 异步补属性。
|
||||
const section = this.spreadSections[index] || {}
|
||||
this.$set(this, "spreadActiveIndex", index)
|
||||
this.$set(this, "spreadDynamics", section.value || [])
|
||||
if (this.$refs.spreadViewport) this.$refs.spreadViewport.scrollTop = 0
|
||||
this.loadSpreadImages()
|
||||
},
|
||||
loadSpreadImages() {
|
||||
// href 传原始新闻链接;data 为正文首图地址。每条只补取一次,回填原对象避免快速切换串图。
|
||||
const missing = this.spreadDynamics.filter(item => !item.thumbUrl && !item.imageRequested)
|
||||
missing.forEach(item => this.$set(item, "imageRequested", true))
|
||||
// 串行补图限制外站并发;切换后仍可缓存到对应栏目,不阻塞新闻展示。
|
||||
missing.reduce((pending, item) => pending.then(() => {
|
||||
return this.$axios.post("/platform/home/getNewsImage", {href: item.href}).then(res => {
|
||||
if (res.code === 0 && /^https?:\/\//i.test(res.data || "")) {
|
||||
this.$set(item, "thumbUrl", res.data)
|
||||
this.$set(item, "imageFailed", false)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}), Promise.resolve())
|
||||
},
|
||||
openSpreadMenu() {
|
||||
// 栏目选择弹层交由全局历史栈管理,系统返回键仅关闭当前弹层。
|
||||
if (this.spreadMenuVisible) {
|
||||
this.closeSpreadMenu()
|
||||
return
|
||||
}
|
||||
if (this.spreadSections.length < 2) return
|
||||
window.h5PopupHistory.open(this.spreadHistoryToken, "news-section")
|
||||
this.$set(this, "spreadMenuVisible", true)
|
||||
},
|
||||
closeSpreadMenu() {
|
||||
if (this.spreadMenuVisible) window.h5PopupHistory.close(this.spreadHistoryToken, "news-section")
|
||||
},
|
||||
chooseSpreadSection(index) {
|
||||
this.selectSpreadSection(index)
|
||||
this.closeSpreadMenu()
|
||||
},
|
||||
getSpreadCategoryLabels(item) {
|
||||
const labels = [item.parentCategoryName, item.childCategoryName].filter(Boolean)
|
||||
return labels.length ? labels : (item.categoryName ? [item.categoryName] : [])
|
||||
@@ -561,13 +640,29 @@ const home = {
|
||||
return first && (first.src || first.url || first.data || (first.response && first.response.data)) || ""
|
||||
},
|
||||
formatSpreadDate(value) {
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD") : ""
|
||||
if (!value) {
|
||||
return ""
|
||||
}
|
||||
const numberValue = Number(value)
|
||||
return Number.isNaN(numberValue) ? String(value) : this.$moment(numberValue).format("YYYY-MM-DD")
|
||||
},
|
||||
enterSpreadDetail(item) {
|
||||
this.$pjaxReplace("/platform/h5/spread/detail?id=" + encodeURIComponent(item.id) + "&from=home")
|
||||
const url = this.resolveUnionNewsUrl(item.href)
|
||||
if (url) {
|
||||
window.location.href = url
|
||||
}
|
||||
},
|
||||
enterSpreadList() {
|
||||
this.$pjaxReplace("/platform/h5/spread")
|
||||
window.location.href = "https://xgh.cug.edu.cn/"
|
||||
},
|
||||
resolveUnionNewsUrl(href) {
|
||||
if (!href) {
|
||||
return ""
|
||||
}
|
||||
if (/^https?:\/\//i.test(href)) {
|
||||
return href
|
||||
}
|
||||
return new URL(href, "https://xgh.cug.edu.cn/").href
|
||||
},
|
||||
menuClick(item) {
|
||||
this.$pjaxReplace(item.href)
|
||||
@@ -606,6 +701,10 @@ const home = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 注册弹层返回回调,关闭状态由历史回退同步,避免留下多余历史记录。
|
||||
this.spreadHistoryToken = window.h5PopupHistory.register(() => {
|
||||
this.$set(this, "spreadMenuVisible", false)
|
||||
})
|
||||
this.listHomeBrand()
|
||||
this.listRecommendApp()
|
||||
this.listClassroomCourses()
|
||||
@@ -623,6 +722,8 @@ const home = {
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
const token = this.spreadHistoryToken
|
||||
window.h5PopupHistory.clear(token).then(() => window.h5PopupHistory.unregister(token))
|
||||
if (this.homeScrollContainer) {
|
||||
this.homeScrollContainer.removeEventListener("scroll", this.handleHomeScroll)
|
||||
}
|
||||
@@ -1383,7 +1484,8 @@ const home = {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.union-news-panel {
|
||||
.home-page/deep/ .union-news-panel {
|
||||
position: relative;
|
||||
margin: 15px 12px 7px;
|
||||
padding: 13px 12px 8px;
|
||||
border-radius: 10px;
|
||||
@@ -1391,21 +1493,29 @@ const home = {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.union-news-panel__head {
|
||||
.home-page/deep/ .union-news-panel__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.union-news-panel__title {
|
||||
.home-page/deep/ .union-news-panel__title {
|
||||
position: relative;
|
||||
z-index: 42;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #202124;
|
||||
font-size: 15px;
|
||||
line-height: 21px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.union-news-panel__more {
|
||||
.home-page/deep/ .union-news-panel__more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #969799;
|
||||
@@ -1413,32 +1523,68 @@ const home = {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.union-news-panel__more .van-icon {
|
||||
.home-page/deep/ .union-news-panel__more .van-icon {
|
||||
margin-left: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.union-news-list {
|
||||
.home-page/deep/ .union-news-menu-dismiss {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-menu {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 12px;
|
||||
width: 124px;
|
||||
padding: 5px;
|
||||
z-index: 43;
|
||||
border: 1px solid #e8edf3;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-shadow: 0 5px 16px rgba(35, 55, 80, .12);
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-menu button {
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 9px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
color: #444;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-menu button.is-selected {
|
||||
color: #1989fa;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-list {
|
||||
margin-top: 7px;
|
||||
padding-bottom: 3px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
display: block;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.union-news-list::-webkit-scrollbar {
|
||||
.home-page/deep/ .union-news-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.union-news-item--skeleton {
|
||||
.home-page/deep/ .union-news-item--skeleton {
|
||||
height: 96px;
|
||||
min-height: 96px;
|
||||
}
|
||||
|
||||
.union-news-skeleton__cover {
|
||||
.home-page/deep/ .union-news-skeleton__cover {
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
margin-right: 10px;
|
||||
@@ -1446,65 +1592,93 @@ const home = {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.union-news-skeleton__content {
|
||||
.home-page/deep/ .union-news-skeleton__content {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.union-news-skeleton__tags { height: 15px; margin-bottom: 4px; display: flex; gap: 4px; }
|
||||
.union-news-skeleton__tag { width: 48px; height: 15px; border-radius: 999px; }
|
||||
.union-news-skeleton__tag--short { width: 38px; }
|
||||
.union-news-skeleton__line { height: 10px; border-radius: 3px; }
|
||||
.union-news-skeleton__line--title { width: 88%; }
|
||||
.union-news-skeleton__line--summary { width: 72%; margin-top: 7px; }
|
||||
.union-news-skeleton__meta { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.union-news-skeleton__meta span { width: 55px; height: 8px; border-radius: 3px; }
|
||||
.union-news-skeleton__meta span:last-child { width: 28px; }
|
||||
.home-page/deep/ .union-news-skeleton__tags { height: 15px; margin-bottom: 4px; display: flex; gap: 4px; }
|
||||
.home-page/deep/ .union-news-skeleton__tag { width: 48px; height: 15px; border-radius: 999px; }
|
||||
.home-page/deep/ .union-news-skeleton__tag--short { width: 38px; }
|
||||
.home-page/deep/ .union-news-skeleton__line { height: 10px; border-radius: 3px; }
|
||||
.home-page/deep/ .union-news-skeleton__line--title { width: 88%; }
|
||||
.home-page/deep/ .union-news-skeleton__line--summary { width: 72%; margin-top: 7px; }
|
||||
.home-page/deep/ .union-news-skeleton__meta { margin-top: 8px; display: flex; align-items: center; justify-content: space-between; }
|
||||
.home-page/deep/ .union-news-skeleton__meta span { width: 55px; height: 8px; border-radius: 3px; }
|
||||
.home-page/deep/ .union-news-skeleton__meta span:last-child { width: 28px; }
|
||||
|
||||
.union-news-item {
|
||||
width: calc(100% - 32px);
|
||||
min-width: calc(100% - 32px);
|
||||
min-height: 96px;
|
||||
padding: 10px;
|
||||
.home-page/deep/ .union-news-item {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 96px;
|
||||
padding: 12px 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #edf1f6;
|
||||
border-radius: 9px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.union-news-item + .union-news-item {
|
||||
.home-page/deep/ .union-news-item + .union-news-item {
|
||||
border-top: 1px solid #edf1f6;
|
||||
}
|
||||
|
||||
.union-news-item:active {
|
||||
.home-page/deep/ .union-news-item:active {
|
||||
background-color: #f7faff;
|
||||
}
|
||||
|
||||
.union-news-item__cover {
|
||||
.home-page/deep/ .union-news-item__cover {
|
||||
width: 80px;
|
||||
height: 48px;
|
||||
height: 60px;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.union-news-item__content {
|
||||
.home-page/deep/ .union-news-viewport {
|
||||
height: 288px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-item__placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f4f8;
|
||||
color: #9aaabe;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-empty {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-empty__image {
|
||||
max-height: 200px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.home-page/deep/ .union-news-item__content {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.union-news-item__tags {
|
||||
.home-page/deep/ .union-news-item__tags {
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.union-news-item__tag {
|
||||
.home-page/deep/ .union-news-item__tag {
|
||||
max-width: 46%;
|
||||
padding: 1px 6px;
|
||||
overflow: hidden;
|
||||
@@ -1517,12 +1691,12 @@ const home = {
|
||||
background-color: #eaf4ff;
|
||||
}
|
||||
|
||||
.union-news-item__tag--secondary {
|
||||
.home-page/deep/ .union-news-item__tag--secondary {
|
||||
color: #627ea4;
|
||||
background-color: #f0f4f9;
|
||||
}
|
||||
|
||||
.union-news-item__title {
|
||||
.home-page/deep/ .union-news-item__title {
|
||||
overflow: hidden;
|
||||
color: #202124;
|
||||
font-size: 13px;
|
||||
@@ -1532,7 +1706,7 @@ const home = {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.union-news-item__summary {
|
||||
.home-page/deep/ .union-news-item__summary {
|
||||
margin-top: 2px;
|
||||
overflow: hidden;
|
||||
color: #7d8797;
|
||||
@@ -1542,7 +1716,7 @@ const home = {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.union-news-item__meta {
|
||||
.home-page/deep/ .union-news-item__meta {
|
||||
margin-top: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1552,17 +1726,17 @@ const home = {
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.union-news-item__views {
|
||||
.home-page/deep/ .union-news-item__views {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.union-news-item__views .van-icon {
|
||||
.home-page/deep/ .union-news-item__views .van-icon {
|
||||
margin-right: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.union-news-empty {
|
||||
.home-page/deep/ .union-news-empty {
|
||||
display: flex;
|
||||
padding: 12px 0 16px;
|
||||
flex-direction: column;
|
||||
@@ -1573,7 +1747,7 @@ const home = {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.union-news-empty__image {
|
||||
.home-page/deep/ .union-news-empty__image {
|
||||
display: block;
|
||||
width: 82%;
|
||||
max-width: 280px;
|
||||
@@ -1581,7 +1755,7 @@ const home = {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.union-news-empty__text {
|
||||
.home-page/deep/ .union-news-empty__text {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user