Files
training/miniprogram/src/pages/content-detail/index.jsx
T

125 lines
6.4 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { View, Text, Image, Video, ScrollView, Input, Button, RichText } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
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 <View className="cd page-in"><Text className="cd-err">加载中</Text></View>;
if (err || !item) return <View className="cd page-in"><Text className="cd-err">{err || '内容不存在'}</Text></View>;
const id = item.id;
const img = item.cover || (item.video ? item.video_cover : '');
const isSmall = item.card_mode === 'small'; // 小图模式:详情页不显示封面
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 (
<View className="cd page-in">
{/* 封面/视频(小图模式不显示封面) */}
{!isSmall && (item.video
? <Video className="cd-video" src={item.video} poster={item.video_cover || item.cover || ''} controls />
: img
? <Image className="cd-cover" src={img} mode="widthFix" />
: null)}
{/* 标题 + 元信息 */}
<Text className="cd-title">{item.title}</Text>
<View className="cd-meta-row">
{item.publisher_avatar ? <Image className="cd-ava" src={item.publisher_avatar} mode="aspectFill" /> : <View className="cd-ava" />}
<Text className="cd-author">{author}</Text><View className="ic ic-check cd-badge-ic" />
</View>
<View className="cd-meta-line">
<Text className="cd-meta-at">{rw(item.created_at)}</Text>
<Text className="cd-dot">·</Text>
<Text className="cd-meta-at">阅读 {views}</Text>
</View>
{/* 正文 */}
{item.summary ? <Text className="cd-summary">{item.summary}</Text> : null}
{item.body
? <RichText className="cd-body" nodes={item.body} />
: <Text className="cd-body">暂无正文</Text>}
{images.length > 0 && (
<ScrollView className="cd-gallery" scrollX>
{images.map((u, i) => <Image key={i} className="cd-gal-img" src={u} mode="aspectFill" />)}
</ScrollView>
)}
{item.link ? (
<Button className="btn btn-ghost cd-link" type="default" onClick={() => Taro.setClipboardData({ data: item.link })}>原文链接点击复制</Button>
) : null}
{/* 作者条 + 动作 */}
<View className="cd-bar">
<View className="cd-bar-left">
{item.publisher_avatar ? <Image className="cd-ava2" src={item.publisher_avatar} mode="aspectFill" /> : <View className="cd-ava2" />}
<Text className="cd-author">{author}</Text><View className="ic ic-check cd-badge-ic" />
</View>
<View className="cd-bar-actions">
<View className="cd-act" onClick={() => setLike((v) => !v)}><View className={`ic ic-thumb cd-act-ic${like ? ' on' : ''}`} /><Text className="cd-act-n">{like ? 1 : 0}</Text></View>
<View className="cd-act" onClick={copyLink}><View className="ic ic-share cd-act-ic" /><Text className="cd-act-n">转发</Text></View>
<View className="cd-act" onClick={() => setFav((v) => !v)}><View className={`ic ic-heart cd-act-ic${fav ? ' on' : ''}`} /></View>
<View className="cd-act" onClick={scrollToComment}><View className="ic ic-cmt cd-act-ic" /><Text className="cd-act-n">留言</Text></View>
</View>
</View>
{/* 阅读 + 留言 */}
<View className="cd-read">阅读 {views}</View>
<View className="cd-comments">
<Text className="cd-cmt-title">留言</Text>
<View className="cd-cmt-input">
<Input className="cd-cmt-field" placeholder="写留言…" value={draft} onInput={(e) => setDraft(e.detail.value)} />
<View className="cd-cmt-tools"><View className="ic ic-cmt" /></View>
<Button className="btn btn-sm cd-cmt-send" disabled={sending || !draft.trim()} onClick={send}>发送</Button>
</View>
{comments.length === 0
? <Text className="cd-cmt-empty">还没有留言来写首评</Text>
: comments.map((c) => (
<View key={c.id} className="cd-cmt">
<View className="cd-cmt-head">
{c.avatar ? <Image className="cd-ava" src={c.avatar} mode="aspectFill" /> : <View className="cd-ava" />}
<View className="cd-cmt-meta"><Text className="cd-cmt-name">{c.nickname || c.username || '用户'}</Text><Text className="cd-cmt-time">{rw(c.created_at)}</Text></View>
</View>
<Text className="cd-cmt-text">{c.content}</Text>
</View>
))}
</View>
</View>
);
}