diff --git a/miniprogram/src/app.config.js b/miniprogram/src/app.config.js index 9af9480..c477e5a 100644 --- a/miniprogram/src/app.config.js +++ b/miniprogram/src/app.config.js @@ -12,6 +12,8 @@ export default { 'pages/park-apply/index', 'pages/cert-apply/index', 'pages/park-transfer/index', + 'pages/content/index', + 'pages/content-detail/index', 'pages/scan-login/index' ], window: { @@ -31,8 +33,7 @@ export default { { pagePath: 'pages/booking/index', text: '活动' }, { pagePath: 'pages/test/index', text: '测评' }, { pagePath: 'pages/index/index', text: '首页' }, - { pagePath: 'pages/policy/index', text: '政策' }, - // { pagePath: 'pages/plan/index', text: '流程' }, + { pagePath: 'pages/content/index', text: '资讯' }, { pagePath: 'pages/mine/index', text: '我的' } ] } diff --git a/miniprogram/src/custom-tab-bar/index.jsx b/miniprogram/src/custom-tab-bar/index.jsx index 81bd9ac..9109ef0 100644 --- a/miniprogram/src/custom-tab-bar/index.jsx +++ b/miniprogram/src/custom-tab-bar/index.jsx @@ -8,7 +8,7 @@ const LIST = [ { pagePath: '/pages/booking/index', text: '活动', icon: 'book' }, { pagePath: '/pages/test/index', text: '测评', icon: 'compass' }, { pagePath: '/pages/index/index', text: 'OPC', icon: 'star', center: true }, - { pagePath: '/pages/policy/index', text: '政策', icon: 'shield' }, + { pagePath: '/pages/content/index', text: '资讯', icon: 'shield' }, { pagePath: '/pages/mine/index', text: '我的', icon: 'user' } ]; diff --git a/miniprogram/src/pages/content-detail/index.config.js b/miniprogram/src/pages/content-detail/index.config.js new file mode 100644 index 0000000..137fffe --- /dev/null +++ b/miniprogram/src/pages/content-detail/index.config.js @@ -0,0 +1 @@ +export default { navigationBarTitleText: '资讯详情' }; diff --git a/miniprogram/src/pages/content-detail/index.jsx b/miniprogram/src/pages/content-detail/index.jsx new file mode 100644 index 0000000..dd71f4e --- /dev/null +++ b/miniprogram/src/pages/content-detail/index.jsx @@ -0,0 +1,35 @@ +import { View, Text } from '@tarojs/components'; +import Taro from '@tarojs/taro'; +import { useState, useEffect } from 'react'; +import { getContent } from '@/utils/content'; +import './index.scss'; + +/** 资讯详情:展示单条 title/summary/body。 */ +export default function ContentDetail() { + const [item, setItem] = useState(null); + const [err, setErr] = useState(''); + const [loaded, setLoaded] = 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)); + }, []); + + if (!loaded) return 加载中…; + if (err || !item) return {err || '内容不存在'}; + + return ( + + {item.title} + 发布于 {fmt(item.created_at)} + {item.summary ? {item.summary} : null} + {item.body || '暂无正文'} + + ); +} + +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 new file mode 100644 index 0000000..8ede336 --- /dev/null +++ b/miniprogram/src/pages/content-detail/index.scss @@ -0,0 +1,7 @@ +/* 资讯详情 · 小程序样式(暗色令牌一致) */ +.cd { position: relative; z-index: 2; padding: 32rpx 28rpx 60rpx; } +.cd-title { display: block; font-size: 36rpx; color: #fff; font-weight: 700; line-height: 1.4; } +.cd-meta { display: block; margin-top: 14rpx; font-size: 23rpx; color: #7c7c80; } +.cd-summary { display: block; margin-top: 20rpx; font-size: 27rpx; color: #c4c2c3; line-height: 1.7; } +.cd-body { display: block; margin-top: 24rpx; font-size: 28rpx; color: #e4e4e6; line-height: 1.8; white-space: pre-wrap; } +.cd-err { display: block; padding: 90rpx 0; text-align: center; font-size: 26rpx; color: #8e8e8e; } diff --git a/miniprogram/src/pages/content/index.config.js b/miniprogram/src/pages/content/index.config.js new file mode 100644 index 0000000..6b011a7 --- /dev/null +++ b/miniprogram/src/pages/content/index.config.js @@ -0,0 +1 @@ +export default { navigationBarTitleText: '资讯中心' }; diff --git a/miniprogram/src/pages/content/index.jsx b/miniprogram/src/pages/content/index.jsx new file mode 100644 index 0000000..ebc0e84 --- /dev/null +++ b/miniprogram/src/pages/content/index.jsx @@ -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 ( + + {/* 类别分段 */} + + {CONTENT_CATS.map((c) => ( + setCat(c.key)}>{c.label} + ))} + + + {/* 政策测评入口(非 tab,政策分类下提供) */} + {cat === 'policy' && ( + + 政策测评 + 测一测 → + + )} + + {err ? {err} : null} + {loaded ? ( + items.length === 0 + ? 暂无{CONTENT_CATS.find((c) => c.key === cat)?.label || ''}内容 + : items.map((it) => ( + go(it.id)}> + {it.title} + {it.summary ? {it.summary} : null} + {fmt(it.created_at)} + + )) + ) : ( + + )} + + ); +} + +function fmt(s) { + return (s || '').replace('T', ' ').replace('Z', '').slice(0, 16); +} diff --git a/miniprogram/src/pages/content/index.scss b/miniprogram/src/pages/content/index.scss new file mode 100644 index 0000000..4e56820 --- /dev/null +++ b/miniprogram/src/pages/content/index.scss @@ -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; } diff --git a/miniprogram/src/utils/content.js b/miniprogram/src/utils/content.js new file mode 100644 index 0000000..67b508c --- /dev/null +++ b/miniprogram/src/utils/content.js @@ -0,0 +1,26 @@ +import { request, cachedRequest } from './api'; + +/** 资讯中心类别 */ +export const CONTENT_CATS = [ + { key: 'policy', label: '政策' }, + { key: 'news', label: '资讯' }, + { key: 'skill', label: '技能' }, + { key: 'dynamic', label: '动态' }, +]; + +/** 获取某类别已发布资讯(公开浏览) */ +export async function listContent(type) { + const r = await request('GET', `/opc/content?type=${type}`, {}, false); + return Array.isArray(r?.items) ? r.items : []; +} + +/** 带短缓存的资讯列表(切换分类时避免跳变) */ +export function cachedContent(type) { + return cachedRequest('GET', `/opc/content?type=${type}`, 30000, {}, false); +} + +/** 资讯详情 */ +export async function getContent(id) { + const r = await request('GET', `/opc/content/${id}`, {}, false); + return r || null; +}