Files
training/miniprogram/src/pages/hall/index.jsx
T
Pine b4d4ac4258 feat(mini): 全局沉浸式导航统一改造
1. 大厅改为资讯页固定模式:flex+ScrollView 独立滚动,两级tab位于滚动区之外完全固定不被下拉;tab样式统一为药丸渐变
2. 资讯页tab上间距优化(4rpx→18rpx)
3. 所有页面标题区统一三段式:英文分类标签(no) + 中文标题(title) + 中文副标题(sub),移除所有英文注脚en
4. 所有二级页面(pages-extra)加 back 返回按钮
5. 补齐14个缺失页面的 navigationStyle:custom 和 PageHeader:
   cert-apply/content-detail/event-detail/my-cert/my-park/my-tasks/
   park-apply/park-transfer/pay/pay-qr/plan/scan-login/scan/tasks
6. 大厅PageHeader副标题改为中文描述
2026-09-01 22:14:19 +08:00

267 lines
12 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, Button, ScrollView } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useState, useEffect, useCallback } from 'react';
import { hallOverview, hallTasks, hallTalents, hallJobs, hallServices } from '@/utils/hall';
import { PageHeader } from '@/components/TopInset';
import Skeleton from '@/components/skeleton';
import { isAuthed } from '@/utils/auth';
import '@/styles/hall.scss';
/* 大厅(Tab 容器):任务 / 人才 / 岗位 / 服务 / 社区 页内切换;详情跳子页
* 原生导航栏关闭(navigationStyle=custom),两级 Tab(一级 TABS + 二级筛选 chips
* 吸顶悬浮在顶部,内容滚动时不被带走。 */
const TABS = [
{ k: 'tasks', l: '任务' },
{ k: 'talents', l: '人才' },
{ k: 'jobs', l: '岗位' },
{ k: 'services', l: '服务' },
{ k: 'community', l: '社区' },
];
const CHIPS = {
tasks: [
{ k: '', l: '全部' }, { k: 'grab', l: '抢单' }, { k: 'bid', l: '投标' },
{ k: 'register', l: '报名' }, { k: 'assign', l: '指派' },
],
talents: [
{ k: '', l: '全部' }, { k: 'opc', l: 'OPC' }, { k: 'seeker', l: '求职者' },
],
jobs: [
{ k: '', l: '全部' }, { k: 'fulltime', l: '全职' }, { k: 'parttime', l: '兼职' },
{ k: 'intern', l: '实习' }, { k: 'remote', l: '远程' },
],
community: [
{ k: '', l: '全部' }, { k: '交流', l: '交流' }, { k: '求助', l: '求助' },
{ k: '资源', l: '资源' }, { k: '经验', l: '经验' }, { k: '公告', l: '公告' },
],
};
const MODE_LABEL = { grab: '抢单', bid: '投标', register: '报名', assign: '指派', recommend: '推荐' };
const WT = { fulltime: '全职', parttime: '兼职', intern: '实习', remote: '远程' };
const money = (a, b) => (!a && !b ? '面议' : a && b && a !== b ? `¥${a}${b}` : `¥${a || b}`);
const salary = (a, b) => (!a && !b ? '面议' : a && b && a !== b ? `${a}${b}元/月` : `${a || b}元/月`);
const PUBLISH_URL = {
tasks: '/pages-extra/hall-task-publish/index',
services: '/pages-extra/hall-service-publish/index',
community: '/pages-extra/community-post/index',
};
export default function Hall() {
const [tab, setTab] = useState('tasks');
const [stat, setStat] = useState(null);
const [mode, setMode] = useState(''); // 任务筛选
const [ttype, setTtype] = useState(''); // 人才筛选
const [wt, setWt] = useState(''); // 岗位筛选
const [topic, setTopic] = useState(''); // 社区筛选
useEffect(() => {
hallOverview().then(setStat).catch(() => {});
}, []);
const goPublish = () => {
if (!isAuthed()) { Taro.showToast({ title: '请先登录', icon: 'none' }); return; }
const url = PUBLISH_URL[tab];
if (!url) return;
Taro.navigateTo({ url });
};
// 当前二级筛选
const cur = CHIPS[tab] || null;
const chipVal = tab === 'tasks' ? mode : tab === 'talents' ? ttype : tab === 'jobs' ? wt : topic;
const chipSet = tab === 'tasks' ? setMode : tab === 'talents' ? setTtype : tab === 'jobs' ? setWt : setTopic;
return (
<View className="hl page-in">
<PageHeader no="HALL" title="大厅" sub="任务 · 人才 · 岗位 · 服务 · 社区" />
{/* 两级 Tab:位于滚动区之外,完全固定不被下拉 */}
<View className="hl-tabbar">
<View className="hl-tabs">
{TABS.map((t) => (
<Text key={t.k} className={`hl-tab${tab === t.k ? ' on' : ''}`} onClick={() => setTab(t.k)}>{t.l}</Text>
))}
<View className="hl-tab-spacer" />
{PUBLISH_URL[tab] && (
<Text className="hl-pub" onClick={goPublish}>+ 发布</Text>
)}
</View>
{cur && (
<View className="hl-chips">
{cur.map((c) => (
<Text key={c.k} className={`hl-chip${chipVal === c.k ? ' on' : ''}`} onClick={() => chipSet(c.k)}>{c.l}</Text>
))}
</View>
)}
</View>
{/* 内容区独立滚动 */}
<ScrollView className="hl-scroll" scrollY enhanced showScrollbar={false}>
{tab === 'tasks' && <TasksPane mode={mode} onMode={setMode} />}
{tab === 'talents' && <TalentsPane ttype={ttype} onTtype={setTtype} />}
{tab === 'jobs' && <JobsPane wt={wt} onWt={setWt} />}
{tab === 'services' && <ServicesPane />}
{tab === 'community' && <CommunityPane topic={topic} onTopic={setTopic} />}
<View className="hl-scroll-pad" />
</ScrollView>
</View>
);
}
/* ---------- 任务 ---------- */
function TasksPane({ mode, onMode }) {
const [items, setItems] = useState(null);
const [err, setErr] = useState('');
const load = useCallback(() => {
hallTasks({ mode }).then((d) => setItems(d.items || [])).catch((e) => setErr(e.message));
}, [mode]);
useEffect(load, [load]);
return (
<>
{err && <Text className="hl-err">{err}</Text>}
{!items && !err && <Skeleton />}
{items && items.length === 0 && <Text className="hl-empty">暂无任务</Text>}
<View className="hl-cards">
{(items || []).map((t) => (
<View key={t.id} className="hl-card" onClick={() => Taro.navigateTo({ url: `/pages-extra/task-detail/index?id=${t.id}` })}>
<View className="hl-tags">
<Text className="hl-tag">{t.category || '综合'}</Text>
<Text className={`hl-tag ${t.mode}`}>{MODE_LABEL[t.mode] || t.mode}</Text>
<Text className="hl-tag price">{money(t.budget_min, t.budget_max)}</Text>
{t.can_accept === true && <Text className="hl-tag grab"> 可接</Text>}
</View>
<Text className="hl-name">{t.title}</Text>
<Text className="hl-desc">{t.description}</Text>
<View className="hl-foot">
<Text>{t.publisher_name || '平台任务'}</Text>
<Text>{t.deadline ? `截止 ${t.deadline}` : '长期'}</Text>
</View>
</View>
))}
</View>
</>
);
}
/* ---------- 人才 ---------- */
function TalentsPane({ ttype, onTtype }) {
const [items, setItems] = useState(null);
const [err, setErr] = useState('');
useEffect(() => {
hallTalents({ type: ttype }).then((d) => setItems(d.items || [])).catch((e) => setErr(e.message));
}, [ttype]);
return (
<>
{err && <Text className="hl-err">{err}</Text>}
{!items && !err && <Skeleton />}
{items && items.length === 0 && <Text className="hl-empty">暂无人才档案</Text>}
<View className="hl-cards">
{(items || []).map((t) => (
<View key={t.userId} className="hl-card" onClick={() => Taro.navigateTo({ url: `/pages-extra/hall-talent-detail/index?id=${t.userId}` })}>
<View className="hl-talent">
<View className="hl-avatar"><Text>{(t.displayName || '·').slice(0, 1)}</Text></View>
<View style={{ flex: 1, minWidth: 0 }}>
<Text className="hl-name" style={{ marginTop: 0 }}>{t.displayName} · {t.talentType === 'seeker' ? '求职者' : 'OPC'}</Text>
<Text className="hl-desc">{t.headline}</Text>
<View className="hl-tags" style={{ marginTop: 10 }}>
{(t.fields || []).map((x) => <Text className="hl-tag" key={x}>{x}</Text>)}
{(t.skills || []).slice(0, 4).map((x) => <Text className="hl-tag" key={x}>{x}</Text>)}
</View>
</View>
</View>
</View>
))}
</View>
</>
);
}
/* ---------- 岗位 ---------- */
function JobsPane({ wt, onWt }) {
const [items, setItems] = useState(null);
const [err, setErr] = useState('');
useEffect(() => {
hallJobs({ work_type: wt }).then((d) => setItems(d.items || [])).catch((e) => setErr(e.message));
}, [wt]);
return (
<>
{err && <Text className="hl-err">{err}</Text>}
{!items && !err && <Skeleton />}
{items && items.length === 0 && <Text className="hl-empty">暂无招聘岗位</Text>}
<View className="hl-cards">
{(items || []).map((j) => (
<View key={j.id} className="hl-card" onClick={() => Taro.navigateTo({ url: `/pages-extra/hall-job-detail/index?id=${j.id}` })}>
<View className="hl-tags">
<Text className="hl-tag">{j.category || '综合'}</Text>
<Text className="hl-tag">{WT[j.workType] || j.workType}</Text>
<Text className="hl-tag price">{salary(j.salaryMin, j.salaryMax)}</Text>
</View>
<Text className="hl-name">{j.title}</Text>
<Text className="hl-desc">{j.description}</Text>
<View className="hl-foot"><Text>{j.companyName || '企业'}</Text><Text>{j.location || '云南'}</Text></View>
</View>
))}
</View>
</>
);
}
/* ---------- 服务 ---------- */
function ServicesPane() {
const [items, setItems] = useState(null);
const [err, setErr] = useState('');
useEffect(() => {
hallServices().then((d) => setItems(d.items || [])).catch((e) => setErr(e.message));
}, []);
return (
<>
{err && <Text className="hl-err">{err}</Text>}
{!items && !err && <Skeleton />}
{items && items.length === 0 && <Text className="hl-empty">暂无服务</Text>}
<View className="hl-cards">
{(items || []).map((s) => (
<View key={s.id} className="hl-card" onClick={() => Taro.navigateTo({ url: `/pages-extra/hall-service-detail/index?id=${s.id}` })}>
<View className="hl-tags">
<Text className="hl-tag">{s.category || '综合'}</Text>
<Text className="hl-tag price">{s.price ? `¥${s.price}` : '面议'}</Text>
{s.deliveryDays > 0 && <Text className="hl-tag">{s.deliveryDays} 天交付</Text>}
</View>
<Text className="hl-name">{s.title}</Text>
<Text className="hl-desc">{s.description}</Text>
<View className="hl-foot"><Text>{s.opcName}</Text><Text>{s.orderCount ? `${s.orderCount}` : `评分 ${s.rating || 5}`}</Text></View>
</View>
))}
</View>
</>
);
}
/* ---------- 社区(列表页内切换;帖子详情跳子页) ---------- */
function CommunityPane({ topic, onTopic }) {
const [posts, setPosts] = useState(null);
const load = useCallback(() => {
import('@/utils/hall').then(({ communityPosts }) =>
communityPosts({ topic: topic === '' ? '' : topic }).then((d) => setPosts(d.items || [])).catch(() => {}));
}, [topic]);
useEffect(load, [load]);
return (
<>
{!posts && <Skeleton />}
{posts && posts.length === 0 && <Text className="hl-empty">还没有帖子来发第一帖</Text>}
{(posts || []).map((p) => (
<View key={p.id} className="hl-post" onClick={() => Taro.navigateTo({ url: `/pages-extra/community-post-detail/index?id=${p.id}` })}>
<View className="hl-post-head">
<View className="hl-avatar" style={{ width: 64, height: 64, fontSize: 26 }}><Text>{(p.authorName || '·').slice(0, 1)}</Text></View>
<View>
<Text className="hl-pauthor">{p.authorName}</Text>
<Text className="hl-pmeta">{p.topic}{p.pinned ? ' · 置顶' : ''} · {(p.createdAt || '').slice(0, 10)}</Text>
</View>
</View>
<Text className="hl-ptitle">{p.title}</Text>
<Text className="hl-pcontent">{p.content}</Text>
<View className="hl-pfoot"><Text> {p.likeCount || 0}</Text><Text>评论 {p.commentCount || 0}</Text></View>
</View>
))}
<View style={{ display: 'flex', gap: 18, margin: '10rpx 24rpx 30rpx' }}>
<Button className="hl-btn ghost" onClick={() => Taro.navigateTo({ url: '/pages-extra/community-post/index' })}>发帖</Button>
<Button className="hl-btn ghost" onClick={() => Taro.navigateTo({ url: '/pages-extra/parks/index' })}>实体社区 · 载体</Button>
</View>
</>
);
}