feat(miniprogram): 资讯tabs拆分+吸顶悬浮+全站交互配色统一

- 资讯中心:类别 tab(新增「全部」,全类型按优先级/时间排序)移出标题区,悬浮于标题区正下方固定不随滚动
- 活动页 tabs:透明毛玻璃底 + sticky 吸顶(top=标题区实测高度)
- 选中态统一:蓝紫渐变(135deg #6b8bff→#8f7bff)+白字+柔投影;未选中玻璃底+淡蓝边(活动/我的 phase-tab 修正)
- 留言点赞红统一 #ff5f86
This commit is contained in:
Pine
2026-08-29 00:08:49 +08:00
parent 8362334f1b
commit a05eb8ce29
8 changed files with 71 additions and 48 deletions
+13 -6
View File
@@ -7,6 +7,7 @@ import { useTabIndex } from '@/utils/tab';
import Skeleton from '@/components/skeleton';
import CountdownBox from '@/components/CountdownBox';
import './index.scss';
import { PageHeader } from '@/components/TopInset';
const FILTERS = [
{ key: 'all', label: '全部' },
@@ -31,6 +32,16 @@ export default function Booking() {
const [filter, setFilter] = useState('all');
const [err, setErr] = useState('');
const [loaded, setLoaded] = useState(false);
const [tabsTop, setTabsTop] = useState(0);
// tabs 吸顶位置 = 固定标题区实测高度(滚动到顶时 tabs 悬浮在标题区正下方)
useEffect(() => {
Taro.nextTick(() => {
Taro.createSelectorQuery()
.select('.page-navbar')
.boundingClientRect((r) => { if (r && r.height) setTabsTop(r.height); })
.exec();
});
}, []);
useEffect(() => {
Promise.all([
@@ -89,11 +100,7 @@ export default function Booking() {
return (
<View className="events page-in">
<View className="ev-head">
<Text className="ev-no">EVENTS</Text>
<Text className="ev-title">活动</Text>
<Text className="ev-en">线下 · 线上 公益课与沙龙</Text>
</View>
<PageHeader no="EVENTS" title="活动" en="线下 · 线上 公益课与沙龙" />
{/* 近期活动 · 首屏横幅(始终渲染,数据到位后刷新) */}
<View className="mk-section">
@@ -109,7 +116,7 @@ export default function Booking() {
</View>
{/* 筛选 tab */}
<View className="ev-tabs">
<View className="ev-tabs" style={{ top: `${tabsTop}px` }}>
{FILTERS.map((f) => (
<Text key={f.key} className={`ev-tab${filter === f.key ? ' on' : ''}`} onClick={() => setFilter(f.key)}>{f.label}</Text>
))}
+10 -5
View File
@@ -20,9 +20,12 @@
padding: 16rpx 8rpx;
margin-top: 0;
position: sticky;
top: 0;
top: 0; /* 运行时由内联 style 覆盖为标题区实测高度 */
z-index: 3;
background: #f4f6ff;
/* 常驻毛玻璃底:非吸顶时与页面底色几乎一致(视觉透明),吸顶悬浮时遮挡滚过的卡片 */
background: rgba(247, 249, 255, 0.92);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
/* 顶部用伪元素延伸到视口外,确保吸顶时完全贴合、无透光间隙 */
}
.ev-tabs::before {
@@ -35,11 +38,13 @@
background: #f4f6ff;
}
.ev-tab {
font-size: 26rpx; color: #9aa4c6; padding: 10rpx 30rpx;
border-radius: 999px; border: 1rpx solid rgba(255,255,255,0.12);
font-size: 26rpx; color: #8a94c0; padding: 10rpx 30rpx;
border-radius: 999px; border: 1rpx solid rgba(99,132,255,0.14);
background: rgba(255,255,255,0.72); backdrop-filter: blur(8px);
}
.ev-tab.on {
color: #0a0d22; background: linear-gradient(120deg, #8fc3ff, #a8a0ff); font-weight: 700; border-color: transparent;
color: #ffffff; background: linear-gradient(135deg, #6b8bff, #8f7bff); font-weight: 600; border-color: transparent;
box-shadow: 0 8rpx 22rpx rgba(98,132,255,.28);
}
/* 双列瀑布流 */
@@ -65,7 +65,7 @@
.cd-cmt-like { display: flex; align-items: center; gap: 6rpx; }
.cd-cmt-like-ic { width: 26rpx; height: 26rpx; }
.cd-cmt-like-n { font-size: 22rpx; color: #9aa4c6; }
.cd-cmt-like.on .cd-cmt-like-ic { background-color: #e05a6d; }
.cd-cmt-like.on .cd-cmt-like-ic { background-color: #ff5f86; }
/* 回复嵌套:左缩进 + 竖线 */
.cd-cmt-replies { margin-top: 12rpx; padding-left: 24rpx; border-left: 3rpx solid rgba(99,132,255,.18); }
.cd-cmt-sub { margin-top: 14rpx; }
+23 -14
View File
@@ -5,6 +5,7 @@ import { listContent, CONTENT_CATS } from '@/utils/content';
import { useTabIndex } from '@/utils/tab';
import Skeleton from '@/components/skeleton';
import './index.scss';
import { PageHeader } from '@/components/TopInset';
/** 类别视觉(无 image 字段时用类别渐变占位) */
const CAT_META = {
@@ -90,7 +91,15 @@ export default function ContentIndex() {
useEffect(() => {
setLoaded(false); setErr('');
listContent(cat)
const load = cat === 'all'
? Promise.all(CONTENT_CATS.map((c) => listContent(c.key).catch(() => [])))
.then((ls) => {
const all = ls.flat();
all.sort((a, b) => (b.priority || 0) - (a.priority || 0) || String(b.created_at || '').localeCompare(String(a.created_at || '')));
return all;
})
: listContent(cat);
load
.then(setItems)
.catch(() => setErr('加载失败,请稍后重试'))
.finally(() => setLoaded(true));
@@ -101,21 +110,19 @@ export default function ContentIndex() {
return (
<View className="ct page-in">
{/* 标准页头:kicker + 渐变大标题 + 英文注脚;类别 tab 药丸居右(对齐「我的」页头样式) */}
<View className="ct-head">
<View className="ct-head-l">
<Text className="ct-no">NEWS</Text>
<Text className="ct-title">资讯中心</Text>
<Text className="ct-en">INFORMATION</Text>
</View>
<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>
{/* 固定页头:标题在状态栏左侧,不随滚动 */}
{/* 固定标题区 */}
<PageHeader no="NEWS" title="资讯中心" en="INFORMATION" />
{/* 类别 tab(含全部):紧贴标题区下方,位于滚动区之外 */}
<View className="ct-cats ct-cats-bar">
<View key="all" className={`ct-cat${cat === 'all' ? ' on' : ''}`} onClick={() => setCat('all')}>全部</View>
{CONTENT_CATS.map((c) => (
<View key={c.key} className={`ct-cat${cat === c.key ? ' on' : ''}`} onClick={() => setCat(c.key)}>{c.label}</View>
))}
</View>
{/* 内容区独立滚动:页头(标题+tab)固定不动 */}
{/* 内容区独立滚动 */}
<ScrollView className="ct-scroll" scrollY enhanced showScrollbar={false}>
{/* 政策测评入口(非 tab,政策分类下提供) */}
{cat === 'policy' && (
@@ -135,6 +142,8 @@ export default function ContentIndex() {
) : (
<Skeleton />
)}
{/* 底部留白避开自定义 tabBarScrollView 内无法用容器 padding */}
<View className="ct-scroll-pad" />
</ScrollView>
</View>
);
+14 -4
View File
@@ -2,10 +2,11 @@
/* 页面柔彩渐变背景(浅色粉紫→薄荷→天蓝) */
.ct { position: relative; z-index: 2; display: flex; flex-direction: column;
height: 100vh; overflow: hidden; padding: 24rpx 24rpx 0; box-sizing: border-box;
height: 100vh; overflow: hidden; padding: 0 24rpx 0; box-sizing: border-box;
background: linear-gradient(165deg, #edf2ff 0%, #f6eefc 34%, #ecf7f6 68%, #eaf4ff 100%); }
/* 内容区独立滚动(页头固定),底部避开自定义 tabBar */
.ct-scroll { flex: 1; min-height: 0; padding-bottom: 180rpx; }
/* 内容区独立滚动(页头固定),底部避开自定义 tabBar
height:0 + flex-grow 强制 ScrollView 拿到确定高度,否则微信里内部高度随内容增长、整页滚动 */
.ct-scroll { flex: 1 1 0; height: 0; min-height: 0; }
/* 顶部柔光装饰(可选,营造 Soft 氛围) */
.ct::before { content: ''; position: fixed; inset: 0; z-index: -1;
background: radial-gradient(60% 46% at 12% 4%, rgba(229,167,224,.30), transparent 60%),
@@ -23,7 +24,14 @@
.ct-en { font-family: 'BubbledotICG', monospace; color: #5b5b5b; font-size: 24rpx; letter-spacing: 2rpx; }
/* 类别 tab:药丸居右,4 个一行与标题同排 */
.ct-cats { flex: 1; min-width: 0; display: flex; flex-wrap: nowrap; justify-content: flex-end; gap: 8rpx; padding: 0 0 6rpx; }
.ct-cats { min-width: 0; display: flex; flex-wrap: nowrap; justify-content: flex-start; gap: 12rpx; }
/* 类别 tab 条:紧贴标题区下方,位于滚动区之外,固定不随内容滚动 */
.ct-cats-bar {
flex-wrap: nowrap; justify-content: flex-start; gap: 12rpx;
padding: 4rpx 0 12rpx;
background: transparent;
}
.ct-cats-bar .ct-cat { flex: 0 0 auto; padding: 10rpx 30rpx; }
.ct-cat {
flex: 1 1 0; min-width: 0; text-align: center; padding: 8rpx 0; border-radius: 999px; white-space: nowrap; overflow: hidden;
background: rgba(255,255,255,.72); border: 1rpx solid rgba(99,132,255,.14); color: #8a94c0; font-size: 22rpx;
@@ -86,3 +94,5 @@
.ct-empty { padding: 80rpx 0; text-align: center; font-size: 26rpx; color: #9aa4c6; }
.ct-err { display: block; padding: 60rpx 0; text-align: center; font-size: 26rpx; color: #ff5f86; }
.ct-scroll-pad { height: 200rpx; }
+3 -10
View File
@@ -9,6 +9,7 @@ import { API_BASE } from '@/utils/api';
import Skeleton from '@/components/skeleton';
import CountdownBox from '@/components/CountdownBox';
import './index.scss';
import { PageHeader } from '@/components/TopInset';
/* 报名标签:按 auditStatus + event_status 推断 待参加 / 审核中 / 已结束 / 未通过 */
const PHASE_META = {
@@ -274,11 +275,7 @@ export default function Mine() {
if (!user || !isAuthed()) {
return (
<View className="mine page-in">
<View className="mine-head">
<Text className="mine-no">MY</Text>
<Text className="mine-title">我的</Text>
<Text className="mine-en">Account</Text>
</View>
<PageHeader no="MY" title="我的" en="Account" />
<View className="mine-card login-card">
<Button className="btn-main wx-btn" loading={busy} onClick={onWxLogin}>微信一键登录</Button>
<Text className="muted mine-tip">登录后可绑定手机查看报名场次与活动签到</Text>
@@ -314,11 +311,7 @@ export default function Mine() {
/* ============ 已登录:资料 + 我的报名 ============ */
return (
<View className="mine page-in">
<View className="mine-head">
<Text className="mine-no">MY</Text>
<Text className="mine-title">我的</Text>
<Text className="mine-en">Account</Text>
</View>
<PageHeader no="MY" title="我的" en="Account" />
{/* 资料卡 */}
<View className="mine-card profile">
+4 -3
View File
@@ -83,10 +83,11 @@
border: 1rpx solid rgba(99, 132, 255, 0.14);
}
.phase-tab.on {
color: #0a0d22;
background: linear-gradient(120deg, #8fc3ff, #a8a0ff);
font-weight: 700;
color: #ffffff;
background: linear-gradient(135deg, #6b8bff, #8f7bff);
font-weight: 600;
border-color: transparent;
box-shadow: 0 8rpx 22rpx rgba(98,132,255,.28);
}
.mine-subtitle {
display: block;
+3 -5
View File
@@ -5,6 +5,7 @@ import { useTabIndex } from '@/utils/tab';
import { request } from '@/utils/api';
import { getUser } from '@/utils/auth';
import './index.scss';
import { PageHeader } from '@/components/TopInset';
const adaptColor = (s) => (s >= 78 ? '#a8f0c8' : s >= 55 ? '#f5d9a0' : '#f0b8b8');
@@ -102,11 +103,7 @@ export default function Test() {
if (stage === 'welcome') {
return (
<View className="ot page-in">
<View className="ot-head">
<Text className="ot-no">00</Text>
<Text className="ot-title">开始测评</Text>
<Text className="ot-en">Start</Text>
</View>
<PageHeader no="00" title="开始测评" en="Start" />
<Text className="ot-intro">
一个人也可以是一家公司510 分钟看清你的 OPC 赛道角色与 AI 协作方式
</Text>
@@ -194,6 +191,7 @@ export default function Test() {
const level = r.adaptLevel || {};
return (
<View className="ot page-in">
<TopInset />
{/* 头部:类型码 + 人设 + 适配指数 */}
<View className="ot-head">
<Text className="ot-no">02</Text>