Compare commits
2 Commits
35e87889db
...
05d19aed0f
| Author | SHA1 | Date | |
|---|---|---|---|
| 05d19aed0f | |||
| bf3ffeb4ef |
@@ -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: '我的' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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' }
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '资讯详情' };
|
||||
@@ -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 <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>;
|
||||
|
||||
return (
|
||||
<View className="cd page-in">
|
||||
<Text className="cd-title">{item.title}</Text>
|
||||
<Text className="cd-meta">发布于 {fmt(item.created_at)}</Text>
|
||||
{item.summary ? <Text className="cd-summary">{item.summary}</Text> : null}
|
||||
<Text className="cd-body">{item.body || '暂无正文'}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function fmt(s) {
|
||||
return (s || '').replace('T', ' ').replace('Z', '').slice(0, 16);
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -0,0 +1 @@
|
||||
export default { navigationBarTitleText: '资讯中心' };
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { isAuthed } from './services/auth';
|
||||
import ParkApply from './user/ParkApply';
|
||||
import CertApply from './user/CertApply';
|
||||
import ParkTransfer from './user/ParkTransfer';
|
||||
import Content from './user/Content';
|
||||
|
||||
/* 公开路由(无需登录) */
|
||||
const PUBLIC_ROUTES = [
|
||||
@@ -41,6 +42,7 @@ const PUBLIC_ROUTES = [
|
||||
{ path: 'park-apply', Page: ParkApply },
|
||||
{ path: 'cert-apply', Page: CertApply },
|
||||
{ path: 'park-transfer', Page: ParkTransfer },
|
||||
{ path: 'content', Page: Content },
|
||||
{ path: 'profile', Page: Profile }
|
||||
];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
export const NAV = [
|
||||
{ path: '', label: '首页' },
|
||||
{ path: 'events', label: '活动' },
|
||||
{ path: 'content', label: '资讯' },
|
||||
{ path: 'opc-test', label: '测评' }
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/** C 端资讯(公开浏览 /opc/content) */
|
||||
const API_BASE = 'https://opc.pinesound.cn';
|
||||
|
||||
export const CONTENT_CATS = [
|
||||
{ key: 'policy', label: '政策' },
|
||||
{ key: 'news', label: '资讯' },
|
||||
{ key: 'skill', label: '技能' },
|
||||
{ key: 'dynamic', label: '动态' },
|
||||
];
|
||||
|
||||
export async function listContent(type) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/opc/content?type=${type}`);
|
||||
const d = await res.json().catch(() => ({}));
|
||||
return Array.isArray(d.items) ? d.items : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { ContentTemplate } from '@/components/templates';
|
||||
import { Button } from '@/components/atoms';
|
||||
import { Reveal } from '@/components/organisms';
|
||||
import { listContent, CONTENT_CATS } from '@/services/content';
|
||||
import '@/styles/booking.css';
|
||||
|
||||
/** 资讯中心(C端):按类别(政策/资讯/技能/动态)展示 admin 发布的资讯,点击展开正文。 */
|
||||
export default function Content() {
|
||||
const [cat, setCat] = React.useState('policy');
|
||||
const [items, setItems] = React.useState([]);
|
||||
const [openId, setOpenId] = React.useState(null);
|
||||
const [loaded, setLoaded] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoaded(false);
|
||||
listContent(cat).then((l) => { setItems(l); setLoaded(true); });
|
||||
}, [cat]);
|
||||
|
||||
return (
|
||||
<ContentTemplate active="content" kicker="News" title="资讯中心" desc="政策 / 资讯 / 技能 / 动态(平台发布)">
|
||||
{/* 类别分段 */}
|
||||
<section className="section">
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{CONTENT_CATS.map((c) => (
|
||||
<Button key={c.key} variant={cat === c.key ? 'cta' : 'white'} onClick={() => { setCat(c.key); setOpenId(null); }}>{c.label}</Button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section" style={{ marginTop: 8 }}>
|
||||
{!loaded ? <p className="pf-muted">加载中…</p> : items.length === 0
|
||||
? <p className="pf-muted">暂无{CONTENT_CATS.find((c) => c.key === cat)?.label}内容</p>
|
||||
: items.map((it) => (
|
||||
<Reveal key={it.id} as="article" className="bk-ev-card" style={{ marginBottom: 10, padding: '16px 18px' }}>
|
||||
<div style={{ cursor: 'pointer' }} onClick={() => setOpenId(openId === it.id ? null : it.id)}>
|
||||
<h3 style={{ margin: 0, fontSize: 16 }}>{it.title}</h3>
|
||||
{it.summary && <p className="pf-muted" style={{ margin: '6px 0 0' }}>{it.summary}</p>}
|
||||
<p className="pf-muted" style={{ margin: '6px 0 0' }}>{fmt(it.created_at)}</p>
|
||||
</div>
|
||||
{openId === it.id && <p className="pf-body" style={{ whiteSpace: 'pre-wrap', marginTop: 10, lineHeight: 1.8 }}>{it.body || '暂无正文'}</p>}
|
||||
</Reveal>
|
||||
))}
|
||||
</section>
|
||||
</ContentTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
function fmt(s) {
|
||||
return (s || '').replace('T', ' ').replace('Z', '').slice(0, 16);
|
||||
}
|
||||
@@ -188,6 +188,21 @@ export default function Home() {
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* 2.8 · 资讯中心 */}
|
||||
<Reveal as="section" className="section mk-section">
|
||||
<SectionHead no="00" title="资讯中心" en="News" />
|
||||
<div className="mk-assess">
|
||||
<div className="assess-txt">
|
||||
<span className="assess-badge">资讯</span>
|
||||
<span className="assess-title">政策 / 资讯 / 技能 / 动态</span>
|
||||
<span className="assess-desc">平台发布的政策、行业资讯、技能干货与园区动态,分类浏览。</span>
|
||||
</div>
|
||||
<div className="assess-cta">
|
||||
<Button variant="cta" href="#/content">去浏览 →</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
{/* 3 · 痛点理念 */}
|
||||
<Reveal as="section" className="section mk-section">
|
||||
<SectionHead no="01" title={MK_PRINCIPLE.title} en={MK_PRINCIPLE.en} />
|
||||
|
||||
Reference in New Issue
Block a user