diff --git a/miniprogram/src/pages/content-detail/index.jsx b/miniprogram/src/pages/content-detail/index.jsx
index 24972e0..a811126 100644
--- a/miniprogram/src/pages/content-detail/index.jsx
+++ b/miniprogram/src/pages/content-detail/index.jsx
@@ -1,70 +1,123 @@
-import { View, Text, Image, Video, ScrollView, Button, RichText } from '@tarojs/components';
+import { View, Text, Image, Video, ScrollView, Input, Button, RichText } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
-import { getContent } from '@/utils/content';
+import { getContent, getComments, addComment } from '@/utils/content';
+import { isAuthed } from '@/utils/auth';
import './index.scss';
-/** 资讯详情:封面/视频 + 正文 + 图集 + 外链 + 发布人。 */
+const rw = (s) => (s || '').replace('T', ' ').replace('Z', '').slice(0, 16);
+
+/** 资讯详情(微信文章式):标题/作者/阅读 + 正文 + 动作条 + 留言。 */
export default function ContentDetail() {
const [item, setItem] = useState(null);
+ const [comments, setComments] = useState([]);
const [err, setErr] = useState('');
const [loaded, setLoaded] = useState(false);
+ const [like, setLike] = useState(false);
+ const [fav, setFav] = useState(false);
+ const [draft, setDraft] = useState('');
+ const [sending, setSending] = useState(false);
useEffect(() => {
const inst = Taro.getCurrentInstance();
const id = (inst && inst.router && inst.router.params && inst.router.params.id) || '';
if (!id) { setErr('无效内容'); setLoaded(true); return; }
getContent(id).then(setItem).catch(() => setErr('加载失败')).finally(() => setLoaded(true));
+ getComments(id).then(setComments).catch(() => {});
}, []);
if (!loaded) return 加载中…;
if (err || !item) return {err || '内容不存在'};
+ const id = item.id;
const img = item.cover || (item.video ? item.video_cover : '');
const images = Array.isArray(item.images) ? item.images : [];
+ const author = item.publisher_name || '云超服';
+ const views = item.read_count || 0;
+
+ const copyLink = () => Taro.setClipboardData({ data: `https://opc.pinesound.cn/#/content-detail?id=${id}` }).then(() => Taro.showToast({ title: '链接已复制', icon: 'none' }));
+ const scrollToComment = () => { Taro.pageScrollTo({ selector: '.cd-comments', duration: 300 }); };
+ const send = async () => {
+ if (!isAuthed()) { Taro.showToast({ title: '请先登录', icon: 'none' }); return; }
+ if (!draft.trim()) return;
+ setSending(true);
+ try {
+ const c = await addComment(id, draft.trim());
+ if (c) { setComments((m) => [c, ...m]); setDraft(''); Taro.showToast({ title: '已留言', icon: 'success' }); }
+ } catch (e) { Taro.showToast({ title: (e && e.message) || '留言失败', icon: 'none' }); }
+ finally { setSending(false); }
+ };
return (
- {/* 视频或封面 */}
+ {/* 封面/视频 */}
{item.video
?
: img
?
: null}
+ {/* 标题 + 元信息 */}
{item.title}
-
- {/* 发布人行 */}
- {item.publisher_avatar
- ?
- : }
- {item.publisher_name || '云超服'}
+ {item.publisher_avatar ? : }
+ {author}✓
+
+
+ {rw(item.created_at)}
·
- {fmt(item.created_at)}
+ 阅读 {views}
+ {/* 正文 */}
{item.summary ? {item.summary} : null}
{item.body
?
: 暂无正文}
-
- {/* 图集 */}
{images.length > 0 && (
{images.map((u, i) => )}
)}
-
{item.link ? (
-
+
) : null}
+
+ {/* 作者条 + 动作 */}
+
+
+ {item.publisher_avatar ? : }
+ {author}✓
+
+
+ setLike((v) => !v)}>👍{like ? 1 : 0}
+ ↗转发
+ setFav((v) => !v)}>♥
+ ✎留言
+
+
+
+ {/* 阅读 + 留言 */}
+ 阅读 {views}
+
+ 留言
+
+ setDraft(e.detail.value)} />
+ 😊🖼
+
+
+ {comments.length === 0
+ ? 还没有留言,来写首评
+ : comments.map((c) => (
+
+
+ {c.avatar ? : }
+ {c.nickname || c.username || '用户'}{rw(c.created_at)}
+
+ {c.content}
+
+ ))}
+
);
}
-
-function fmt(s) {
- return (s || '').replace('T', ' ').replace('Z', '').slice(0, 16);
-}
diff --git a/miniprogram/src/pages/content-detail/index.scss b/miniprogram/src/pages/content-detail/index.scss
index 651f16a..e026e31 100644
--- a/miniprogram/src/pages/content-detail/index.scss
+++ b/miniprogram/src/pages/content-detail/index.scss
@@ -1,18 +1,47 @@
-/* 资讯详情 · 小程序样式(暗色令牌一致) */
+/* 资讯详情 · 小程序样式(微信文章式) */
.cd { position: relative; z-index: 2; padding: 0 0 60rpx; }
.cd-cover { display: block; width: 100%; border-radius: 20rpx; }
.cd-video { width: 100%; height: 400rpx; border-radius: 20rpx; margin-bottom: 24rpx; }
-.cd-title { display: block; margin-top: 24rpx; padding: 0 28rpx; font-size: 38rpx; color: #fff; font-weight: 700; line-height: 1.4; }
+.cd-title { display: block; margin-top: 24rpx; padding: 0 28rpx; font-size: 40rpx; color: #fff; font-weight: 700; line-height: 1.4; }
.cd-meta-row { display: flex; align-items: center; gap: 10rpx; padding: 20rpx 28rpx 0; }
-.cd-ava { width: 36rpx; height: 36rpx; border-radius: 50%; background: linear-gradient(135deg, #555, #333); }
-.cd-author { font-size: 24rpx; color: #c4c2c3; }
-.cd-dot { font-size: 24rpx; color: #6d6d72; }
-.cd-meta-at { font-size: 24rpx; color: #7c7c80; }
+.cd-ava { width: 36rpx; height: 36rpx; border-radius: 50%; background: linear-gradient(135deg, #555, #333); flex-shrink: 0; }
+.cd-ava2 { width: 44rpx; height: 44rpx; border-radius: 50%; background: linear-gradient(135deg, #555, #333); flex-shrink: 0; }
+.cd-author { font-size: 25rpx; color: #c4c2c3; display: inline-flex; align-items: center; gap: 4rpx; }
+.cd-badge { font-size: 22rpx; color: #26a69a; }
+.cd-meta-line { display: flex; align-items: center; gap: 8rpx; padding: 8rpx 28rpx 0; }
+.cd-meta-at { font-size: 23rpx; color: #7c7c80; }
+.cd-dot { font-size: 23rpx; color: #6d6d72; }
.cd-summary { display: block; margin: 20rpx 28rpx 0; font-size: 27rpx; color: #c4c2c3; line-height: 1.7; }
-.cd-body { display: block; margin: 20rpx 28rpx 0; font-size: 28rpx; color: #e4e4e6; line-height: 1.8; }
+.cd-body { display: block; margin: 20rpx 28rpx 0; font-size: 30rpx; color: #e4e4e6; line-height: 1.9; }
.cd-body img, .cd-body video { max-width: 100%; border-radius: 14rpx; }
.cd-body p { margin: 0 0 8rpx; }
.cd-gallery { display: flex; gap: 12rpx; margin: 24rpx 28rpx 0; white-space: nowrap; }
.cd-gal-img { width: 240rpx; height: 240rpx; border-radius: 14rpx; flex-shrink: 0; margin-right: 12rpx; }
.cd-link { margin: 28rpx 28rpx 0; }
+
+/* 作者条 + 动作 */
+.cd-bar { display: flex; align-items: center; justify-content: space-between; padding: 22rpx 28rpx; margin-top: 24rpx; border-top: 1rpx solid rgba(255,255,255,.08); }
+.cd-bar-left { display: flex; align-items: center; gap: 12rpx; }
+.cd-bar-actions { display: flex; align-items: center; gap: 2rpx; }
+.cd-act { display: flex; flex-direction: column; align-items: center; gap: 2rpx; padding: 6rpx 16rpx; border-radius: 10rpx; }
+.cd-act-ic { font-size: 34rpx; color: #cfcfd2; }
+.cd-act-ic.on { color: #3b82f6; }
+.cd-act-n { font-size: 20rpx; color: #8e8e8e; }
+
+/* 阅读 + 留言 */
+.cd-read { padding: 24rpx 28rpx 0; font-size: 24rpx; color: #7c7c80; }
+.cd-comments { padding: 20rpx 28rpx 0; border-top: 1rpx solid rgba(255,255,255,.08); margin-top: 20rpx; }
+.cd-cmt-title { display: block; font-size: 30rpx; color: #fff; font-weight: 700; margin-bottom: 16rpx; }
+.cd-cmt-input { display: flex; align-items: center; gap: 12rpx; background: #14141a; border: 1rpx solid #2a2a2d; border-radius: 14rpx; padding: 10rpx 16rpx; }
+.cd-cmt-field { flex: 1; color: #e4e4e6; font-size: 26rpx; }
+.cd-cmt-tools { display: flex; gap: 14rpx; font-size: 30rpx; color: #9b9b9b; }
+.cd-cmt-send { margin: 0; padding: 0 20rpx; font-size: 22rpx; line-height: 2; }
+.cd-cmt-empty { display: block; padding: 60rpx 0; text-align: center; font-size: 24rpx; color: #8e8e8e; }
+.cd-cmt { margin-top: 22rpx; }
+.cd-cmt-head { display: flex; align-items: center; gap: 12rpx; }
+.cd-cmt-meta { display: flex; flex-direction: column; }
+.cd-cmt-name { font-size: 24rpx; color: #8e8e8e; }
+.cd-cmt-time { font-size: 20rpx; color: #6d6d72; }
+.cd-cmt-text { display: block; margin-top: 10rpx; font-size: 27rpx; color: #e4e4e6; line-height: 1.6; }
+
.cd-err { display: block; padding: 90rpx 0; text-align: center; font-size: 26rpx; color: #8e8e8e; }
diff --git a/miniprogram/src/utils/content.js b/miniprogram/src/utils/content.js
index 67b508c..b457040 100644
--- a/miniprogram/src/utils/content.js
+++ b/miniprogram/src/utils/content.js
@@ -24,3 +24,15 @@ export async function getContent(id) {
const r = await request('GET', `/opc/content/${id}`, {}, false);
return r || null;
}
+
+/** 资讯留言列表(公开) */
+export async function getComments(id) {
+ const r = await request('GET', `/opc/content/${id}/comments`, {}, false);
+ return Array.isArray(r?.items) ? r.items : [];
+}
+
+/** 发表留言(需登录) */
+export async function addComment(id, content) {
+ const r = await request('POST', `/opc/content/${id}/comments`, { content }, true);
+ return r?.comment || null;
+}