b4d4ac4258
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副标题改为中文描述
267 lines
12 KiB
React
267 lines
12 KiB
React
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>
|
||
</>
|
||
);
|
||
}
|