会员管理整改
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.SaCheckLogin;
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import com.budwk.app.base.annotation.SLog;
|
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.base.result.Result;
|
||||||
import com.budwk.app.sys.models.Sys_dict;
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
import com.budwk.app.sys.services.SysDictService;
|
import com.budwk.app.sys.services.SysDictService;
|
||||||
@@ -119,6 +120,8 @@ public class SysDictController {
|
|||||||
sysDictService.save(dict, parentId);
|
sysDictService.save(dict, parentId);
|
||||||
sysDictService.clearCache();
|
sysDictService.clearCache();
|
||||||
return Result.success("system.success");
|
return Result.success("system.success");
|
||||||
|
} catch (BaseException e) {
|
||||||
|
return Result.error(e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error("system.error");
|
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) {
|
public Object editDo(@Param("..") Sys_dict dict, @Param("parentId") String parentId, HttpServletRequest req) {
|
||||||
try {
|
try {
|
||||||
dict.setUpdatedBy(SecurityUtil.getUserId());
|
dict.setUpdatedBy(SecurityUtil.getUserId());
|
||||||
sysDictService.updateIgnoreNull(dict);
|
sysDictService.updateDict(dict);
|
||||||
sysDictService.clearCache();
|
sysDictService.clearCache();
|
||||||
return Result.success();
|
return Result.success();
|
||||||
|
} catch (BaseException e) {
|
||||||
|
return Result.error(e.getMessage());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.error();
|
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_activity;
|
||||||
import com.budwk.app.sys.models.Sys_home_template;
|
import com.budwk.app.sys.models.Sys_home_template;
|
||||||
import com.budwk.app.sys.models.Sys_menu;
|
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.SysMenuService;
|
||||||
import com.budwk.app.sys.services.SysUserService;
|
import com.budwk.app.sys.services.SysUserService;
|
||||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||||
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
import com.budwk.app.zhgh.activity.basic.service.ActivityBasicScopeService;
|
||||||
import io.swagger.annotations.ApiOperation;
|
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.Cnd;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.dao.FieldFilter;
|
import org.nutz.dao.FieldFilter;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
import org.nutz.dao.sql.Sql;
|
import org.nutz.dao.sql.Sql;
|
||||||
import org.nutz.dao.util.Daos;
|
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.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.json.Json;
|
|
||||||
import org.nutz.lang.Lang;
|
import org.nutz.lang.Lang;
|
||||||
import org.nutz.lang.Strings;
|
import org.nutz.lang.Strings;
|
||||||
import org.nutz.lang.util.NutMap;
|
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.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -58,9 +52,9 @@ public class SysHomeController {
|
|||||||
@Inject
|
@Inject
|
||||||
private SysUserService sysUserService;
|
private SysUserService sysUserService;
|
||||||
@Inject
|
@Inject
|
||||||
private RedisService redisService;
|
|
||||||
@Inject
|
|
||||||
private ActivityBasicScopeService activityBasicScopeService;
|
private ActivityBasicScopeService activityBasicScopeService;
|
||||||
|
@Inject
|
||||||
|
private SysHomeNewsService sysHomeNewsService;
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@Ok("re")
|
@Ok("re")
|
||||||
@@ -293,34 +287,17 @@ public class SysHomeController {
|
|||||||
@SaCheckLogin
|
@SaCheckLogin
|
||||||
@ApiOperation("获取新闻")
|
@ApiOperation("获取新闻")
|
||||||
@Ok("json")
|
@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() {
|
public Result getNews() {
|
||||||
try {
|
return Result.success(sysHomeNewsService.getWebsiteNews());
|
||||||
String url = "https://xgh.cug.edu.cn/";
|
}
|
||||||
Document document = Jsoup.connect(url).get();
|
|
||||||
Elements homeElements = document.select(".gh,.jc,.fc,.jg");
|
|
||||||
|
|
||||||
List<Map<String, Object>> news = homeElements.stream().map(homeElement -> {
|
/**
|
||||||
//获取板块名称
|
* href 传首页新闻项原始链接;成功结果 data 为图片地址字符串,空字符串表示没有可用图片。
|
||||||
String name = homeElement.selectFirst(".title").text();
|
*/
|
||||||
Elements liElements = homeElement.select("li");
|
@At
|
||||||
List<NutMap> ele = liElements.stream().map(liElement -> {
|
@SaCheckLogin
|
||||||
NutMap row = NutMap.NEW();
|
@Ok("json")
|
||||||
String href = liElement.selectFirst("a").attr("href");
|
public Result getNewsImage(@Param("href") String href) {
|
||||||
String time = liElement.selectFirst("span").text();
|
return Result.success(sysHomeNewsService.getNewsImage(href));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,16 @@ public class Sys_dict extends BaseModel implements Serializable {
|
|||||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||||
private String code;
|
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
|
@Column
|
||||||
@Comment("启用状态")
|
@Comment("启用状态")
|
||||||
@ColDefine(type = ColType.BOOLEAN)
|
@ColDefine(type = ColType.BOOLEAN)
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ public interface SysDictService extends BaseService<Sys_dict> {
|
|||||||
*/
|
*/
|
||||||
void save(Sys_dict dict, String pid);
|
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"})
|
@IocBean(args = {"refer:dao"})
|
||||||
@CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "sys_dict", isHash = true)
|
@CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "sys_dict", isHash = true)
|
||||||
public class SysDictServiceImpl extends BaseServiceImpl<Sys_dict> implements SysDictService {
|
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) {
|
public SysDictServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
@@ -132,18 +135,76 @@ public class SysDictServiceImpl extends BaseServiceImpl<Sys_dict> implements Sys
|
|||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void save(Sys_dict dict, String pid) {
|
public void save(Sys_dict dict, String pid) {
|
||||||
String path = "";
|
String path = "";
|
||||||
|
Sys_dict parentDict = null;
|
||||||
if (!Strings.isEmpty(pid)) {
|
if (!Strings.isEmpty(pid)) {
|
||||||
Sys_dict pp = this.fetch(pid);
|
parentDict = this.fetch(pid);
|
||||||
path = pp.getPath();
|
path = parentDict.getPath();
|
||||||
}
|
}
|
||||||
dict.setPath(getSubPath("sys_dict", "path", path));
|
dict.setPath(getSubPath("sys_dict", "path", path));
|
||||||
dict.setParentId(pid);
|
dict.setParentId(pid);
|
||||||
|
validateUserAttribute(dict, parentDict);
|
||||||
dao().insert(dict);
|
dao().insert(dict);
|
||||||
if (!Strings.isEmpty(pid)) {
|
if (!Strings.isEmpty(pid)) {
|
||||||
this.update(Chain.make("hasChildren", true), Cnd.where("id", "=", 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)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void saveByParentCode(Sys_dict dict, String parentCode) {
|
public void saveByParentCode(Sys_dict dict, String parentCode) {
|
||||||
Sys_dict parentDict = fetch(Cnd.where("code", "=", 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 初始化器。
|
// NutzBoot 使用编程式 Context,必须显式运行官方 WebSocket 初始化器。
|
||||||
StandardContext context = (StandardContext) tomcat.getHost().findChild(starter.getContextPath());
|
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.setUseHttpOnly(conf.getBoolean("tomcat.session.cookie.httponly", true));
|
||||||
context.addServletContainerInitializer(new WsSci(), Set.of(WkWebSocket.class));
|
context.addServletContainerInitializer(new WsSci(), Set.of(WkWebSocket.class));
|
||||||
// Tomcat 的错误页再次 forward 时仍保持 ERROR 派发;让 MVC 渲染原业务错误页。
|
// Tomcat 的错误页再次 forward 时仍保持 ERROR 派发;让 MVC 渲染原业务错误页。
|
||||||
|
|||||||
@@ -323,7 +323,8 @@
|
|||||||
let h5GlobalLoading = null
|
let h5GlobalLoading = null
|
||||||
let h5PendingRequestCount = 0
|
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) {
|
if (!h5GlobalLoading) {
|
||||||
h5GlobalLoading = vant.Toast.loading({
|
h5GlobalLoading = vant.Toast.loading({
|
||||||
icon: icon,
|
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)
|
showH5GlobalLoading(icon)
|
||||||
|
|
||||||
const closeLoading = function() {
|
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="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">
|
<el-table-column label="启用状态" header-align="center" prop="disabled" align="center" :show-overflow-tooltip="true">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<i v-if="scope.row.disabled" class="fa fa-circle text-danger ml5"></i>
|
<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>
|
<span class="ti-angle-down"></span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-dropdown-menu slot="dropdown">
|
<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 divided :command="{type:'enable',id:scope.row.id,name:scope.row.name,row:scope.row}">
|
||||||
启用
|
启用
|
||||||
</el-dropdown-item>
|
</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 :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-item :command="{type:'delete',id:scope.row.id,name:scope.row.name}">删除</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -77,12 +85,20 @@ layout("/layouts/platform.html"){
|
|||||||
></el-cascader>
|
></el-cascader>
|
||||||
<el-input v-if="isAddFromSub" type="text" v-model="formData.parentName" :disabled="true"></el-input>
|
<el-input v-if="isAddFromSub" type="text" v-model="formData.parentName" :disabled="true"></el-input>
|
||||||
</el-form-item>
|
</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-form-item prop="name" label="字典名称">
|
||||||
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="3" type="text"></el-input>
|
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="3" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="code" label="字典代码">
|
<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-input maxlength="100" placeholder="字典代码" v-model="formData.code" auto-complete="off" tabindex="5" type="text"></el-input>
|
||||||
</el-form-item>
|
</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-form-item prop="remark" label="字典备注">
|
||||||
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -91,18 +107,26 @@ layout("/layouts/platform.html"){
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button @click="addDialogVisible = false">取 消</el-button>
|
<el-button @click="closeAddDialog">取 消</el-button>
|
||||||
<el-button type="primary" @click="doAdd">确 定</el-button>
|
<el-button type="primary" :loading="formLoading" @click="doAdd">确 定</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog title="修改字典" :visible.sync="editDialogVisible" :close-on-click-modal="false" width="50%">
|
<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 :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-form-item prop="name" label="字典名称">
|
||||||
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="1" type="text"></el-input>
|
<el-input maxlength="100" placeholder="字典名称" v-model="formData.name" auto-complete="off" tabindex="1" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="code" label="字典代码">
|
<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-input maxlength="100" placeholder="字典代码" v-model="formData.code" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||||
</el-form-item>
|
</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-form-item prop="remark" label="字典备注">
|
||||||
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
<el-input maxlength="100" placeholder="字典备注" v-model="formData.remark" auto-complete="off" tabindex="2" type="text"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -111,8 +135,8 @@ layout("/layouts/platform.html"){
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button @click="editDialogVisible = false">取 消</el-button>
|
<el-button @click="closeEditDialog">取 消</el-button>
|
||||||
<el-button type="primary" @click="doEdit">确 定</el-button>
|
<el-button type="primary" :loading="formLoading" @click="doEdit">确 定</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog title="字典排序" :visible.sync="sortDialogVisible" width="50%">
|
<el-dialog title="字典排序" :visible.sync="sortDialogVisible" width="50%">
|
||||||
@@ -136,12 +160,19 @@ layout("/layouts/platform.html"){
|
|||||||
editDialogVisible: false,
|
editDialogVisible: false,
|
||||||
sortDialogVisible: false,
|
sortDialogVisible: false,
|
||||||
isAddFromSub: false,
|
isAddFromSub: false,
|
||||||
|
showUserAttributeFields: false,
|
||||||
|
formLoading: false,
|
||||||
|
userAttributeDictId: "",
|
||||||
|
personTypeOptions: [],
|
||||||
|
personTypeNameMap: {},
|
||||||
tableData: [], //后台取出的数据
|
tableData: [], //后台取出的数据
|
||||||
tableKey: "",
|
tableKey: "",
|
||||||
formData: {
|
formData: {
|
||||||
id: "",
|
id: "",
|
||||||
parentId: "",
|
parentId: "",
|
||||||
parentName: "",
|
parentName: "",
|
||||||
|
personType: "",
|
||||||
|
userAttributeCode: "",
|
||||||
type: "menu",
|
type: "menu",
|
||||||
target: "data-pjax",
|
target: "data-pjax",
|
||||||
children: "false"
|
children: "false"
|
||||||
@@ -244,83 +275,86 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
openAdd: function () {
|
openAdd: function () {
|
||||||
var self = this
|
var self = this
|
||||||
self.isAddFromSub = false
|
self.$set(self, "isAddFromSub", false)
|
||||||
self.addDialogVisible = true
|
self.$set(self, "showUserAttributeFields", false)
|
||||||
self.parentMenu = []
|
self.$set(self, "addDialogVisible", true)
|
||||||
self.menuOptions = []
|
self.$set(self, "parentMenu", [])
|
||||||
|
self.$set(self, "menuOptions", [])
|
||||||
//表单还原为初始值
|
//表单还原为初始值
|
||||||
self.formData = {
|
self.$set(self, "formData", {
|
||||||
id: "",
|
id: "",
|
||||||
parentId: "",
|
parentId: "",
|
||||||
parentName: "",
|
parentName: "",
|
||||||
|
personType: "",
|
||||||
|
userAttributeCode: "",
|
||||||
type: "menu",
|
type: "menu",
|
||||||
target: "data-pjax",
|
target: "data-pjax",
|
||||||
children: "false"
|
children: "false"
|
||||||
}
|
})
|
||||||
if (this.$refs["addForm"]) this.$refs["addForm"].resetFields()
|
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 () {
|
doAdd: function () {
|
||||||
var self = this
|
var self = this
|
||||||
var url = base + "/platform/sys/dict/addDo"
|
var url = base + "/platform/sys/dict/addDo"
|
||||||
self.$refs["addForm"].validate(function (valid) {
|
self.$refs["addForm"].validate((valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
//表单验证通过
|
return
|
||||||
|
}
|
||||||
|
// 顶级或级联新增时,从级联选择结果获取父节点;子级快捷新增沿用当前父节点。
|
||||||
if (!self.isAddFromSub) {
|
if (!self.isAddFromSub) {
|
||||||
//如果不是添加子菜单,则从级联选择框取父节点ID
|
self.$set(self.formData, "parentId", self.parentMenu[self.parentMenu.length - 1] || "")
|
||||||
self.formData.parentId = self.parentMenu[self.parentMenu.length - 1] || ""
|
|
||||||
}
|
}
|
||||||
$.post(
|
self.$set(self, "formLoading", true)
|
||||||
url,
|
$.post(url, self.formData)
|
||||||
self.formData,
|
.then((data) => {
|
||||||
function (data) {
|
if (data.code === 0) {
|
||||||
if (data.code == 0) {
|
self.$message({ message: data.msg, type: "success" })
|
||||||
self.$message({
|
self.$set(self, "addDialogVisible", false)
|
||||||
message: data.msg,
|
|
||||||
type: "success"
|
|
||||||
})
|
|
||||||
self.addDialogVisible = false
|
|
||||||
self.loadChildByExpandedKeys()
|
self.loadChildByExpandedKeys()
|
||||||
// self.initTreeTable();
|
|
||||||
} else {
|
} else {
|
||||||
self.$message({
|
self.$message({ message: data.msg, type: "error" })
|
||||||
message: data.msg,
|
}
|
||||||
type: "error"
|
})
|
||||||
|
.always(() => {
|
||||||
|
self.$set(self, "formLoading", false)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
"json"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
doEdit: function () {
|
doEdit: function () {
|
||||||
var self = this
|
var self = this
|
||||||
var url = base + "/platform/sys/dict/editDo"
|
var url = base + "/platform/sys/dict/editDo"
|
||||||
self.$refs["editForm"].validate(function (valid) {
|
self.$refs["editForm"].validate((valid) => {
|
||||||
if (valid) {
|
if (!valid) {
|
||||||
//表单验证通过
|
return
|
||||||
$.post(
|
}
|
||||||
url,
|
self.$set(self, "formLoading", true)
|
||||||
self.formData,
|
$.post(url, self.formData)
|
||||||
function (data) {
|
.then((data) => {
|
||||||
if (data.code == 0) {
|
if (data.code === 0) {
|
||||||
self.$message({
|
self.$message({ message: data.msg, type: "success" })
|
||||||
message: data.msg,
|
self.$set(self, "editDialogVisible", false)
|
||||||
type: "success"
|
|
||||||
})
|
|
||||||
self.editDialogVisible = false
|
|
||||||
self.loadChildByExpandedKeys()
|
self.loadChildByExpandedKeys()
|
||||||
// self.initTreeTable()
|
|
||||||
} else {
|
} else {
|
||||||
self.$message({
|
self.$message({ message: data.msg, type: "error" })
|
||||||
message: data.msg,
|
}
|
||||||
type: "error"
|
})
|
||||||
|
.always(() => {
|
||||||
|
self.$set(self, "formLoading", false)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
|
||||||
"json"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initTreeTable: function () {
|
initTreeTable: function () {
|
||||||
@@ -331,18 +365,22 @@ layout("/layouts/platform.html"){
|
|||||||
{ pid: "" },
|
{ pid: "" },
|
||||||
function (data) {
|
function (data) {
|
||||||
if (data.code == 0) {
|
if (data.code == 0) {
|
||||||
self.tableData = data.data
|
self.$set(self, "tableData", data.data)
|
||||||
self.tableKey = +new Date()
|
self.$set(self, "tableKey", +new Date())
|
||||||
|
const userAttributeRoot = (data.data || []).find((item) => item.code === "USER_ATTRIBUTE")
|
||||||
|
self.$set(self, "userAttributeDictId", userAttributeRoot ? userAttributeRoot.id : "")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json"
|
"json"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
loadChild: function (tree, treeNode, resolve) {
|
loadChild: function (tree, treeNode, resolve) {
|
||||||
|
var self = this
|
||||||
// 在之前声明的全局变量中,增加一个key为 本条数据的id,id可替换为你数据中的任意唯一值
|
// 在之前声明的全局变量中,增加一个key为 本条数据的id,id可替换为你数据中的任意唯一值
|
||||||
this.tableTreeRefreshTool[tree.id] = {}
|
this.tableTreeRefreshTool[tree.id] = {}
|
||||||
// 重要!保存resolve方法,以便后续使用
|
// 重要!保存resolve方法,以便后续使用
|
||||||
this.tableTreeRefreshTool[tree.id].resolve = resolve
|
this.tableTreeRefreshTool[tree.id].resolve = resolve
|
||||||
|
this.tableTreeRefreshTool[tree.id].parentCode = tree.code
|
||||||
// 记录展开次数,具体作用后续介绍
|
// 记录展开次数,具体作用后续介绍
|
||||||
this.tableTreeRefreshTool[tree.id].expandCount = 0
|
this.tableTreeRefreshTool[tree.id].expandCount = 0
|
||||||
var url = base + "/platform/sys/dict/child"
|
var url = base + "/platform/sys/dict/child"
|
||||||
@@ -351,7 +389,8 @@ layout("/layouts/platform.html"){
|
|||||||
{ pid: tree.id },
|
{ pid: tree.id },
|
||||||
function (data) {
|
function (data) {
|
||||||
if (data.code == 0) {
|
if (data.code == 0) {
|
||||||
resolve(data.data)
|
// 直接根据当前展开的父字典识别直属子项,避免依赖顶级字典异步初始化结果。
|
||||||
|
resolve(self.markUserAttributeChildren(data.data, tree.code))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json"
|
"json"
|
||||||
@@ -362,9 +401,10 @@ layout("/layouts/platform.html"){
|
|||||||
var self = this
|
var self = this
|
||||||
if ("add" == command.type) {
|
if ("add" == command.type) {
|
||||||
self.openAdd()
|
self.openAdd()
|
||||||
self.formData.parentId = command.id
|
self.$set(self.formData, "parentId", command.id)
|
||||||
self.formData.parentName = command.name
|
self.$set(self.formData, "parentName", command.name)
|
||||||
self.isAddFromSub = true
|
self.$set(self, "isAddFromSub", true)
|
||||||
|
self.$set(self, "showUserAttributeFields", command.row && command.row.code === "USER_ATTRIBUTE")
|
||||||
}
|
}
|
||||||
if ("edit" == command.type) {
|
if ("edit" == command.type) {
|
||||||
$.post(
|
$.post(
|
||||||
@@ -372,8 +412,9 @@ layout("/layouts/platform.html"){
|
|||||||
{},
|
{},
|
||||||
function (data) {
|
function (data) {
|
||||||
if (data.code == 0) {
|
if (data.code == 0) {
|
||||||
self.formData = data.data //加载后台表单数据
|
self.$set(self, "formData", data.data) //加载后台表单数据
|
||||||
self.editDialogVisible = true //打开编辑窗口
|
self.$set(self, "showUserAttributeFields", Boolean(command.row && command.row.isUserAttributeChild))
|
||||||
|
self.$set(self, "editDialogVisible", true) //打开编辑窗口
|
||||||
} else {
|
} else {
|
||||||
self.$message({
|
self.$message({
|
||||||
message: data.msg,
|
message: data.msg,
|
||||||
@@ -460,15 +501,28 @@ layout("/layouts/platform.html"){
|
|||||||
const curr = this.tableTreeRefreshTool[v]
|
const curr = this.tableTreeRefreshTool[v]
|
||||||
// api请求
|
// api请求
|
||||||
$.get("/platform/sys/dict/child", { pid: v }).then((res) => {
|
$.get("/platform/sys/dict/child", { pid: v }).then((res) => {
|
||||||
curr.resolve(res.data)
|
curr.resolve(this.markUserAttributeChildren(res.data, curr.parentCode))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.initTreeTable()
|
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()
|
this.initTreeTable()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,12 +70,26 @@ const home = {
|
|||||||
|
|
||||||
<div class="union-news-panel">
|
<div class="union-news-panel">
|
||||||
<div class="union-news-panel__head">
|
<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">
|
<div class="union-news-panel__more" @click="enterSpreadList">
|
||||||
<span>全部动态</span>
|
<span>全部动态</span>
|
||||||
<van-icon name="arrow"></van-icon>
|
<van-icon name="arrow"></van-icon>
|
||||||
</div>
|
</div>
|
||||||
</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 v-if="spreadLoading && !spreadDynamics.length" class="union-news-list union-news-list--skeleton">
|
||||||
<div class="union-news-item union-news-item--skeleton"
|
<div class="union-news-item union-news-item--skeleton"
|
||||||
v-for="index in 2"
|
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-list" v-else-if="spreadDynamics.length">
|
||||||
<div class="union-news-item"
|
<div class="union-news-item"
|
||||||
v-for="item in spreadDynamics"
|
v-for="item in spreadDynamics"
|
||||||
:key="item.id"
|
:key="item.href || item.title"
|
||||||
@click="enterSpreadDetail(item)">
|
@click="enterSpreadDetail(item)">
|
||||||
<img v-if="getSpreadCover(item.thumbUrl)"
|
<img v-if="item.thumbUrl && !item.imageFailed"
|
||||||
class="union-news-item__cover"
|
class="union-news-item__cover"
|
||||||
:src="getSpreadCover(item.thumbUrl)"
|
:src="item.thumbUrl" referrerpolicy="no-referrer"
|
||||||
|
@error="$set(item, 'imageFailed', true)"
|
||||||
:alt="item.title">
|
: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__content">
|
||||||
<div class="union-news-item__tags">
|
<div class="union-news-item__tags">
|
||||||
<span class="union-news-item__tag"
|
<span class="union-news-item__tag"
|
||||||
@@ -117,9 +135,10 @@ const home = {
|
|||||||
</div>
|
</div>
|
||||||
<div class="union-news-item__meta">
|
<div class="union-news-item__meta">
|
||||||
<span>{{ formatSpreadDate(item.publishedAt || item.createdAt) }}</span>
|
<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>
|
<van-icon name="eye-o"></van-icon>
|
||||||
{{ item.viewCount || 0 }}
|
{{ item.viewCount }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +151,7 @@ const home = {
|
|||||||
<span class="union-news-empty__text">暂无工会动态</span>
|
<span class="union-news-empty__text">暂无工会动态</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="classroom-section">
|
<div class="classroom-section">
|
||||||
<div class="classroom-section__head">
|
<div class="classroom-section__head">
|
||||||
@@ -247,6 +267,10 @@ const home = {
|
|||||||
classroomRecommendOptions: [],
|
classroomRecommendOptions: [],
|
||||||
showHomeSummary: false,
|
showHomeSummary: false,
|
||||||
homeScrollContainer: null,
|
homeScrollContainer: null,
|
||||||
|
spreadSections: [],
|
||||||
|
spreadMenuVisible: false,
|
||||||
|
spreadHistoryToken: null,
|
||||||
|
spreadActiveIndex: 0,
|
||||||
spreadDynamics: [],
|
spreadDynamics: [],
|
||||||
spreadLoading: true,
|
spreadLoading: true,
|
||||||
spreadLoadSeq: 0,
|
spreadLoadSeq: 0,
|
||||||
@@ -311,6 +335,10 @@ const home = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
spreadHasImages() {
|
||||||
|
// 按当前栏目统一保留图片列;异步补图及加载失败都会重新计算,全部无图时文字占满行宽。
|
||||||
|
return this.spreadDynamics.some(item => item.thumbUrl && !item.imageFailed)
|
||||||
|
},
|
||||||
currentBanner() {
|
currentBanner() {
|
||||||
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
|
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
|
||||||
}
|
}
|
||||||
@@ -516,18 +544,29 @@ const home = {
|
|||||||
listSpreadHomeContent() {
|
listSpreadHomeContent() {
|
||||||
const loadSeq = ++this.spreadLoadSeq
|
const loadSeq = ++this.spreadLoadSeq
|
||||||
this.spreadLoading = true
|
this.spreadLoading = true
|
||||||
this.$axios.post("/platform/h5/spread/pageData", {
|
this.$axios.post("/platform/home/getNews").then((res) => {
|
||||||
sortType: "latest",
|
|
||||||
pageNumber: 1,
|
|
||||||
pageSize: 10
|
|
||||||
}).then((res) => {
|
|
||||||
if (loadSeq === this.spreadLoadSeq && res.code === 0) {
|
if (loadSeq === this.spreadLoadSeq && res.code === 0) {
|
||||||
const data = res.data || {}
|
// 接口按栏目返回 href/time/text,移动端保存全部栏目供切换,并映射为现有动态卡片字段。
|
||||||
this.spreadDynamics = data.list || []
|
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(() => {
|
}).catch(() => {
|
||||||
if (loadSeq === this.spreadLoadSeq) {
|
if (loadSeq === this.spreadLoadSeq) {
|
||||||
this.spreadDynamics = []
|
this.$set(this, "spreadSections", [])
|
||||||
|
this.$set(this, "spreadDynamics", [])
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
if (loadSeq === this.spreadLoadSeq) {
|
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) {
|
getSpreadCategoryLabels(item) {
|
||||||
const labels = [item.parentCategoryName, item.childCategoryName].filter(Boolean)
|
const labels = [item.parentCategoryName, item.childCategoryName].filter(Boolean)
|
||||||
return labels.length ? labels : (item.categoryName ? [item.categoryName] : [])
|
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)) || ""
|
return first && (first.src || first.url || first.data || (first.response && first.response.data)) || ""
|
||||||
},
|
},
|
||||||
formatSpreadDate(value) {
|
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) {
|
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() {
|
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) {
|
menuClick(item) {
|
||||||
this.$pjaxReplace(item.href)
|
this.$pjaxReplace(item.href)
|
||||||
@@ -606,6 +701,10 @@ const home = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
// 注册弹层返回回调,关闭状态由历史回退同步,避免留下多余历史记录。
|
||||||
|
this.spreadHistoryToken = window.h5PopupHistory.register(() => {
|
||||||
|
this.$set(this, "spreadMenuVisible", false)
|
||||||
|
})
|
||||||
this.listHomeBrand()
|
this.listHomeBrand()
|
||||||
this.listRecommendApp()
|
this.listRecommendApp()
|
||||||
this.listClassroomCourses()
|
this.listClassroomCourses()
|
||||||
@@ -623,6 +722,8 @@ const home = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
const token = this.spreadHistoryToken
|
||||||
|
window.h5PopupHistory.clear(token).then(() => window.h5PopupHistory.unregister(token))
|
||||||
if (this.homeScrollContainer) {
|
if (this.homeScrollContainer) {
|
||||||
this.homeScrollContainer.removeEventListener("scroll", this.handleHomeScroll)
|
this.homeScrollContainer.removeEventListener("scroll", this.handleHomeScroll)
|
||||||
}
|
}
|
||||||
@@ -1383,7 +1484,8 @@ const home = {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-panel {
|
.home-page/deep/ .union-news-panel {
|
||||||
|
position: relative;
|
||||||
margin: 15px 12px 7px;
|
margin: 15px 12px 7px;
|
||||||
padding: 13px 12px 8px;
|
padding: 13px 12px 8px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -1391,21 +1493,29 @@ const home = {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-panel__head {
|
.home-page/deep/ .union-news-panel__head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 10px;
|
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;
|
color: #202124;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-panel__more {
|
.home-page/deep/ .union-news-panel__more {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
@@ -1413,32 +1523,68 @@ const home = {
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-panel__more .van-icon {
|
.home-page/deep/ .union-news-panel__more .van-icon {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-size: 13px;
|
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;
|
margin-top: 7px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
display: flex;
|
display: block;
|
||||||
gap: 10px;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-list::-webkit-scrollbar {
|
.home-page/deep/ .union-news-list::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item--skeleton {
|
.home-page/deep/ .union-news-item--skeleton {
|
||||||
height: 96px;
|
height: 96px;
|
||||||
min-height: 96px;
|
min-height: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-skeleton__cover {
|
.home-page/deep/ .union-news-skeleton__cover {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
@@ -1446,65 +1592,93 @@ const home = {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-skeleton__content {
|
.home-page/deep/ .union-news-skeleton__content {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-skeleton__tags { height: 15px; margin-bottom: 4px; display: flex; gap: 4px; }
|
.home-page/deep/ .union-news-skeleton__tags { height: 15px; margin-bottom: 4px; display: flex; gap: 4px; }
|
||||||
.union-news-skeleton__tag { width: 48px; height: 15px; border-radius: 999px; }
|
.home-page/deep/ .union-news-skeleton__tag { width: 48px; height: 15px; border-radius: 999px; }
|
||||||
.union-news-skeleton__tag--short { width: 38px; }
|
.home-page/deep/ .union-news-skeleton__tag--short { width: 38px; }
|
||||||
.union-news-skeleton__line { height: 10px; border-radius: 3px; }
|
.home-page/deep/ .union-news-skeleton__line { height: 10px; border-radius: 3px; }
|
||||||
.union-news-skeleton__line--title { width: 88%; }
|
.home-page/deep/ .union-news-skeleton__line--title { width: 88%; }
|
||||||
.union-news-skeleton__line--summary { width: 72%; margin-top: 7px; }
|
.home-page/deep/ .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; }
|
.home-page/deep/ .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; }
|
.home-page/deep/ .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__meta span:last-child { width: 28px; }
|
||||||
|
|
||||||
.union-news-item {
|
.home-page/deep/ .union-news-item {
|
||||||
width: calc(100% - 32px);
|
width: 100%;
|
||||||
min-width: calc(100% - 32px);
|
min-width: 0;
|
||||||
min-height: 96px;
|
height: 96px;
|
||||||
padding: 10px;
|
padding: 12px 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid #edf1f6;
|
border: 0;
|
||||||
border-radius: 9px;
|
border-radius: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item + .union-news-item {
|
.home-page/deep/ .union-news-item + .union-news-item {
|
||||||
border-top: 1px solid #edf1f6;
|
border-top: 1px solid #edf1f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item:active {
|
.home-page/deep/ .union-news-item:active {
|
||||||
background-color: #f7faff;
|
background-color: #f7faff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__cover {
|
.home-page/deep/ .union-news-item__cover {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 48px;
|
height: 60px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
object-fit: cover;
|
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;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__tags {
|
.home-page/deep/ .union-news-item__tags {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__tag {
|
.home-page/deep/ .union-news-item__tag {
|
||||||
max-width: 46%;
|
max-width: 46%;
|
||||||
padding: 1px 6px;
|
padding: 1px 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -1517,12 +1691,12 @@ const home = {
|
|||||||
background-color: #eaf4ff;
|
background-color: #eaf4ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__tag--secondary {
|
.home-page/deep/ .union-news-item__tag--secondary {
|
||||||
color: #627ea4;
|
color: #627ea4;
|
||||||
background-color: #f0f4f9;
|
background-color: #f0f4f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__title {
|
.home-page/deep/ .union-news-item__title {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #202124;
|
color: #202124;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -1532,7 +1706,7 @@ const home = {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__summary {
|
.home-page/deep/ .union-news-item__summary {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: #7d8797;
|
color: #7d8797;
|
||||||
@@ -1542,7 +1716,7 @@ const home = {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__meta {
|
.home-page/deep/ .union-news-item__meta {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1552,17 +1726,17 @@ const home = {
|
|||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__views {
|
.home-page/deep/ .union-news-item__views {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-item__views .van-icon {
|
.home-page/deep/ .union-news-item__views .van-icon {
|
||||||
margin-right: 3px;
|
margin-right: 3px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-empty {
|
.home-page/deep/ .union-news-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 12px 0 16px;
|
padding: 12px 0 16px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1573,7 +1747,7 @@ const home = {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-empty__image {
|
.home-page/deep/ .union-news-empty__image {
|
||||||
display: block;
|
display: block;
|
||||||
width: 82%;
|
width: 82%;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
@@ -1581,7 +1755,7 @@ const home = {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.union-news-empty__text {
|
.home-page/deep/ .union-news-empty__text {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user