feat(content): 小程序「政策」tab→资讯中心(分类feed)

- 新增 pages/content(资讯中心):类别分段tab(政策/资讯/技能/动态) + published列表;政策分类下提供「政策测评」入口(原测评页保留非tab)
- 新增 pages/content-detail(资讯详情:title/summary/body)
- utils/content.js:listContent(cat)/cachedContent/getContent(公开 /opc/content)
- app.config:tab 政策→资讯(pages/content)、注册 content/content-detail、policy 保留注册
- custom-tab-bar:政策→资讯

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-27 14:08:47 +08:00
parent 35e87889db
commit bf3ffeb4ef
9 changed files with 164 additions and 3 deletions
@@ -0,0 +1 @@
export default { navigationBarTitleText: '资讯中心' };
+65
View File
@@ -0,0 +1,65 @@
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect } from 'react';
import { listContent, CONTENT_CATS } from '@/utils/content';
import { useTabIndex } from '@/utils/tab';
import Skeleton from '@/components/skeleton';
import './index.scss';
/** 资讯中心(C 端 tab):按类别(政策/资讯/技能/动态)展示 admin 发布的资讯。 */
export default function ContentIndex() {
useTabIndex(3);
const [cat, setCat] = useState('policy');
const [items, setItems] = useState([]);
const [err, setErr] = useState('');
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(false); setErr('');
listContent(cat)
.then(setItems)
.catch(() => setErr('加载失败,请稍后重试'))
.finally(() => setLoaded(true));
}, [cat]);
const go = (id) => Taro.navigateTo({ url: `/pages/content-detail/index?id=${id}` });
const goQuiz = () => Taro.navigateTo({ url: '/pages/policy/index' });
return (
<View className="ct page-in">
{/* 类别分段 */}
<View className="ct-cats">
{CONTENT_CATS.map((c) => (
<View key={c.key} className={`ct-cat${cat === c.key ? ' on' : ''}`} onClick={() => setCat(c.key)}>{c.label}</View>
))}
</View>
{/* 政策测评入口(非 tab,政策分类下提供) */}
{cat === 'policy' && (
<View className="ct-quiz" onClick={goQuiz}>
<Text className="ct-quiz-l">政策测评</Text>
<Text className="ct-quiz-d">测一测 </Text>
</View>
)}
{err ? <Text className="ct-err">{err}</Text> : null}
{loaded ? (
items.length === 0
? <View className="ct-empty"><Text>暂无{CONTENT_CATS.find((c) => c.key === cat)?.label || ''}内容</Text></View>
: items.map((it) => (
<View key={it.id} className="ct-item" onClick={() => go(it.id)}>
<Text className="ct-item-title">{it.title}</Text>
{it.summary ? <Text className="ct-item-summary">{it.summary}</Text> : null}
<Text className="ct-item-time">{fmt(it.created_at)}</Text>
</View>
))
) : (
<Skeleton />
)}
</View>
);
}
function fmt(s) {
return (s || '').replace('T', ' ').replace('Z', '').slice(0, 16);
}
+25
View File
@@ -0,0 +1,25 @@
/* 资讯中心 · 小程序样式(暗色令牌一致) */
.ct { position: relative; z-index: 2; padding: 16rpx 24rpx 60rpx; }
.ct-cats { display: flex; gap: 10rpx; padding: 8rpx 0 16rpx; }
.ct-cat {
flex: 1; text-align: center; padding: 14rpx 0; border-radius: 12rpx;
background: #17171b; border: 1rpx solid #2a2a2d; color: #9b9b9b; font-size: 27rpx;
}
.ct-cat.on { background: #fff; color: #111; border-color: #fff; font-weight: 600; }
.ct-quiz {
display: flex; align-items: center; justify-content: space-between;
padding: 18rpx 22rpx; margin-bottom: 8rpx; border-radius: 14rpx;
background: linear-gradient(180deg, #14141a, #0e0e12); border: 1rpx solid rgba(255,255,255,.12);
}
.ct-quiz-l { font-size: 27rpx; color: #fff; font-weight: 600; }
.ct-quiz-d { font-size: 24rpx; color: #ffd28a; }
.ct-item { padding: 22rpx 4rpx; border-bottom: 1rpx solid rgba(255,255,255,.08); }
.ct-item-title { display: block; font-size: 29rpx; color: #fff; font-weight: 600; line-height: 1.5; }
.ct-item-summary { display: block; margin-top: 8rpx; font-size: 25rpx; color: #a9a9ac; line-height: 1.6; }
.ct-item-time { display: block; margin-top: 10rpx; font-size: 22rpx; color: #7c7c80; }
.ct-empty { padding: 80rpx 0; text-align: center; font-size: 26rpx; color: #8e8e8e; }
.ct-err { display: block; padding: 60rpx 0; text-align: center; font-size: 26rpx; color: #f0b8b8; }