feat: 同步长江大学的风采墙优化
This commit is contained in:
@@ -5,20 +5,20 @@ import org.nutz.lang.util.NutMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 首页工会网站新闻服务。
|
||||
* 首页风采墙新闻服务。
|
||||
*/
|
||||
public interface SysHomeNewsService {
|
||||
|
||||
/**
|
||||
* 获取学校工会网站首页新闻。
|
||||
* 获取风采墙可见栏目下的已发布新闻。
|
||||
*
|
||||
* @return 新闻栏目集合;每个栏目包含 label 栏目名称和 value 新闻列表,
|
||||
* 新闻项包含 href 跳转地址、time 发布时间、text 新闻标题和 thumbUrl 缩略图绝对地址(无图时为空)
|
||||
* 新闻项包含 href 跳转地址、time 发布时间、text 新闻标题和 thumbUrl 缩略图地址(无图时为空)
|
||||
*/
|
||||
List<NutMap> getWebsiteNews();
|
||||
|
||||
/**
|
||||
* href 必须是工会首页返回的新闻链接;返回正文首图的绝对地址,无图或不可访问时为空字符串。
|
||||
* href 必须是首页返回的风采墙新闻链接;返回封面或正文首图地址,无图时为空字符串。
|
||||
*/
|
||||
String getNewsImage(String href);
|
||||
}
|
||||
|
||||
@@ -1,159 +1,108 @@
|
||||
package com.budwk.app.sys.services.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.budwk.app.sys.services.SysHomeNewsService;
|
||||
import com.budwk.app.zhgh.spread.models.SpreadCategory;
|
||||
import com.budwk.app.zhgh.spread.models.SpreadContent;
|
||||
import com.budwk.app.zhgh.spread.service.SpreadCategoryService;
|
||||
import com.budwk.app.zhgh.spread.service.SpreadContentService;
|
||||
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.dao.Cnd;
|
||||
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 SpreadCategoryService categoryService;
|
||||
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
private SpreadContentService contentService;
|
||||
|
||||
/**
|
||||
* 抓取学校工会网站首页的工会要闻、基层动态等栏目。
|
||||
* 外部网站暂时不可用时优先返回最近一次缓存,避免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<SpreadCategory> categories = categoryService.listCategories(true);
|
||||
List<NutMap> sections = new ArrayList<>();
|
||||
for (Element homeElement : homeElements) {
|
||||
Element titleElement = homeElement.selectFirst(".title");
|
||||
if (titleElement == null || StrUtil.isBlank(titleElement.text())) {
|
||||
continue;
|
||||
for (SpreadCategory category : categories) {
|
||||
if (!"0".equals(category.getParentId())) continue;
|
||||
List<String> categoryIds = new ArrayList<>();
|
||||
categoryIds.add(category.getId());
|
||||
for (SpreadCategory child : categories) {
|
||||
if (category.getId().equals(child.getParentId())) categoryIds.add(child.getId());
|
||||
}
|
||||
List<SpreadContent> contents = contentService.query(Cnd.where("delFlag", "=", false)
|
||||
.and("isPublished", "=", 1).and("categoryId", "in", categoryIds)
|
||||
.desc("isTop").desc("publishedAt").asc("sortOrder").desc("createdAt"));
|
||||
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());
|
||||
for (SpreadContent content : contents) {
|
||||
Long publishedAt = content.getPublishedAt();
|
||||
rows.add(NutMap.NEW()
|
||||
.addv("href", linkElement.attr("href"))
|
||||
.addv("time", timeElement == null ? "" : timeElement.text())
|
||||
.addv("text", title)
|
||||
.addv("thumbUrl", thumbUrl));
|
||||
.addv("href", "/platform/spread/display?id=" + content.getId())
|
||||
.addv("time", publishedAt == null ? "" : DateUtil.format(new java.util.Date(publishedAt), "yyyy-MM-dd"))
|
||||
.addv("text", content.getTitle())
|
||||
.addv("thumbUrl", getContentImage(content)));
|
||||
}
|
||||
sections.add(NutMap.NEW().addv("label", titleElement.text()).addv("value", rows));
|
||||
sections.add(NutMap.NEW().addv("label", category.getName()).addv("value", rows));
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取最近一次成功抓取的数据;缓存为空或内容异常时返回空集合。
|
||||
*/
|
||||
private List<NutMap> getCachedNews() {
|
||||
String cached = redisService.get(NEWS_CACHE_KEY);
|
||||
if (StrUtil.isBlank(cached)) {
|
||||
return Collections.emptyList();
|
||||
private String getContentImage(SpreadContent content) {
|
||||
String image = "";
|
||||
if (StrUtil.isNotBlank(content.getThumbUrl())) {
|
||||
Object value = content.getThumbUrl().trim();
|
||||
if (value.toString().startsWith("[") || value.toString().startsWith("{")) {
|
||||
try {
|
||||
value = Json.fromJson(value.toString());
|
||||
} catch (RuntimeException e) {
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
if (value instanceof List) {
|
||||
List<?> files = (List<?>) value;
|
||||
value = files.isEmpty() ? "" : files.get(0);
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
Map<?, ?> file = (Map<?, ?>) value;
|
||||
value = file.get("src") != null ? file.get("src") : file.get("url");
|
||||
}
|
||||
image = value == null ? "" : value.toString().split(",", 2)[0].trim();
|
||||
}
|
||||
try {
|
||||
return Json.fromJsonAsList(NutMap.class, cached);
|
||||
} catch (RuntimeException e) {
|
||||
return Collections.emptyList();
|
||||
if (StrUtil.isBlank(image) && StrUtil.isNotBlank(content.getContentBody())) {
|
||||
Element first = Jsoup.parse(content.getContentBody()).selectFirst("img[src]");
|
||||
if (first != null) image = first.attr("src");
|
||||
}
|
||||
return image.startsWith("/") && !image.startsWith("//")
|
||||
|| image.startsWith("https://") || image.startsWith("http://") ? image : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅补取缓存新闻列表中已存在的公开文章,禁止任意地址抓取和自动重定向。
|
||||
* 正文首图独立缓存;无图短暂缓存,防止每次栏目切换重复访问外站。
|
||||
* 仅返回首页已发布风采内容的图片,不再请求外部新闻网站。
|
||||
*/
|
||||
@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 (StrUtil.isBlank(href)) return "";
|
||||
for (NutMap section : getWebsiteNews()) {
|
||||
for (Object value : (List<?>) section.get("value")) {
|
||||
NutMap news = (NutMap) value;
|
||||
if (href.equals(news.getString("href"))) return news.getString("thumbUrl");
|
||||
}
|
||||
}
|
||||
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 "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,15 @@ public class SpreadDisplayController {
|
||||
return Result.success(contentService.pagePublished(pageForm, categoryId));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("热门内容分页")
|
||||
@SaCheckPermission("spread.display")
|
||||
public Result hotData(PageForm pageForm) {
|
||||
pageForm.setPageNumber(pageForm.getPageNumber() == null ? 1 : Math.max(1, pageForm.getPageNumber()));
|
||||
pageForm.setPageSize(10);
|
||||
return Result.success(contentService.pagePublished(pageForm, null, null, "views", null, null));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("内容详情")
|
||||
@SaCheckPermission("spread.display")
|
||||
|
||||
@@ -97,6 +97,19 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
|
||||
query.setTitle(title);
|
||||
query.setIsPublished(1);
|
||||
Cnd cnd = buildCondition(query);
|
||||
List<SpreadCategory> visibleCategories = dao().query(SpreadCategory.class,
|
||||
Cnd.where("delFlag", "=", false).and("visible", "=", 1));
|
||||
List<String> visibleIds = new ArrayList<>();
|
||||
for (SpreadCategory category : visibleCategories) {
|
||||
if ("0".equals(category.getParentId()) || visibleCategories.stream()
|
||||
.anyMatch(parent -> parent.getId().equals(category.getParentId()) && "0".equals(parent.getParentId()))) {
|
||||
visibleIds.add(category.getId());
|
||||
}
|
||||
}
|
||||
if (visibleIds.isEmpty()) {
|
||||
return new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, Collections.emptyList());
|
||||
}
|
||||
cnd.and("c.categoryId", "in", visibleIds);
|
||||
if (publishDays != null && publishDays > 0) {
|
||||
cnd.and("c.publishedAt", ">=", System.currentTimeMillis() - publishDays * 24L * 60L * 60L * 1000L);
|
||||
}
|
||||
@@ -139,7 +152,7 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
|
||||
return "c.publishedAt DESC, c.createdAt DESC";
|
||||
}
|
||||
if ("views".equals(sortType)) {
|
||||
return "c.viewCount DESC, c.publishedAt DESC, c.createdAt DESC";
|
||||
return "c.viewCount DESC, c.publishedAt DESC, c.id DESC";
|
||||
}
|
||||
return "c.isTop DESC, c.publishedAt DESC, c.sortOrder ASC, c.createdAt DESC";
|
||||
}
|
||||
@@ -222,6 +235,8 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
|
||||
LEFT JOIN spread_category parent ON parent.id = child.parentId
|
||||
WHERE c.delFlag = 0
|
||||
AND c.isPublished = 1
|
||||
AND c.thumbUrl IS NOT NULL
|
||||
AND TRIM(c.thumbUrl) <> ''
|
||||
AND child.delFlag = 0
|
||||
AND child.visible = 1
|
||||
AND child.slider = 1
|
||||
@@ -273,7 +288,7 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并规范每张轮播图的标题,轮播图统一继承内容所属工会。
|
||||
* 校验每张轮播图的标题和所属工会,兼容未单独设置工会的历史图片。
|
||||
*/
|
||||
private void validateSliderImages(SpreadContent content) {
|
||||
if (StrUtil.isBlank(content.getSliderImages())) {
|
||||
@@ -301,6 +316,10 @@ public class SpreadContentServiceImpl extends BaseServiceImpl<SpreadContent> imp
|
||||
JSONObject normalizedItem = new JSONObject();
|
||||
normalizedItem.set("src", item.getStr("src"));
|
||||
normalizedItem.set("title", item.getStr("title").trim());
|
||||
Sys_union imageUnion = requireSelectableUnion(StrUtil.isBlank(item.getStr("unionId"))
|
||||
? content.getUnionId() : item.getStr("unionId"));
|
||||
normalizedItem.set("unionId", imageUnion.getId());
|
||||
normalizedItem.set("unionName", imageUnion.getName());
|
||||
normalizedArray.add(normalizedItem);
|
||||
}
|
||||
content.setSliderImages(JSONUtil.toJsonStr(normalizedArray));
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
contextPath: ""
|
||||
staticPathLocal: ''
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
host: 175.27.165.74
|
||||
port: 6379
|
||||
database: 1
|
||||
database: 4
|
||||
mode: normal
|
||||
password: ddss@2025
|
||||
jdbc:
|
||||
url: jdbc:mysql://127.0.0.1:10086/zhgh_zjnu?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 714 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 249 KiB |
@@ -0,0 +1,152 @@
|
||||
|
||||
|
||||
Vue.component("spread-back-button", {
|
||||
template: `<div
|
||||
class="float-btn"
|
||||
:class="{ 'is-dragging': dragging }"
|
||||
:style="{ left: x + 'px', top: y + 'px' }"
|
||||
@mousedown="startDrag"
|
||||
@touchstart="startDrag"
|
||||
@click="handleClick"
|
||||
>
|
||||
<!-- 外发光 -->
|
||||
<div class="glow-ring"></div>
|
||||
|
||||
<!-- 点击涟漪 -->
|
||||
<span
|
||||
v-for="r in ripples"
|
||||
:key="r.id"
|
||||
class="ripple"
|
||||
:style="{ left: r.x + 'px', top: r.y + 'px' }"
|
||||
></span>
|
||||
|
||||
<!-- 图标 -->
|
||||
<div class="icon-wrap">
|
||||
<van-icon name="share-o" size="28" class="flip-icon" ></van-icon>
|
||||
</div>
|
||||
</div>`,
|
||||
props: {
|
||||
path: {
|
||||
type: String,
|
||||
default: '/platform/h5/home'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
x: 20,
|
||||
y: 200,
|
||||
dragging: false,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
moved: false,
|
||||
|
||||
ripples: [],
|
||||
rippleId: 0
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const pos = localStorage.getItem("float-btn-pos")
|
||||
if (pos) {
|
||||
const { x, y } = JSON.parse(pos)
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
|
||||
window.addEventListener("mousemove", this.onDrag)
|
||||
window.addEventListener("mouseup", this.endDrag)
|
||||
|
||||
window.addEventListener("touchmove", this.onDrag, { passive: false })
|
||||
window.addEventListener("touchend", this.endDrag)
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("mousemove", this.onDrag)
|
||||
window.removeEventListener("mouseup", this.endDrag)
|
||||
|
||||
window.removeEventListener("touchmove", this.onDrag)
|
||||
window.removeEventListener("touchend", this.endDrag)
|
||||
},
|
||||
|
||||
methods: {
|
||||
startDrag(e) {
|
||||
const point = e.touches ? e.touches[0] : e
|
||||
|
||||
this.dragging = true
|
||||
this.moved = false
|
||||
|
||||
this.startX = point.clientX
|
||||
this.startY = point.clientY
|
||||
|
||||
this.offsetX = point.clientX - this.x
|
||||
this.offsetY = point.clientY - this.y
|
||||
},
|
||||
|
||||
onDrag(e) {
|
||||
if (!this.dragging) return
|
||||
|
||||
if (e.cancelable) e.preventDefault()
|
||||
|
||||
const point = e.touches ? e.touches[0] : e
|
||||
|
||||
let newX = point.clientX - this.offsetX
|
||||
let newY = point.clientY - this.offsetY
|
||||
|
||||
const btnSize = 54
|
||||
const maxX = window.innerWidth - btnSize
|
||||
const maxY = window.innerHeight - btnSize
|
||||
|
||||
this.x = Math.max(0, Math.min(newX, maxX))
|
||||
this.y = Math.max(0, Math.min(newY, maxY))
|
||||
|
||||
const dx = Math.abs(point.clientX - this.startX)
|
||||
const dy = Math.abs(point.clientY - this.startY)
|
||||
|
||||
if (dx > 5 || dy > 5) {
|
||||
this.moved = true
|
||||
}
|
||||
},
|
||||
|
||||
endDrag() {
|
||||
if (!this.dragging) return
|
||||
|
||||
this.dragging = false
|
||||
|
||||
localStorage.setItem(
|
||||
"float-btn-pos",
|
||||
JSON.stringify({
|
||||
x: this.x,
|
||||
y: this.y
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
handleClick(e) {
|
||||
|
||||
if (this.moved) return
|
||||
|
||||
const point = e.changedTouches ? e.changedTouches[0] : e
|
||||
|
||||
const rect = this.$el.getBoundingClientRect()
|
||||
const id = this.rippleId++
|
||||
|
||||
this.ripples.push({
|
||||
id,
|
||||
x: point.clientX - rect.left,
|
||||
y: point.clientY - rect.top
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.ripples = this.ripples.filter(r => r.id !== id)
|
||||
}, 600)
|
||||
if (this.path === '-1') {
|
||||
window.history.back()
|
||||
} else {
|
||||
this.$pjaxReplace(this.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -60,7 +60,7 @@ const jcdt = {
|
||||
this.pageNumber = 1
|
||||
},
|
||||
onLink(item) {
|
||||
const url = this.isHttpOrHttps(item.href) ? item.href : "https://xgh.cug.edu.cn/" + item.href
|
||||
const url = item.href
|
||||
window.open(url)
|
||||
},
|
||||
isHttpOrHttps(url) {
|
||||
|
||||
@@ -80,7 +80,7 @@ layout("/layouts/platform.html"){
|
||||
upload_mode="image" upload_result_category="interval"></file-upload>
|
||||
<div class="form-tip">建议使用横版图片,单张不超过 10MB。</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="sliderEnabled" label="轮播图片">
|
||||
<el-form-item label="详情轮播图">
|
||||
<div class="slider-upload-head">
|
||||
<span class="slider-upload-title">点击下方“+”新增轮播图片</span>
|
||||
<span class="slider-upload-count">已添加 {{sliderMeta.length}} / 10 张</span>
|
||||
@@ -88,7 +88,7 @@ layout("/layouts/platform.html"){
|
||||
<file-upload :upload_number="10" :upload_size="10 * 1024 * 1024"
|
||||
:value.sync="sliderFiles" accept=".jpg,.jpeg,.png,.webp"
|
||||
upload_mode="image" upload_result_category="array"></file-upload>
|
||||
<div class="form-tip">仅栏目开启“参与轮播”后生效,最多上传 10 张;每张图片只需填写标题,工会名称沿用内容所属工会。</div>
|
||||
<div class="form-tip">用于内容详情页轮播,最多上传 10 张;请填写每张图片的标题和所属工会。首页轮播使用内容封面图。</div>
|
||||
<div v-if="sliderMeta.length" class="slider-meta-list">
|
||||
<div class="slider-meta-toolbar">
|
||||
<span class="form-tip">可调整顺序,轮播将按当前顺序展示。</span>
|
||||
@@ -105,6 +105,10 @@ layout("/layouts/platform.html"){
|
||||
<span class="slider-field-label"><i class="color-danger">*</i> 图片标题</span>
|
||||
<el-input v-model.trim="img.title" maxlength="100" show-word-limit
|
||||
:placeholder="'请输入第' + (index + 1) + '张图片标题'"></el-input>
|
||||
<span class="slider-field-label" style="margin-top: 10px"><i class="color-danger">*</i> 所属工会</span>
|
||||
<el-select v-model="img.unionId" filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="slider-item-actions">
|
||||
@@ -160,23 +164,6 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 仅所选末级栏目及其上级栏目都开启轮播时,显示轮播图片配置。
|
||||
sliderEnabled() {
|
||||
if (!this.formData.categoryId) return false
|
||||
const findPath = (nodes, categoryId, parents) => {
|
||||
for (const node of nodes || []) {
|
||||
const path = parents.concat(node)
|
||||
if (node.id === categoryId) return path
|
||||
const childPath = findPath(node.children, categoryId, path)
|
||||
if (childPath) return childPath
|
||||
}
|
||||
return null
|
||||
}
|
||||
const categoryPath = findPath(this.categoryOptions, this.formData.categoryId, [])
|
||||
return !!categoryPath && categoryPath.every(item => Number(item.slider) === 1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 生成新增内容时使用的默认表单。
|
||||
defaultForm() {
|
||||
@@ -214,6 +201,9 @@ layout("/layouts/platform.html"){
|
||||
contentUnionChanged(unionId) {
|
||||
const union = this.unionOptions.find(item => item.id === unionId)
|
||||
this.formData.unionName = union ? union.name : ""
|
||||
this.sliderMeta.forEach(item => {
|
||||
if (!item.unionId) item.unionId = unionId
|
||||
})
|
||||
},
|
||||
|
||||
// 分工会人员只有一个可选工会时自动带入,校工会管理员仍需主动选择。
|
||||
@@ -240,7 +230,7 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
}).finally(() => this.loading = false)
|
||||
},
|
||||
// 兼容历史图片地址数组,并转换为只包含图片地址和标题的轮播对象。
|
||||
// 兼容历史图片地址数组,未设置工会的图片默认使用内容所属工会。
|
||||
parseSliderImages(value) {
|
||||
if (!value) return []
|
||||
let items = value
|
||||
@@ -254,11 +244,12 @@ layout("/layouts/platform.html"){
|
||||
if (!Array.isArray(items)) return []
|
||||
return items.map(item => {
|
||||
if (typeof item === "string") {
|
||||
return {src: item, title: ""}
|
||||
return {src: item, title: "", unionId: this.formData.unionId || ""}
|
||||
}
|
||||
return {
|
||||
src: item.src || item.url || item.data || (item.response && item.response.data) || "",
|
||||
title: item.title || ""
|
||||
title: item.title || "",
|
||||
unionId: item.unionId || this.formData.unionId || ""
|
||||
}
|
||||
}).filter(item => item.src)
|
||||
},
|
||||
@@ -272,7 +263,7 @@ layout("/layouts/platform.html"){
|
||||
const oldItems = {}
|
||||
this.sliderMeta.forEach(item => oldItems[item.src] = item)
|
||||
this.sliderMeta = (this.sliderFiles || []).map(item => this.getSliderFileUrl(item)).filter(Boolean).map(src => {
|
||||
return oldItems[src] || {src: src, title: ""}
|
||||
return oldItems[src] || {src: src, title: "", unionId: this.formData.unionId || ""}
|
||||
})
|
||||
this.applyUnionDefaults()
|
||||
},
|
||||
@@ -301,20 +292,21 @@ layout("/layouts/platform.html"){
|
||||
this.$message.success("轮播图片已清空")
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 将轮播图片地址和标题转换为可持久化 JSON。
|
||||
// 将轮播图片地址、标题和所属工会转换为可持久化 JSON。
|
||||
buildSliderImages() {
|
||||
this.syncSliderMeta()
|
||||
return JSON.stringify(this.sliderMeta.map(item => ({
|
||||
src: item.src,
|
||||
title: item.title
|
||||
title: item.title,
|
||||
unionId: item.unionId
|
||||
})))
|
||||
},
|
||||
// 校验每张轮播图的标题。
|
||||
// 校验每张轮播图的标题和所属工会。
|
||||
validateSliderMeta() {
|
||||
this.syncSliderMeta()
|
||||
const invalidIndex = this.sliderMeta.findIndex(item => !item.title)
|
||||
const invalidIndex = this.sliderMeta.findIndex(item => !item.title || !item.unionId)
|
||||
if (invalidIndex < 0) return true
|
||||
this.$message.warning("请输入第" + (invalidIndex + 1) + "张轮播图标题")
|
||||
this.$message.warning("请完善第" + (invalidIndex + 1) + "张轮播图的标题和所属工会")
|
||||
return false
|
||||
},
|
||||
// 重置新增表单;编辑状态下恢复数据库内容。
|
||||
@@ -334,14 +326,9 @@ layout("/layouts/platform.html"){
|
||||
submit() {
|
||||
this.$refs.contentForm.validate(valid => {
|
||||
if (!valid) return
|
||||
if (this.sliderEnabled && !this.validateSliderMeta()) return
|
||||
if (!this.validateSliderMeta()) return
|
||||
const data = Object.assign({}, this.formData)
|
||||
if (this.sliderEnabled) {
|
||||
data.sliderImages = this.buildSliderImages()
|
||||
} else {
|
||||
// 未开启轮播的栏目不提交隐藏字段,编辑时保留已有数据,避免切换栏目造成误删。
|
||||
delete data.sliderImages
|
||||
}
|
||||
data.sliderImages = this.buildSliderImages()
|
||||
// 推荐和头条没有对应业务逻辑,提交时不再携带这两个字段。
|
||||
delete data.isRecommend
|
||||
delete data.isHeadline
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,60 +2,252 @@
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body { background: #f5f7fa; }
|
||||
.spread-detail-h5 { min-height: 100vh; color: #202124; background: #f5f7fa; }
|
||||
.spread-h5__detail { margin: 10px 12px 24px; padding: 18px 15px 24px; border-radius: 10px; background: #fff; }
|
||||
.spread-h5__detail-title { margin: 0; color: #202124; font-size: 20px; line-height: 29px; text-align: center; word-break: break-word; }
|
||||
.spread-h5__detail-meta { margin-top: 10px; padding-bottom: 14px; color: #9aa2af; font-size: 11px; line-height: 17px; text-align: center; border-bottom: 1px solid #edf1f6; }
|
||||
.spread-h5__detail-summary { margin: 15px 0 0; padding: 10px 12px; color: #697386; font-size: 14px; line-height: 22px; border-radius: 6px; background: #f7f9fc; }
|
||||
.spread-h5__detail-cover { width: 100%; max-height: 240px; margin: 16px 0 0; display: block; border-radius: 6px; object-fit: contain; }
|
||||
.spread-h5__slider { height: 220px; margin-top: 16px; overflow: hidden; border-radius: 6px; background: #eef2f7; }
|
||||
.spread-h5__slide { position: relative; height: 220px; }
|
||||
.spread-h5__slide img { width: 100%; height: 100%; display: block; object-fit: contain; }
|
||||
.spread-h5__slide-caption { position: absolute; left: 0; right: 0; bottom: 0; padding: 24px 12px 10px; overflow: hidden; color: #fff; font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; background: linear-gradient(to top, rgba(15,23,42,.72), transparent); box-sizing: border-box; }
|
||||
.spread-h5__detail-body { margin-top: 16px; overflow: hidden; color: #323233; font-size: 15px; line-height: 1.8; word-break: break-word; }
|
||||
.spread-h5__detail-body img, .spread-h5__detail-body video { max-width: 100% !important; height: auto !important; }
|
||||
.spread-h5__detail-body table { max-width: 100% !important; }
|
||||
.spread-h5__detail--skeleton .van-skeleton { padding: 0; }
|
||||
.spread-h5__detail-skeleton-cover { margin-top: 18px; }
|
||||
.spread-h5__detail-skeleton-cover .van-skeleton__row { height: 180px; border-radius: 6px; }
|
||||
.spread-h5__detail-skeleton-body { margin-top: 18px; }
|
||||
.spread-h5__detail-empty { padding: 80px 20px; color: #9aa2af; font-size: 14px; text-align: center; }
|
||||
#app.spread-mobile { height: auto; min-height: 100%; }
|
||||
.spread-mobile .dynamic-detail-page {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #f4f7fb;
|
||||
}
|
||||
.spread-mobile .page-loading {
|
||||
padding-top: 140px;
|
||||
}
|
||||
.spread-mobile .article-card {
|
||||
min-height: calc(100vh - 46px);
|
||||
padding: 22px 18px 30px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 24px rgba(29, 75, 129, 0.08);
|
||||
}
|
||||
.spread-mobile .article-title {
|
||||
margin: 0;
|
||||
color: #17233d;
|
||||
font-size: 22px;
|
||||
line-height: 32px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.spread-mobile .article-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
margin-top: 12px;
|
||||
color: #8a96a8;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.spread-mobile .article-thumbnail,
|
||||
.spread-mobile .article-carousel {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #eef3f8;
|
||||
}
|
||||
.spread-mobile .article-thumbnail {
|
||||
height: 220px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.spread-mobile .article-carousel {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
margin: 12px 0 0;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
.spread-mobile .article-carousel .van-swipe__track,
|
||||
.spread-mobile .article-carousel .van-swipe-item {
|
||||
height: 100%;
|
||||
}
|
||||
.spread-mobile .carousel-slide {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.spread-mobile .article-cover {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
flex: none;
|
||||
background: transparent;
|
||||
}
|
||||
.spread-mobile .carousel-title {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 38px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.spread-mobile .carousel-title > span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.spread-mobile .carousel-count {
|
||||
margin-left: 12px;
|
||||
color: #fff;
|
||||
flex: none;
|
||||
}
|
||||
.spread-mobile .image-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9aa8b8;
|
||||
background: #eef3f8;
|
||||
font-size: 13px;
|
||||
}
|
||||
.spread-mobile .article-summary {
|
||||
margin-top: 18px;
|
||||
padding: 12px 14px;
|
||||
border-left: 3px solid #1677ff;
|
||||
color: #607086;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background: #f4f8ff;
|
||||
}
|
||||
.spread-mobile .article-content {
|
||||
margin-top: 20px;
|
||||
overflow: hidden;
|
||||
color: #39475a;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
word-break: break-word;
|
||||
}
|
||||
.spread-mobile .article-content img,
|
||||
.spread-mobile .article-content video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.spread-mobile .article-content table {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.spread-mobile .article-content p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.spread-mobile .skeleton-block {
|
||||
display: block;
|
||||
background: linear-gradient(90deg, #edf1f5 25%, #f7f9fb 37%, #edf1f5 63%);
|
||||
background-size: 400% 100%;
|
||||
animation: detailSkeleton 1.4s ease infinite;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-title {
|
||||
width: 78%;
|
||||
height: 28px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-meta .skeleton-block {
|
||||
width: 72px;
|
||||
height: 13px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-image {
|
||||
width: 100%;
|
||||
height: 220px;
|
||||
margin-top: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-summary {
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
margin-top: 18px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-content {
|
||||
margin-top: 22px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-content .skeleton-block {
|
||||
width: 100%;
|
||||
height: 14px;
|
||||
margin-top: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-content .skeleton-block:nth-child(2n) {
|
||||
width: 88%;
|
||||
}
|
||||
.spread-mobile .detail-skeleton-content .skeleton-block:last-child {
|
||||
width: 62%;
|
||||
}
|
||||
@keyframes detailSkeleton {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app" class="spread-detail-h5" v-cloak>
|
||||
<div id="app" class="spread-mobile" v-cloak>
|
||||
<van-nav-bar title="动态详情" left-arrow left-text="返回" fixed placeholder :z-index="20" @click-left="handleBack"></van-nav-bar>
|
||||
|
||||
<article v-if="detailLoading" class="spread-h5__detail spread-h5__detail--skeleton">
|
||||
<van-skeleton title :row="2"></van-skeleton>
|
||||
<van-skeleton class="spread-h5__detail-skeleton-cover" :row="1"></van-skeleton>
|
||||
<van-skeleton class="spread-h5__detail-skeleton-body" title :row="6"></van-skeleton>
|
||||
<div class="dynamic-detail-page">
|
||||
<div v-if="detailLoading" class="article-card detail-skeleton">
|
||||
<span class="skeleton-block detail-skeleton-title"></span>
|
||||
<div class="detail-skeleton-meta">
|
||||
<span class="skeleton-block"></span>
|
||||
<span class="skeleton-block"></span>
|
||||
<span class="skeleton-block"></span>
|
||||
</div>
|
||||
<span class="skeleton-block detail-skeleton-summary"></span>
|
||||
<span class="skeleton-block detail-skeleton-image"></span>
|
||||
<div class="detail-skeleton-content">
|
||||
<span v-for="index in 5" :key="'detail-skeleton-line-' + index" class="skeleton-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
<article v-else-if="detail && detail.id" class="article-card">
|
||||
<h1 class="article-title">{{ detail.title }}</h1>
|
||||
<div class="article-meta">
|
||||
<span>{{ detail.categoryName || '工会动态' }}</span>
|
||||
<span>{{ formatDate(detail.publishedAt || detail.createdAt) }}</span>
|
||||
<span><van-icon name="eye-o" ></van-icon> {{ detail.viewCount || 0 }}</span>
|
||||
</div>
|
||||
<div v-if="detail.summary" class="article-summary">{{ detail.summary }}</div>
|
||||
<van-swipe
|
||||
v-if="detailSlides.length"
|
||||
class="article-carousel"
|
||||
:autoplay="detailSlides.length > 1 ? 3500 : 0"
|
||||
:show-indicators="false"
|
||||
>
|
||||
<van-swipe-item v-for="(image, index) in detailSlides" :key="image.src + '-' + index">
|
||||
<div class="carousel-slide">
|
||||
<van-image class="article-cover" fit="cover" :src="image.src">
|
||||
<template #loading>
|
||||
<van-loading type="spinner" size="24" ></van-loading>
|
||||
</template>
|
||||
<template #error>
|
||||
<div class="image-empty">暂无图片</div>
|
||||
</template>
|
||||
</van-image>
|
||||
<div class="carousel-title">
|
||||
<span>{{ image.title }}</span>
|
||||
<span class="carousel-count">{{ index + 1 }}/{{ detailSlides.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div v-if="detail.contentBody" class="article-content" v-html="detail.contentBody"></div>
|
||||
<van-empty v-else description="暂无正文内容" ></van-empty>
|
||||
</article>
|
||||
|
||||
<article v-else-if="detail" class="spread-h5__detail">
|
||||
<h1 class="spread-h5__detail-title">{{detail.title}}</h1>
|
||||
<div class="spread-h5__detail-meta">
|
||||
{{formatDate(detail.publishedAt || detail.createdAt)}} {{detail.categoryName}}
|
||||
<van-icon name="eye-o"></van-icon> {{detail.viewCount || 0}}
|
||||
</div>
|
||||
<img v-if="getCoverUrl(detail.thumbUrl)" class="spread-h5__detail-cover" :src="getCoverUrl(detail.thumbUrl)" :alt="detail.title">
|
||||
<div v-if="detail.summary" class="spread-h5__detail-summary">{{detail.summary}}</div>
|
||||
<van-swipe v-if="detailSlides.length"
|
||||
class="spread-h5__slider"
|
||||
:autoplay="3000"
|
||||
:show-indicators="detailSlides.length > 1"
|
||||
indicator-color="#1989fa">
|
||||
<van-swipe-item v-for="(slide, index) in detailSlides" :key="slide.src + '-' + index">
|
||||
<div class="spread-h5__slide">
|
||||
<img :src="slide.src" :alt="slide.title || detail.title">
|
||||
<div v-if="slide.title" class="spread-h5__slide-caption">{{slide.title}}</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div class="spread-h5__detail-body" v-html="detail.contentBody"></div>
|
||||
</article>
|
||||
|
||||
<div v-else class="spread-h5__detail-empty">动态不存在或已下架</div>
|
||||
<van-empty v-else description="动态信息不存在或未发布" ></van-empty>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -94,8 +286,8 @@ layout("/layouts/platform_h5.html"){
|
||||
})
|
||||
},
|
||||
handleBack() {
|
||||
const source = new URLSearchParams(window.location.search).get("from")
|
||||
this.$pjaxReplace(source === "home" ? "/platform/h5/home" : "/platform/h5/spread")
|
||||
const categoryId = this.detail && this.detail.categoryId
|
||||
this.$pjaxReplace("/platform/h5/spread" + (categoryId ? "?categoryId=" + encodeURIComponent(categoryId) : ""))
|
||||
},
|
||||
getCoverUrl(value) {
|
||||
if (!value) return ""
|
||||
@@ -127,12 +319,13 @@ layout("/layouts/platform_h5.html"){
|
||||
if (typeof item === "string") return {src: item.trim(), title: ""}
|
||||
return {
|
||||
src: item.src || item.url || item.data || (item.response && item.response.data) || "",
|
||||
title: item.title || item.description || ""
|
||||
title: item.title || item.description || "",
|
||||
unionName: item.unionName || ""
|
||||
}
|
||||
}).filter(item => item.src)
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD") : ""
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD HH:mm") : ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -2,125 +2,458 @@
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
<style>
|
||||
body { background: #f5f7fa; }
|
||||
.spread-h5 { min-height: 100vh; color: #202124; background: #f5f7fa; }
|
||||
.spread-h5__toolbar { position: sticky; top: 46px; z-index: 15; background: #fff; box-shadow: 0 3px 10px rgba(31,72,122,.06); }
|
||||
.spread-h5__search { padding: 8px 12px 2px; background: #fff; }
|
||||
.spread-h5__search .van-search { padding: 0; }
|
||||
.spread-h5__categories { padding: 2px 12px 8px; background: #fff; }
|
||||
.spread-h5__tabs-nav { display: flex; align-items: center; border-bottom: 1px solid #edf1f6; }
|
||||
.spread-h5__tabs { height: 36px; padding: 0; display: flex; gap: 0; overflow-x: auto; overflow-y: hidden; border-bottom: 1px solid #edf1f6; border-radius: 0; background: transparent; box-sizing: border-box; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
||||
.spread-h5__tabs-nav .spread-h5__tabs { min-width: 0; flex: 1; border-bottom: 0; }
|
||||
.spread-h5__tabs-arrow { width: 26px; height: 30px; padding: 0; flex-shrink: 0; display: flex; align-items: center; justify-content: center; color: #8d98a8; font-size: 14px; line-height: 1; border: 0; background: transparent; }
|
||||
.spread-h5__tabs-arrow .van-icon { display: block; line-height: 1; }
|
||||
.spread-h5__tabs-arrow:active { color: #087af4; background: #f3f8ff; }
|
||||
.spread-h5__tabs::-webkit-scrollbar { display: none; }
|
||||
.spread-h5__tab { position: relative; min-width: 92px; height: 30px; padding: 0 8px; flex: 1 0 calc((100% - 8px) / 3); display: flex; align-items: center; justify-content: center; color: #7d8797; font-size: 13px; white-space: nowrap; border-radius: 6px; box-sizing: border-box; }
|
||||
.spread-h5__tab--active { color: #087af4; font-weight: 600; background: transparent; box-shadow: none; }
|
||||
.spread-h5__tabs:not(.spread-h5__tabs--secondary) .spread-h5__tab--active::after { content: ""; position: absolute; left: 50%; bottom: -1px; width: 26px; height: 2px; border-radius: 2px; background: #087af4; transform: translateX(-50%); }
|
||||
.spread-h5__filters { height: 42px; margin-top: 8px; display: flex; overflow: hidden; border-radius: 10px; box-sizing: border-box; }
|
||||
.spread-h5__filter-menu { min-width: 0; flex: 1; background: transparent; }
|
||||
.spread-h5__filter-menu .van-dropdown-menu__bar { height: 40px; background: transparent; box-shadow: none; }
|
||||
.spread-h5__filter-menu .van-dropdown-menu__item { position: relative; min-width: 0; }
|
||||
.spread-h5__filter-menu .van-dropdown-menu__title { max-width: 100%; padding: 0 16px 0 8px; color: #5f6978; font-size: 12px; line-height: 28px; box-sizing: border-box; }
|
||||
.spread-h5__filter-menu .van-dropdown-menu__title::after { right: 6px; border-color: transparent transparent #8d98a8 #8d98a8; }
|
||||
.spread-h5__filter-menu .van-dropdown-menu__title--active::after { border-color: transparent transparent currentColor currentColor; }
|
||||
.spread-h5__list { margin: 10px 12px 20px; padding: 0 12px; border-radius: 10px; background: #fff; }
|
||||
.spread-h5__item { min-height: 82px; padding: 11px 0; display: flex; align-items: center; box-sizing: border-box; }
|
||||
.spread-h5__item + .spread-h5__item { border-top: 1px solid #f0f2f5; }
|
||||
.spread-h5__cover { width: 96px; height: 60px; margin-right: 11px; flex-shrink: 0; border-radius: 5px; object-fit: cover; }
|
||||
.spread-h5__item-content { min-width: 0; flex: 1; }
|
||||
.spread-h5__item-title { overflow: hidden; color: #202124; font-size: 14px; line-height: 20px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.spread-h5__item-summary { margin-top: 4px; overflow: hidden; color: #7d8797; font-size: 11px; line-height: 16px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.spread-h5__item-meta { margin-top: 6px; display: flex; align-items: center; justify-content: space-between; color: #9aa2af; font-size: 10px; line-height: 14px; }
|
||||
.spread-h5__views { display: inline-flex; align-items: center; }
|
||||
.spread-h5__views .van-icon { margin-right: 3px; }
|
||||
.spread-h5__skeleton-list { margin: 10px 12px 20px; padding: 0 12px; border-radius: 10px; background: #fff; }
|
||||
.spread-h5__skeleton-item { min-height: 82px; padding: 11px 0; box-sizing: border-box; }
|
||||
.spread-h5__skeleton-item + .spread-h5__skeleton-item { border-top: 1px solid #f0f2f5; }
|
||||
.spread-h5__skeleton-item .van-skeleton { padding: 0; }
|
||||
.spread-h5__skeleton-item .van-skeleton__avatar { width: 96px; height: 60px; margin-right: 11px; border-radius: 5px; }
|
||||
.spread-h5__loading { padding: 35px 0; text-align: center; }
|
||||
.spread-h5__finished { padding: 10px 0 18px; color: #b0b6c0; font-size: 11px; line-height: 16px; text-align: center; }
|
||||
.spread-h5__empty { min-height: 250px; padding: 28px 18px 36px; display: flex; align-items: center; justify-content: center; flex-direction: column; box-sizing: border-box; text-align: center; }
|
||||
.spread-h5__empty-image { display: block; width: 82%; max-width: 280px; height: auto; object-fit: contain; }
|
||||
.spread-h5__empty-title { margin-top: 8px; color: #50627a; font-size: 15px; line-height: 22px; font-weight: 500; }
|
||||
.spread-h5__empty-hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
|
||||
#app.spread-mobile { height: 100%; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.spread-mobile .van-nav-bar__placeholder { flex-shrink: 0; }
|
||||
.spread-mobile .dynamic-page {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
color: #17233d;
|
||||
background: #f4f7fc;
|
||||
}
|
||||
.spread-mobile .spread-news-header {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
flex: none;
|
||||
margin: 0 -16px;
|
||||
background: #f4f7fc;
|
||||
}
|
||||
.spread-mobile .spread-news-header::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
padding-top: 46.258%;
|
||||
background: url('/assets/mobile/img/home/v4/spread-news-header.png') center top / 100% auto no-repeat;
|
||||
-webkit-mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
|
||||
mask-image: linear-gradient(to bottom, #000 65%, transparent 100%);
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
}
|
||||
.spread-mobile .page-heading {
|
||||
min-height: 104px;
|
||||
padding: 24px 20px 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.spread-mobile .spread-news-title {
|
||||
font-size: 28px;
|
||||
line-height: 38px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.spread-mobile .page-subtitle {
|
||||
margin-top: 10px;
|
||||
color: #8390a3;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.spread-mobile .page-loading {
|
||||
padding-top: 120px;
|
||||
}
|
||||
.spread-mobile .dynamic-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin-left: -16px;
|
||||
margin-right: -16px;
|
||||
gap: 0;
|
||||
}
|
||||
.spread-mobile .category-sidebar,
|
||||
.spread-mobile .category-skeleton {
|
||||
width: 100%;
|
||||
max-height: 35vh;
|
||||
flex: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.spread-mobile .category-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 6px 16px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.spread-mobile .category-row::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.spread-mobile .category-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: none;
|
||||
max-width: 200px;
|
||||
min-height: 32px;
|
||||
padding: 6px 12px;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
color: #657289;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
.spread-mobile .category-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.spread-mobile .category-item.active {
|
||||
color: #1677ff;
|
||||
background: #e1edff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.spread-mobile .category-row-root .category-item {
|
||||
padding: 8px 4px 12px;
|
||||
margin-right: 12px;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.spread-mobile .category-row-root .category-item.active::after {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: calc(50% - 10px);
|
||||
width: 20px;
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
background: #1677ff;
|
||||
content: '';
|
||||
}
|
||||
.spread-mobile .category-skeleton {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.spread-mobile .category-skeleton-item {
|
||||
width: 64px;
|
||||
flex: none;
|
||||
height: 32px;
|
||||
margin: 7px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.spread-mobile .dynamic-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
padding: 0 0 28px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
background: #f4f7fc;
|
||||
}
|
||||
.spread-mobile .dynamic-empty-state {
|
||||
padding: 32px 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.spread-mobile .dynamic-empty-page {
|
||||
flex: 1;
|
||||
padding-top: 52px;
|
||||
}
|
||||
.spread-mobile .dynamic-empty-illustration {
|
||||
display: block;
|
||||
width: 220px;
|
||||
max-width: 100%;
|
||||
height: 150px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.spread-mobile .dynamic-empty-title {
|
||||
margin-top: 8px;
|
||||
color: #17233d;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.spread-mobile .dynamic-empty-desc {
|
||||
margin-top: 3px;
|
||||
color: #8a96a8;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.spread-mobile .dynamic-group + .dynamic-group {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.spread-mobile .group-title {
|
||||
margin-bottom: 8px;
|
||||
padding-left: 9px;
|
||||
border-left: 3px solid #1677ff;
|
||||
color: #53637a;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.spread-mobile .dynamic-list {
|
||||
overflow: hidden;
|
||||
padding: 12px 12px 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.spread-mobile .dynamic-item {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 16px 14px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.spread-mobile .dynamic-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.spread-mobile .dynamic-cover {
|
||||
width: 112px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex: none;
|
||||
}
|
||||
.spread-mobile .image-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9aa8b8;
|
||||
background: #eef3f8;
|
||||
font-size: 12px;
|
||||
}
|
||||
.spread-mobile .dynamic-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.spread-mobile .dynamic-title {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: #17233d;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
word-break: break-word;
|
||||
}
|
||||
.spread-mobile .dynamic-summary {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
color: #7e8897;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
.spread-mobile .dynamic-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 16px;
|
||||
margin-top: 10px;
|
||||
color: #9aa4b2;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.spread-mobile .dynamic-meta > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.spread-mobile .dynamic-meta > span + span {
|
||||
padding-left: 12px;
|
||||
border-left: 1px solid #e5eaf2;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-group + .dynamic-skeleton-group {
|
||||
margin-top: 14px;
|
||||
}
|
||||
.spread-mobile .skeleton-block {
|
||||
display: block;
|
||||
background: linear-gradient(90deg, #edf1f5 25%, #f7f9fb 37%, #edf1f5 63%);
|
||||
background-size: 400% 100%;
|
||||
animation: dynamicSkeleton 1.4s ease infinite;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-category {
|
||||
width: 80px;
|
||||
height: 18px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-item {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 16px 14px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-cover {
|
||||
width: 112px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
flex: none;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-title {
|
||||
width: 86%;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-summary {
|
||||
width: 68%;
|
||||
height: 12px;
|
||||
margin-top: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .dynamic-skeleton-meta {
|
||||
width: 48%;
|
||||
height: 12px;
|
||||
margin-top: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.spread-mobile .pagination-skeleton {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@keyframes dynamicSkeleton {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="app" class="spread-h5" v-cloak>
|
||||
<div id="app" class="spread-mobile" v-cloak>
|
||||
<van-nav-bar title="工会动态" left-arrow left-text="返回" fixed placeholder :z-index="20" @click-left="handleBack"></van-nav-bar>
|
||||
<div class="dynamic-page">
|
||||
<header class="spread-news-header">
|
||||
<div class="page-heading">
|
||||
<div class="spread-news-title">工会动态</div>
|
||||
<div class="page-subtitle">记录工会风采,传递职工声音</div>
|
||||
</div>
|
||||
<div v-if="categoryLoading" class="category-skeleton">
|
||||
<span v-for="index in 5" :key="'category-skeleton-' + index" class="skeleton-block category-skeleton-item"></span>
|
||||
</div>
|
||||
<nav v-else-if="categories.length" class="category-sidebar" aria-label="新闻栏目">
|
||||
<div v-for="(row, depth) in categoryRows" :key="depth" class="category-row" :class="{ 'category-row-root': depth === 0 }">
|
||||
<button
|
||||
v-for="category in row.categories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{ active: category.id === row.selectedId }"
|
||||
:aria-pressed="category.id === row.selectedId"
|
||||
type="button"
|
||||
@click="handleCategoryClick(category)"
|
||||
>
|
||||
<span class="category-name">{{ category.name || '未命名栏目' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<template>
|
||||
<div class="spread-h5__toolbar">
|
||||
<div class="spread-h5__search">
|
||||
<van-search v-model.trim="keyword"
|
||||
shape="round"
|
||||
placeholder="搜索动态标题"
|
||||
@search="doSearch"
|
||||
@clear="doSearch"></van-search>
|
||||
</div>
|
||||
<div class="spread-h5__categories" v-if="categories.length">
|
||||
<div class="spread-h5__tabs-nav">
|
||||
<button type="button" class="spread-h5__tabs-arrow" @click="scrollTabs(-1)">
|
||||
<van-icon name="arrow-left"></van-icon>
|
||||
</button>
|
||||
<div ref="spreadTabs" class="spread-h5__tabs">
|
||||
<div class="spread-h5__tab"
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:class="{'spread-h5__tab--active': parentId === category.id}"
|
||||
@click="selectParent(category)">{{category.name}}</div>
|
||||
</div>
|
||||
<button type="button" class="spread-h5__tabs-arrow" @click="scrollTabs(1)">
|
||||
<van-icon name="arrow"></van-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="spread-h5__filters">
|
||||
<van-dropdown-menu class="spread-h5__filter-menu" active-color="#087af4">
|
||||
<van-dropdown-item v-model="categoryId"
|
||||
:options="typeOptions"
|
||||
@change="handleFilterChange"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="sortType"
|
||||
:options="sortOptions"
|
||||
@change="handleFilterChange"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="publishDays"
|
||||
:options="publishTimeOptions"
|
||||
@change="handleFilterChange"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</div>
|
||||
<div v-if="categoryLoading" class="dynamic-layout">
|
||||
<div class="dynamic-content">
|
||||
<div class="dynamic-list">
|
||||
<div v-for="index in 5" :key="'dynamic-item-skeleton-' + index" class="dynamic-skeleton-item">
|
||||
<span class="skeleton-block dynamic-skeleton-cover"></span>
|
||||
<div class="dynamic-skeleton-info">
|
||||
<span class="skeleton-block dynamic-skeleton-title"></span>
|
||||
<span class="skeleton-block dynamic-skeleton-meta"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="categories.length" class="dynamic-layout">
|
||||
<section class="dynamic-content">
|
||||
<van-list
|
||||
:key="activeCategoryId"
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
:finished-text="dynamicList.length ? '没有更多了' : ''"
|
||||
:error.sync="error"
|
||||
error-text="请求失败,点击重新加载"
|
||||
:immediate-check="false"
|
||||
@load="loadDynamicList"
|
||||
>
|
||||
<div v-if="dynamicList.length" class="dynamic-list">
|
||||
<article
|
||||
v-for="item in dynamicList"
|
||||
:key="item.id"
|
||||
class="dynamic-item"
|
||||
@click="openDetail(item)"
|
||||
>
|
||||
<van-image v-if="imageUrl(item.thumbUrl)" class="dynamic-cover" fit="cover" :src="imageUrl(item.thumbUrl)">
|
||||
<template #loading>
|
||||
<van-loading type="spinner" size="20" ></van-loading>
|
||||
</template>
|
||||
<template #error>
|
||||
<div class="image-empty">暂无图片</div>
|
||||
</template>
|
||||
</van-image>
|
||||
<div class="dynamic-info">
|
||||
<div class="dynamic-title">{{ item.title }}</div>
|
||||
<div class="dynamic-meta">
|
||||
<span><van-icon name="calendar-o" ></van-icon> {{ formatDate(item.publishedAt || item.createdAt) }}</span>
|
||||
<span><van-icon name="eye-o" ></van-icon> {{ item.viewCount || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<template #loading>
|
||||
<div class="dynamic-list pagination-skeleton">
|
||||
<div v-for="index in 2" :key="'pagination-skeleton-' + index" class="dynamic-skeleton-item">
|
||||
<span class="skeleton-block dynamic-skeleton-cover"></span>
|
||||
<div class="dynamic-skeleton-info">
|
||||
<span class="skeleton-block dynamic-skeleton-title"></span>
|
||||
<span class="skeleton-block dynamic-skeleton-meta"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</van-list>
|
||||
|
||||
<div v-if="listSkeletonLoading" class="spread-h5__skeleton-list">
|
||||
<div class="spread-h5__skeleton-item" v-for="index in 4" :key="'spread-list-skeleton-' + index">
|
||||
<van-skeleton title avatar :row="2"></van-skeleton>
|
||||
</div>
|
||||
<div v-if="!loading && finished && !dynamicList.length" class="dynamic-empty-state">
|
||||
<van-image
|
||||
class="dynamic-empty-illustration"
|
||||
fit="contain"
|
||||
:src="'/assets/mobile/img/home/v4/spread-news-empty.png'"
|
||||
></van-image>
|
||||
<div class="dynamic-empty-title">暂无工会动态</div>
|
||||
<div class="dynamic-empty-desc">当前没有工会动态,敬请期待~</div>
|
||||
</div>
|
||||
<div v-if="!listSkeletonLoading && pageData.list.length" class="spread-h5__list">
|
||||
<div class="spread-h5__item" v-for="item in pageData.list" :key="item.id" @click="enterDetail(item)">
|
||||
<img v-if="getCoverUrl(item.thumbUrl)" class="spread-h5__cover" :src="getCoverUrl(item.thumbUrl)" :alt="item.title">
|
||||
<div class="spread-h5__item-content">
|
||||
<div class="spread-h5__item-title">{{item.title}}</div>
|
||||
<div v-if="item.summary || item.subTitle" class="spread-h5__item-summary">{{item.summary || item.subTitle}}</div>
|
||||
<div class="spread-h5__item-meta">
|
||||
<span>{{formatDate(item.publishedAt || item.createdAt)}}</span>
|
||||
<span class="spread-h5__views"><van-icon name="eye-o"></van-icon>{{item.viewCount || 0}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading && !listSkeletonLoading" class="spread-h5__loading"><van-loading size="22px">加载中...</van-loading></div>
|
||||
<div v-if="!loading && finished && pageData.list.length" class="spread-h5__finished">没有更多了</div>
|
||||
<div v-if="!loading && finished && !pageData.list.length" class="spread-h5__empty">
|
||||
<img class="spread-h5__empty-image"
|
||||
src="/assets/mobile/img/home/v4/union-news-empty-v4.png"
|
||||
alt="暂无工会动态">
|
||||
<div class="spread-h5__empty-title">暂无工会动态</div>
|
||||
<div class="spread-h5__empty-hint">当前筛选条件下暂无可展示的动态</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
<div v-else-if="categoryError" class="dynamic-empty-state dynamic-empty-page"><van-empty description="栏目加载失败"><van-button type="primary" size="small" @click="loadCategoryList">重新加载</van-button></van-empty></div>
|
||||
<div v-else class="dynamic-empty-state dynamic-empty-page">
|
||||
<van-image
|
||||
class="dynamic-empty-illustration"
|
||||
fit="contain"
|
||||
:src="'/assets/mobile/img/home/v4/spread-news-empty.png'"
|
||||
></van-image>
|
||||
<div class="dynamic-empty-title">暂无工会动态</div>
|
||||
<div class="dynamic-empty-desc">当前没有工会动态,敬请期待~</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -128,166 +461,129 @@ layout("/layouts/platform_h5.html"){
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/h5/spread",
|
||||
categories: [],
|
||||
parentId: "",
|
||||
categoryId: "",
|
||||
keyword: "",
|
||||
sortType: "comprehensive",
|
||||
publishDays: 0,
|
||||
sortOptions: [
|
||||
{text: "综合排序", value: "comprehensive"},
|
||||
{text: "最新发布", value: "latest"},
|
||||
{text: "浏览最多", value: "views"}
|
||||
],
|
||||
publishTimeOptions: [
|
||||
{text: "发布时间", value: 0},
|
||||
{text: "近7天", value: 7},
|
||||
{text: "近30天", value: 30},
|
||||
{text: "近半年", value: 180},
|
||||
{text: "近一年", value: 365}
|
||||
],
|
||||
loading: false,
|
||||
listSkeletonLoading: true,
|
||||
finished: false,
|
||||
scrollContainer: null,
|
||||
requestSeq: 0,
|
||||
pageData: {pageNumber: 1, pageSize: 10, totalCount: 0, list: []}
|
||||
categories: [], categoryLoading: true, categoryError: false, activeCategoryId: "",
|
||||
dynamicList: [], loading: false, finished: false, error: false,
|
||||
currentPage: 1, pageSize: 10, requestVersion: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentChildren() {
|
||||
const parent = this.categories.find(item => item.id === this.parentId)
|
||||
return parent && parent.children ? parent.children : []
|
||||
},
|
||||
typeOptions() {
|
||||
return [{text: "全部类型", value: this.parentId}].concat(
|
||||
this.currentChildren.map(item => ({text: item.name, value: item.id}))
|
||||
)
|
||||
categoryRows() {
|
||||
const findPath = nodes => {
|
||||
for (const category of nodes || []) {
|
||||
if (category.id === this.activeCategoryId) return [category]
|
||||
const children = findPath(category.children)
|
||||
if (children.length) return [category].concat(children)
|
||||
}
|
||||
return []
|
||||
}
|
||||
const rows = []
|
||||
let categories = this.categories
|
||||
findPath(this.categories).forEach(category => {
|
||||
rows.push({categories: categories, selectedId: category.id})
|
||||
categories = category.children || []
|
||||
})
|
||||
return rows.length ? rows : [{categories: this.categories, selectedId: ""}]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadCategories() {
|
||||
return this.$axios.post(this.apiBase + "/categoryTree").then(resp => {
|
||||
if (resp.code !== 0) return
|
||||
this.categories = resp.data || []
|
||||
const firstParent = this.categories[0]
|
||||
if (!firstParent) return
|
||||
this.parentId = firstParent.id
|
||||
this.categoryId = firstParent.id
|
||||
})
|
||||
},
|
||||
loadPage() {
|
||||
if (!this.categoryId) {
|
||||
this.listSkeletonLoading = false
|
||||
return
|
||||
}
|
||||
if (this.loading || this.finished) {
|
||||
return
|
||||
}
|
||||
const showSkeleton = this.pageData.pageNumber === 1 && !this.pageData.list.length
|
||||
if (showSkeleton) this.listSkeletonLoading = true
|
||||
this.loading = true
|
||||
const requestSeq = ++this.requestSeq
|
||||
this.$axios.post(this.apiBase + "/pageData", {
|
||||
categoryId: this.categoryId,
|
||||
title: this.keyword,
|
||||
sortType: this.sortType,
|
||||
publishDays: this.publishDays,
|
||||
pageNumber: this.pageData.pageNumber,
|
||||
pageSize: this.pageData.pageSize
|
||||
}).then(resp => {
|
||||
if (requestSeq !== this.requestSeq) return
|
||||
if (resp.code === 0) {
|
||||
const data = resp.data || {}
|
||||
const rows = data.list || []
|
||||
this.pageData.list = this.pageData.list.concat(rows)
|
||||
this.pageData.totalCount = data.totalCount || 0
|
||||
this.pageData.pageNumber += 1
|
||||
this.finished = this.pageData.list.length >= this.pageData.totalCount || rows.length < this.pageData.pageSize
|
||||
} else {
|
||||
this.finished = true
|
||||
}
|
||||
}).finally(() => {
|
||||
if (requestSeq === this.requestSeq) {
|
||||
this.loading = false
|
||||
if (showSkeleton) this.listSkeletonLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetPage() {
|
||||
this.requestSeq += 1
|
||||
this.loading = false
|
||||
this.listSkeletonLoading = true
|
||||
this.finished = false
|
||||
this.pageData = {pageNumber: 1, pageSize: 10, totalCount: 0, list: []}
|
||||
this.$nextTick(() => this.loadPage())
|
||||
},
|
||||
handlePageScroll() {
|
||||
if (!this.scrollContainer || this.loading || this.finished) return
|
||||
const remaining = this.scrollContainer.scrollHeight
|
||||
- this.scrollContainer.scrollTop
|
||||
- this.scrollContainer.clientHeight
|
||||
if (remaining <= 80) this.loadPage()
|
||||
},
|
||||
doSearch() {
|
||||
this.resetPage()
|
||||
},
|
||||
scrollTabs(direction) {
|
||||
const tabs = this.$refs.spreadTabs
|
||||
if (!tabs) return
|
||||
tabs.scrollTo({
|
||||
left: tabs.scrollLeft + direction * tabs.clientWidth * 0.75,
|
||||
behavior: "smooth"
|
||||
})
|
||||
},
|
||||
selectParent(category) {
|
||||
if (this.parentId === category.id) return
|
||||
this.parentId = category.id
|
||||
this.categoryId = category.id
|
||||
this.resetPage()
|
||||
},
|
||||
handleFilterChange() {
|
||||
this.resetPage()
|
||||
},
|
||||
enterDetail(item) {
|
||||
this.$pjaxReplace(this.apiBase + "/detail?id=" + encodeURIComponent(item.id) + "&from=list")
|
||||
},
|
||||
handleBack() {
|
||||
this.$pjaxReplace("/platform/h5/home")
|
||||
},
|
||||
getCoverUrl(value) {
|
||||
findFirstLeafCategory(categories) {
|
||||
for (const category of categories || []) {
|
||||
if (!category.children || !category.children.length) return category
|
||||
const child = this.findFirstLeafCategory(category.children)
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
},
|
||||
loadCategoryList() {
|
||||
this.categoryLoading = true
|
||||
this.categoryError = false
|
||||
this.$axios.post("/platform/h5/spread/categoryTree").then(resp => {
|
||||
if (resp.code !== 0) throw new Error(resp.msg || "栏目加载失败")
|
||||
this.categories = resp.data || []
|
||||
const requestedId = new URLSearchParams(window.location.search).get("categoryId")
|
||||
const findCategory = nodes => {
|
||||
for (const node of nodes || []) {
|
||||
if (node.id === requestedId) return node
|
||||
const child = findCategory(node.children)
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
}
|
||||
const requested = requestedId && findCategory(this.categories)
|
||||
const category = this.findFirstLeafCategory(requested ? [requested] : this.categories)
|
||||
if (category) {
|
||||
this.activeCategoryId = category.id
|
||||
this.resetDynamicList()
|
||||
this.loadDynamicList()
|
||||
}
|
||||
}).catch(() => {
|
||||
this.categories = []
|
||||
this.categoryError = true
|
||||
}).finally(() => this.categoryLoading = false)
|
||||
},
|
||||
handleCategoryClick(category) {
|
||||
const selected = this.findFirstLeafCategory([category])
|
||||
if (!selected || selected.id === this.activeCategoryId) return
|
||||
this.activeCategoryId = selected.id
|
||||
this.resetDynamicList()
|
||||
this.loadDynamicList()
|
||||
},
|
||||
resetDynamicList() {
|
||||
this.requestVersion += 1
|
||||
this.dynamicList = []
|
||||
this.currentPage = 1
|
||||
this.finished = false
|
||||
this.error = false
|
||||
this.loading = false
|
||||
},
|
||||
loadDynamicList() {
|
||||
if (this.finished || !this.activeCategoryId) return
|
||||
const requestVersion = this.requestVersion
|
||||
this.loading = true
|
||||
this.error = false
|
||||
this.$axios.post("/platform/h5/spread/pageData", {
|
||||
pageNumber: this.currentPage, pageSize: this.pageSize, categoryId: this.activeCategoryId
|
||||
}).then(resp => {
|
||||
if (requestVersion !== this.requestVersion) return
|
||||
if (resp.code !== 0) throw new Error(resp.msg || "加载失败")
|
||||
const data = resp.data || {}
|
||||
const rows = data.list || []
|
||||
this.dynamicList = this.dynamicList.concat(rows)
|
||||
const total = Number(data.totalCount || 0)
|
||||
this.finished = total > 0 ? this.dynamicList.length >= total : rows.length < this.pageSize
|
||||
if (!this.finished) this.currentPage += 1
|
||||
}).catch(() => {
|
||||
if (requestVersion === this.requestVersion) this.error = true
|
||||
}).finally(() => {
|
||||
if (requestVersion === this.requestVersion) this.loading = false
|
||||
})
|
||||
},
|
||||
imageUrl(value) {
|
||||
if (!value) return ""
|
||||
let files = value
|
||||
if (typeof value === "string" && value.trim().startsWith("[")) {
|
||||
try {
|
||||
files = JSON.parse(value)
|
||||
} catch (e) {
|
||||
files = []
|
||||
}
|
||||
if (typeof value === "string" && /^[\[{]/.test(value.trim())) {
|
||||
try { files = JSON.parse(value) } catch (e) { return "" }
|
||||
}
|
||||
if (!Array.isArray(files)) files = String(files).split(",")
|
||||
if (!Array.isArray(files)) files = typeof files === "string" ? files.split(",") : [files]
|
||||
const first = files[0]
|
||||
if (typeof first === "string") return first.trim()
|
||||
return first && (first.src || first.url || first.data || (first.response && first.response.data)) || ""
|
||||
},
|
||||
formatDate(value) {
|
||||
return value ? this.$moment(Number(value)).format("YYYY-MM-DD") : ""
|
||||
},
|
||||
openDetail(item) {
|
||||
this.$pjaxReplace("/platform/h5/spread/detail?id=" + encodeURIComponent(item.id) + "&from=list")
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCategories().then(() => this.resetPage())
|
||||
},
|
||||
mounted() {
|
||||
this.scrollContainer = document.querySelector("body > .main-page .page-wrapper")
|
||||
if (this.scrollContainer) {
|
||||
this.scrollContainer.addEventListener("scroll", this.handlePageScroll, {passive: true})
|
||||
}
|
||||
this.loadCategoryList()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.scrollContainer) {
|
||||
this.scrollContainer.removeEventListener("scroll", this.handlePageScroll)
|
||||
}
|
||||
this.requestVersion += 1
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,8 @@ const home = {
|
||||
<div class="home-page">
|
||||
<div class="home-summary-bar" :class="{'home-summary-bar--visible': showHomeSummary}">
|
||||
<img class="home-summary-bar__logo"
|
||||
src="/assets/mobile/img/home/v4/中国地质大学-logo.png"
|
||||
alt="中国地质大学">
|
||||
:src="appLogo"
|
||||
:alt="appName">
|
||||
<span class="home-summary-bar__divider"></span>
|
||||
<span class="home-summary-bar__title">智慧工会</span>
|
||||
</div>
|
||||
@@ -14,8 +14,8 @@ const home = {
|
||||
<div class="home-hero__content">
|
||||
<div class="home-hero__topbar">
|
||||
<img class="home-hero__brand-logo"
|
||||
src="/assets/mobile/img/home/v4/中国地质大学-logo.png"
|
||||
alt="中国地质大学">
|
||||
:src="appLogo"
|
||||
:alt="appName">
|
||||
</div>
|
||||
<div class="home-hero__title">智慧工会</div>
|
||||
<div class="home-hero__subtitle">情系职工 · 真诚服务</div>
|
||||
@@ -70,25 +70,12 @@ const home = {
|
||||
|
||||
<div class="union-news-panel">
|
||||
<div class="union-news-panel__head">
|
||||
<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__title">工会动态</div>
|
||||
<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 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"
|
||||
@@ -108,19 +95,15 @@ const home = {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="union-news-list" ref="spreadViewport" v-else-if="spreadDynamics.length">
|
||||
<div class="union-news-list" v-else-if="spreadDynamics.length">
|
||||
<div class="union-news-item"
|
||||
v-for="item in spreadDynamics"
|
||||
:key="item.href || item.title"
|
||||
:key="item.id"
|
||||
@click="enterSpreadDetail(item)">
|
||||
<img v-if="item.thumbUrl && !item.imageFailed"
|
||||
<img v-if="getSpreadCover(item.thumbUrl)"
|
||||
class="union-news-item__cover"
|
||||
:src="item.thumbUrl" referrerpolicy="no-referrer"
|
||||
@error="$set(item, 'imageFailed', true)"
|
||||
:src="getSpreadCover(item.thumbUrl)"
|
||||
: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"
|
||||
@@ -134,10 +117,9 @@ const home = {
|
||||
</div>
|
||||
<div class="union-news-item__meta">
|
||||
<span>{{ formatSpreadDate(item.publishedAt || item.createdAt) }}</span>
|
||||
<span v-if="item.viewCount !== null && item.viewCount !== undefined"
|
||||
class="union-news-item__views">
|
||||
<span class="union-news-item__views">
|
||||
<van-icon name="eye-o"></van-icon>
|
||||
{{ item.viewCount }}
|
||||
{{ item.viewCount || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,10 +247,6 @@ const home = {
|
||||
classroomRecommendOptions: [],
|
||||
showHomeSummary: false,
|
||||
homeScrollContainer: null,
|
||||
spreadSections: [],
|
||||
spreadMenuVisible: false,
|
||||
spreadHistoryToken: null,
|
||||
spreadActiveIndex: 0,
|
||||
spreadDynamics: [],
|
||||
spreadLoading: true,
|
||||
spreadLoadSeq: 0,
|
||||
@@ -298,14 +276,14 @@ const home = {
|
||||
className: "feature-card--model",
|
||||
href: "/platform/honor/manage/h5"
|
||||
},
|
||||
{
|
||||
/* {
|
||||
name: "普惠信息",
|
||||
desc: "普惠服务信息",
|
||||
action: "查看信息",
|
||||
asset: "/assets/mobile/img/home/v4/feature-inclusive-v8.png",
|
||||
className: "feature-card--inclusive",
|
||||
href: "/platform/h5/inclusive"
|
||||
}
|
||||
} */
|
||||
],
|
||||
appLogo: "",
|
||||
appName: "",
|
||||
@@ -333,10 +311,6 @@ const home = {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
spreadHasImages() {
|
||||
// 按当前栏目统一保留图片列;异步补图及加载失败都会重新计算,全部无图时文字占满行宽。
|
||||
return this.spreadDynamics.some(item => item.thumbUrl && !item.imageFailed)
|
||||
},
|
||||
currentBanner() {
|
||||
return this.bannerList[this.activeBannerIndex] || this.bannerList[0] || {}
|
||||
}
|
||||
@@ -542,29 +516,18 @@ const home = {
|
||||
listSpreadHomeContent() {
|
||||
const loadSeq = ++this.spreadLoadSeq
|
||||
this.spreadLoading = true
|
||||
this.$axios.post("/platform/home/getNews").then((res) => {
|
||||
this.$axios.post("/platform/h5/spread/pageData", {
|
||||
sortType: "latest",
|
||||
pageNumber: 1,
|
||||
pageSize: 10
|
||||
}).then((res) => {
|
||||
if (loadSeq === this.spreadLoadSeq && res.code === 0) {
|
||||
// 接口按栏目返回 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)
|
||||
const data = res.data || {}
|
||||
this.spreadDynamics = data.list || []
|
||||
}
|
||||
}).catch(() => {
|
||||
if (loadSeq === this.spreadLoadSeq) {
|
||||
this.$set(this, "spreadSections", [])
|
||||
this.$set(this, "spreadDynamics", [])
|
||||
this.spreadDynamics = []
|
||||
}
|
||||
}).finally(() => {
|
||||
if (loadSeq === this.spreadLoadSeq) {
|
||||
@@ -572,48 +535,6 @@ const home = {
|
||||
}
|
||||
})
|
||||
},
|
||||
selectSpreadSection(index) {
|
||||
// 切换栏目后回到横向卡片列表开头,等待新栏目列表渲染完成。
|
||||
// 新闻样式锚定 home-page 根节点作用域,动态行无需等待 styleMixin 异步补属性。
|
||||
const section = this.spreadSections[index] || {}
|
||||
this.$set(this, "spreadActiveIndex", index)
|
||||
this.$set(this, "spreadDynamics", section.value || [])
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.spreadViewport) this.$refs.spreadViewport.scrollLeft = 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] : [])
|
||||
@@ -647,22 +568,10 @@ const home = {
|
||||
return Number.isNaN(numberValue) ? String(value) : this.$moment(numberValue).format("YYYY-MM-DD")
|
||||
},
|
||||
enterSpreadDetail(item) {
|
||||
const url = this.resolveUnionNewsUrl(item.href)
|
||||
if (url) {
|
||||
window.location.href = url
|
||||
}
|
||||
this.$pjaxReplace("/platform/h5/spread/detail?id=" + encodeURIComponent(item.id) + "&from=home")
|
||||
},
|
||||
enterSpreadList() {
|
||||
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
|
||||
this.$pjaxReplace("/platform/h5/spread")
|
||||
},
|
||||
menuClick(item) {
|
||||
this.$pjaxReplace(item.href)
|
||||
@@ -701,10 +610,6 @@ const home = {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 注册弹层返回回调,关闭状态由历史回退同步,避免留下多余历史记录。
|
||||
this.spreadHistoryToken = window.h5PopupHistory.register(() => {
|
||||
this.$set(this, "spreadMenuVisible", false)
|
||||
})
|
||||
this.listHomeBrand()
|
||||
this.listRecommendApp()
|
||||
this.listClassroomCourses()
|
||||
@@ -722,8 +627,6 @@ 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)
|
||||
}
|
||||
@@ -997,7 +900,8 @@ const home = {
|
||||
margin: 0 12px 7px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.08fr 1fr;
|
||||
grid-template-rows: 86px 86px 92px;
|
||||
grid-template-rows: 86px 86px;
|
||||
grid-auto-rows: 92px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user