consumer-app/pages/detail/richTextDetail.vue

412 lines
11 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="rich-text-detail-page" :class="{ 'no-bottom-bar': hideBottomBar }">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
<NavHeader title="详情" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<scroll-view class="content-scroll" scroll-y="true">
<!-- 加载中 -->
<view class="loading-state" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
<!-- 富文本内容 - 混合渲染 -->
<view
class="content-wrapper"
v-else-if="parsedContent && parsedContent.length > 0"
>
<block v-for="(item, index) in parsedContent" :key="index">
<!-- 使 image -->
<view v-if="item.type === 'image'" class="rich-text-image-wrapper">
<image
class="rich-text-image"
:src="item.src"
mode="widthFix"
:lazy-load="true"
></image>
</view>
<!-- 如果是文本内容,使用 rich-text -->
<rich-text
v-else-if="item.type === 'text'"
:nodes="item.html"
class="rich-text-content"
></rich-text>
</block>
</view>
<!-- 空数据提示 -->
<view class="empty-state" v-else>
<image
class="empty-icon"
src="/static/home/entry_icon.png"
mode="aspectFit"
></image>
<text class="empty-text"></text>
</view>
</scroll-view>
<!-- 底部操作栏从收藏进入时隐藏 -->
<DetailActionBar
v-if="detailData && !hideBottomBar"
:liked="isLiked"
:collected="isCollected"
:id="detailId"
:zanType="noticeType"
:type="noticeType"
/>
</view>
</view>
</template>
<script>
import { getGuildDetail } from "@/api/home.js";
import { getBusinessData } from "@/api/profile.js";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
import DetailActionBar from "@/components/DetailActionBar/DetailActionBar.vue";
export default {
components: {
NavHeader,
DetailActionBar,
},
data() {
return {
statusBarHeight: 0,
detailId: null, // 详情ID用于通过接口获取数据
title: "",
content: "",
parsedContent: [], // 解析后的内容数组
loading: false,
isLiked: false, // 是否已点赞
isCollected: false, // 是否已收藏
detailData: null, // 详情数据(通过接口获取时使用)
noticeType: null, // 内容类型
hideBottomBar: false, // 为 true 时不显示底部操作栏(如从收藏进入)
};
},
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
},
onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
// 支持三种模式:
// 1. 从收藏进入collectId + collectType用 getBusinessData 拉取详情
// 2. 通过 id 调用接口获取详情(原 activitiesDetail 的用法)
// 3. 直接传入 title 和 content原 richTextDetail 的用法)
const collectId = options.collectId != null ? decodeURIComponent(String(options.collectId)) : null;
const collectType = options.collectType != null ? decodeURIComponent(String(options.collectType)) : null;
if (collectId != null && collectId !== "") {
this.detailId = collectId;
this.hideBottomBar = !!(options.hideBar || options.hideBar === "1");
this.loadDetailByBusinessData(collectId, collectType || "");
} else if (options.id) {
this.detailId = options.id;
this.loadDetailById();
} else if (options.content) {
// 直接传入内容模式
if (options.title) {
this.title = decodeURIComponent(options.title);
}
let content = decodeURIComponent(options.content);
// 解析 HTML分离图片和文本
this.parsedContent = this.parseHtmlContent(content);
if (options.info) {
// info 从上个页面传过来是 encodeURIComponent(JSON.stringify(obj)),这里要 decode + JSON.parse
try {
const parsed = JSON.parse(decodeURIComponent(options.info));
this.detailData = parsed;
this.detailId = parsed.id || this.detailId;
// 兼容字段noticeType/messageType/type
this.noticeType = parsed.noticeType ?? parsed.messageType ?? parsed.type ?? this.noticeType;
// 如果没传 title则用 info 里的 title
if (!this.title && parsed.title) {
this.title = parsed.title;
}
} catch (e) {
console.error('解析 info 失败:', e, options.info);
}
}
}
},
// 分享给朋友(微信小程序)
onShareAppMessage() {
if (!this.detailData) {
return {
title: this.title || "详情",
path: this.detailId
? `/pages/detail/richTextDetail?id=${this.detailId}`
: "",
};
}
return {
title: this.detailData.title || this.title || "详情",
path: `/pages/detail/richTextDetail?id=${this.detailId}`,
imageUrl: this.detailData.coverUrl || "",
};
},
// 分享到朋友圈(微信小程序)
// #ifdef MP-WEIXIN
onShareTimeline() {
if (!this.detailData) {
return {
title: this.title || "详情",
query: this.detailId ? `id=${this.detailId}` : "",
};
}
return {
title: this.detailData.title || this.title || "详情",
query: `id=${this.detailId}`,
imageUrl: this.detailData.coverUrl || "",
};
},
// #endif
methods: {
// 判断 VO 是否有数据(有数据即已点赞/已收藏)
hasRespData(vo) {
if (vo == null) return false;
if (Array.isArray(vo)) return vo.length > 0;
if (typeof vo === "object") return Object.keys(vo).length > 0;
return !!vo;
},
// 通过收藏进入getBusinessData(id: collectId, type: collectType) 拉取详情
async loadDetailByBusinessData(collectId, collectType) {
try {
this.loading = true;
const res = await getBusinessData({ id: collectId, type: collectType });
if (res) {
this.detailData = res;
this.noticeType = res.noticeType ?? res.messageType ?? res.type ?? collectType;
this.title = res.title || "详情";
if (res.content) {
this.parsedContent = this.parseHtmlContent(res.content);
}
this.isLiked = this.hasRespData(res.zanRespVO);
this.isCollected = this.hasRespData(res.collectRespVO);
}
} catch (error) {
console.error("加载详情失败:", error);
uni.showToast({ title: "加载失败,请重试", icon: "none" });
} finally {
this.loading = false;
}
},
// 通过 ID 加载详情(原 activitiesDetail 的功能)
async loadDetailById() {
if (!this.detailId) {
return;
}
try {
this.loading = true;
const res = await getGuildDetail(this.detailId);
if (res) {
this.detailData = res;
this.noticeType = res.noticeType;
this.title = res.title || "详情";
// 解析 HTML分离图片和文本
if (res.content) {
this.parsedContent = this.parseHtmlContent(res.content);
}
// 点赞状态zanRespVO 有数据表示已点赞
this.isLiked = this.hasRespData(res.zanRespVO);
// 收藏状态collectRespVO 有数据表示已收藏
this.isCollected = this.hasRespData(res.collectRespVO);
}
} catch (error) {
console.error("加载详情失败:", error);
uni.showToast({
title: "加载失败,请重试",
icon: "none",
});
} finally {
this.loading = false;
}
},
// 解析 HTML 内容,将图片和文本分离
parseHtmlContent(html) {
if (!html) return [];
const result = [];
let currentIndex = 0;
// 匹配所有 img 标签
const imgRegex = /<img([^>]*)>/gi;
let match;
let lastIndex = 0;
while ((match = imgRegex.exec(html)) !== null) {
// 添加图片之前的文本内容
if (match.index > lastIndex) {
const textContent = html.substring(lastIndex, match.index);
if (textContent.trim()) {
result.push({
type: "text",
html: textContent,
});
}
}
// 提取图片 src
const imgAttrs = match[1];
const srcMatch = imgAttrs.match(/src=["']([^"']+)["']/i);
if (srcMatch && srcMatch[1]) {
result.push({
type: "image",
src: srcMatch[1],
});
}
lastIndex = match.index + match[0].length;
}
// 添加最后剩余的文本内容
if (lastIndex < html.length) {
const textContent = html.substring(lastIndex);
if (textContent.trim()) {
result.push({
type: "text",
html: textContent,
});
}
}
// 如果没有匹配到图片,直接返回整个内容作为文本
if (result.length === 0 && html.trim()) {
result.push({
type: "text",
html: html,
});
}
return result;
},
},
};
</script>
<style lang="scss" scoped>
.rich-text-detail-page {
height: 100vh;
background-color: #e2e8f1;
display: flex;
flex-direction: column;
overflow: hidden;
padding-bottom: 120rpx; // 为底部操作栏留出空间
&.no-bottom-bar {
padding-bottom: 0;
}
}
.header-fixed-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #e2e8f1;
}
.main-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.content-scroll {
flex: 1;
height: 0; // 配合 flex: 1 使用,让 scroll-view 可以滚动
}
.content-wrapper {
padding: 30rpx 20rpx;
background-color: #ffffff;
margin: 20rpx;
border-radius: 20rpx;
min-height: 200rpx;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: visible;
word-wrap: break-word;
word-break: break-all;
// 图片容器样式
.rich-text-image-wrapper {
width: 100%;
margin: 20rpx 0;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
.rich-text-image {
width: 100%;
height: auto;
display: block;
max-width: 100%;
}
}
// 文本内容样式
.rich-text-content {
width: 100%;
display: block;
font-family: PingFang-SC, PingFang-SC;
font-size: 28rpx;
line-height: 1.8;
color: #333333;
word-wrap: break-word;
word-break: break-all;
box-sizing: border-box;
}
}
/* 加载状态 */
.loading-state {
display: flex;
justify-content: center;
align-items: center;
padding: 200rpx 0;
min-height: 500rpx;
.loading-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
}
}
/* 空数据提示 */
.empty-state {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 200rpx 0;
min-height: 500rpx;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.5;
}
.empty-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 28rpx;
color: #999999;
}
}
</style>