feat(miniprogram): 资讯tabs拆分+吸顶悬浮+全站交互配色统一
- 资讯中心:类别 tab(新增「全部」,全类型按优先级/时间排序)移出标题区,悬浮于标题区正下方固定不随滚动 - 活动页 tabs:透明毛玻璃底 + sticky 吸顶(top=标题区实测高度) - 选中态统一:蓝紫渐变(135deg #6b8bff→#8f7bff)+白字+柔投影;未选中玻璃底+淡蓝边(活动/我的 phase-tab 修正) - 留言点赞红统一 #ff5f86
This commit is contained in:
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 />
|
||||
)}
|
||||
{/* 底部留白避开自定义 tabBar(ScrollView 内无法用容器 padding) */}
|
||||
<View className="ct-scroll-pad" />
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
一个人,也可以是一家公司。5–10 分钟,看清你的 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>
|
||||
|
||||
Reference in New Issue
Block a user