From eeaf9526e5cce6b56e34666c8aea6f9875dadc51 Mon Sep 17 00:00:00 2001 From: Pine Date: Tue, 1 Sep 2026 20:32:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(mini):=20=E5=A4=A7=E5=8E=85=E6=B2=89?= =?UTF-8?q?=E6=B5=B8=E5=BC=8F=E2=80=94=E2=80=94=E5=8E=BB=E9=99=A4=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=AF=BC=E8=88=AA=E6=A0=8F=EF=BC=8C=E4=B8=A4=E7=BA=A7?= =?UTF-8?q?=20Tab=EF=BC=88=E4=B8=80=E7=BA=A7TABS+=E4=BA=8C=E7=BA=A7?= =?UTF-8?q?=E7=AD=9B=E9=80=89=EF=BC=89sticky=20=E5=90=B8=E9=A1=B6=E6=82=AC?= =?UTF-8?q?=E6=B5=AE=E4=B8=8D=E9=9A=8F=E5=86=85=E5=AE=B9=E6=BB=9A=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hall 页新增 index.config.js:navigationStyle=custom(与其他 tabBar 页一致,去掉原生状态栏/双标题) - 二级筛选 chips 从各 pane 提升到 Hall 顶层,与一级 TABS 连续吸顶 - hl-tabs/hl-chips 加 position:sticky + 毛玻璃背景,top 由 getTopInset() navH + 实测 tab 高度动态赋值 - 服务 tab 无筛选不渲染 chips --- miniprogram/src/pages/hall/index.config.js | 1 + miniprogram/src/pages/hall/index.jsx | 104 +++++++++++++-------- miniprogram/src/styles/hall.scss | 9 +- 3 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 miniprogram/src/pages/hall/index.config.js diff --git a/miniprogram/src/pages/hall/index.config.js b/miniprogram/src/pages/hall/index.config.js new file mode 100644 index 0000000..4f510b0 --- /dev/null +++ b/miniprogram/src/pages/hall/index.config.js @@ -0,0 +1 @@ +export default { navigationBarTitleText: '大厅', navigationStyle: 'custom' }; diff --git a/miniprogram/src/pages/hall/index.jsx b/miniprogram/src/pages/hall/index.jsx index 7272f5d..84e21b6 100644 --- a/miniprogram/src/pages/hall/index.jsx +++ b/miniprogram/src/pages/hall/index.jsx @@ -2,12 +2,14 @@ import { View, Text, Button } 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 { PageHeader, getTopInset } from '@/components/TopInset'; import Skeleton from '@/components/skeleton'; import { isAuthed } from '@/utils/auth'; import '@/styles/hall.scss'; -/* 大厅(Tab 容器):任务 / 人才 / 岗位 / 服务 / 社区 页内切换;详情跳子页 */ +/* 大厅(Tab 容器):任务 / 人才 / 岗位 / 服务 / 社区 页内切换;详情跳子页 + * 原生导航栏关闭(navigationStyle=custom),两级 Tab(一级 TABS + 二级筛选 chips) + * 吸顶悬浮在顶部,内容滚动时不被带走。 */ const TABS = [ { k: 'tasks', l: '任务' }, { k: 'talents', l: '人才' }, @@ -15,6 +17,23 @@ const TABS = [ { 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}`); @@ -29,33 +48,63 @@ const PUBLISH_URL = { export default function Hall() { const [tab, setTab] = useState('tasks'); const [stat, setStat] = useState(null); - useEffect(() => { hallOverview().then(setStat).catch(() => {}); }, []); + const [mode, setMode] = useState(''); // 任务筛选 + const [ttype, setTtype] = useState(''); // 人才筛选 + const [wt, setWt] = useState(''); // 岗位筛选 + const [topic, setTopic] = useState(''); // 社区筛选 + const [tabBarH, setTabBarH] = useState(0); // 一级 tab 实测高度(吸顶基准) + const { navH } = getTopInset(); + + useEffect(() => { + hallOverview().then(setStat).catch(() => {}); + Taro.nextTick(() => { + Taro.createSelectorQuery() + .select('.hl-tabs') + .boundingClientRect((r) => { if (r && r.height) setTabBarH(r.height); }) + .exec(); + }); + }, []); + const goPublish = () => { if (!isAuthed()) { Taro.showToast({ title: '请先登录', icon: 'none' }); return; } Taro.navigateTo({ url: PUBLISH_URL[tab] || PUBLISH_URL.tasks }); }; + + // 当前二级筛选 + 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 ( - + {/* 一级 Tab:吸顶在自定义标题下沿 */} + {TABS.map((t) => ( setTab(t.k)}>{t.l} ))} + 发布 - {tab === 'tasks' && } - {tab === 'talents' && } - {tab === 'jobs' && } + {/* 二级筛选:吸顶在一级 Tab 下沿(无筛选的 tab 不渲染) */} + {cur && ( + + {cur.map((c) => ( + chipSet(c.k)}>{c.l} + ))} + + )} + {tab === 'tasks' && } + {tab === 'talents' && } + {tab === 'jobs' && } {tab === 'services' && } - {tab === 'community' && } + {tab === 'community' && } ); } /* ---------- 任务 ---------- */ -function TasksPane() { - const [mode, setMode] = useState(''); +function TasksPane({ mode, onMode }) { const [items, setItems] = useState(null); const [err, setErr] = useState(''); const load = useCallback(() => { @@ -64,11 +113,6 @@ function TasksPane() { useEffect(load, [load]); return ( <> - - {[{ k: '', l: '全部' }, { k: 'grab', l: '抢单' }, { k: 'bid', l: '投标' }, { k: 'register', l: '报名' }, { k: 'assign', l: '指派' }].map((m) => ( - setMode(m.k)}>{m.l} - ))} - {err && {err}} {!items && !err && } {items && items.length === 0 && 暂无任务} @@ -95,20 +139,14 @@ function TasksPane() { } /* ---------- 人才 ---------- */ -function TalentsPane() { - const [type, setType] = useState(''); +function TalentsPane({ ttype, onTtype }) { const [items, setItems] = useState(null); const [err, setErr] = useState(''); useEffect(() => { - hallTalents({ type }).then((d) => setItems(d.items || [])).catch((e) => setErr(e.message)); - }, [type]); + hallTalents({ type: ttype }).then((d) => setItems(d.items || [])).catch((e) => setErr(e.message)); + }, [ttype]); return ( <> - - {[{ k: '', l: '全部' }, { k: 'opc', l: 'OPC' }, { k: 'seeker', l: '求职者' }].map((t) => ( - setType(t.k)}>{t.l} - ))} - {err && {err}} {!items && !err && } {items && items.length === 0 && 暂无人才档案} @@ -134,8 +172,7 @@ function TalentsPane() { } /* ---------- 岗位 ---------- */ -function JobsPane() { - const [wt, setWt] = useState(''); +function JobsPane({ wt, onWt }) { const [items, setItems] = useState(null); const [err, setErr] = useState(''); useEffect(() => { @@ -143,11 +180,6 @@ function JobsPane() { }, [wt]); return ( <> - - {[{ k: '', l: '全部' }, { k: 'fulltime', l: '全职' }, { k: 'parttime', l: '兼职' }, { k: 'intern', l: '实习' }, { k: 'remote', l: '远程' }].map((t) => ( - setWt(t.k)}>{t.l} - ))} - {err && {err}} {!items && !err && } {items && items.length === 0 && 暂无招聘岗位} @@ -200,21 +232,15 @@ function ServicesPane() { } /* ---------- 社区(列表页内切换;帖子详情跳子页) ---------- */ -function CommunityPane() { - const [topic, setTopic] = useState('全部'); +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(() => {})); + communityPosts({ topic: topic === '' ? '' : topic }).then((d) => setPosts(d.items || [])).catch(() => {})); }, [topic]); useEffect(load, [load]); return ( <> - - {['全部', '交流', '求助', '资源', '经验', '公告'].map((t) => ( - setTopic(t)}>{t} - ))} - {!posts && } {posts && posts.length === 0 && 还没有帖子,来发第一帖} {(posts || []).map((p) => ( diff --git a/miniprogram/src/styles/hall.scss b/miniprogram/src/styles/hall.scss index cb44025..463e001 100644 --- a/miniprogram/src/styles/hall.scss +++ b/miniprogram/src/styles/hall.scss @@ -12,7 +12,8 @@ .hl-err { display: block; padding: 40rpx 24rpx; text-align: center; color: #e5484d; font-size: 26rpx; } .hl-empty { display: block; padding: 100rpx 0; text-align: center; color: #9aa7bd; font-size: 26rpx; } -.hl-chips { display: flex; gap: 14rpx; overflow-x: auto; padding: 8rpx 24rpx 20rpx; white-space: nowrap; +.hl-chips { position: sticky; display: flex; gap: 14rpx; overflow-x: auto; padding: 8rpx 24rpx 20rpx; white-space: nowrap; + background: rgba(247, 249, 255, 0.92); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); &::-webkit-scrollbar { display: none; } } .hl-chip { flex: none; padding: 10rpx 26rpx; border-radius: 999rpx; background: #fff; color: #6b7a99; font-size: 24rpx; border: 1rpx solid #e6ebf5; @@ -76,8 +77,10 @@ .hl-pcontent { display: block; font-size: 25rpx; line-height: 1.7; color: #7a879e; margin-top: 8rpx; } .hl-pfoot { display: flex; gap: 36rpx; margin-top: 16rpx; font-size: 22rpx; color: #9aa7bd; } -/* 大厅 Tab(页内切换) */ -.hl-tabs { display: flex; align-items: center; gap: 8rpx; margin: 0 24rpx 20rpx; } +/* 大厅 Tab(页内切换,吸顶悬浮不随内容滚动;top 由页面动态赋值) */ +.hl-tabs { position: sticky; display: flex; align-items: center; gap: 8rpx; margin: 0 24rpx 12rpx; + padding: 14rpx 0 10rpx; background: rgba(247, 249, 255, 0.92); + backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); } .hl-tab { padding: 10rpx 26rpx; font-size: 26rpx; color: #6b7a99; &.on { color: #3a5fe0; font-weight: 700; border-bottom: 4rpx solid #3a5fe0; } } .hl-tab-spacer { flex: 1; }