feat(theme): 全面完善暖色系双主题动态适配,优化同城组局品类筛选与试听条防折行规范

这个提交包含在:
wikiboss
2026-09-16 20:20:25 +08:00
父节点 21549b1f13
当前提交 21792f10b3
共有 6 个文件被更改,包括 259 次插入 和 144 次删除

查看文件

@@ -1,5 +1,5 @@
<template> <template>
<view class="create-activity-container"> <view class="create-activity-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }">
<!-- 模式选择 Tab (AA制 / 项目制) --> <!-- 模式选择 Tab (AA制 / 项目制) -->
<view class="mode-switch-wrap"> <view class="mode-switch-wrap">
<view <view
@@ -9,7 +9,7 @@
> >
<image class="mode-svg" :src="ICONS.bolt" mode="aspectFit" /> <image class="mode-svg" :src="ICONS.bolt" mode="aspectFit" />
<view class="mode-text-box"> <view class="mode-text-box">
<text class="mode-title">AA制车队</text> <text class="mode-title">AA制拼桌</text>
<text class="mode-desc">报名人均平摊费用</text> <text class="mode-desc">报名人均平摊费用</text>
</view> </view>
</view> </view>
@@ -20,8 +20,8 @@
> >
<image class="mode-svg" :src="ICONS.trophy" mode="aspectFit" /> <image class="mode-svg" :src="ICONS.trophy" mode="aspectFit" />
<view class="mode-text-box"> <view class="mode-text-box">
<text class="mode-title">项目制悬赏</text> <text class="mode-title">房主请客</text>
<text class="mode-desc">房主全额悬赏奖金池</text> <text class="mode-desc">房主全额买单请客</text>
</view> </view>
</view> </view>
</view> </view>
@@ -49,7 +49,7 @@
<input <input
class="row-input" class="row-input"
v-model="form.title" v-model="form.title"
placeholder="如:三里屯火锅探店 / 百星五排极速冲分" placeholder="如:三里屯铜锅涮肉AA探店 / 工体纯K麦霸欢唱局"
placeholder-class="ph" placeholder-class="ph"
/> />
</view> </view>
@@ -65,11 +65,11 @@
</view> </view>
<view class="input-row"> <view class="input-row">
<text class="row-label">地点/频道</text> <text class="row-label">地点/商圈</text>
<input <input
class="row-input" class="row-input"
v-model="form.location" v-model="form.location"
placeholder="如:朝阳区聚宝源 / 开黑语音 1 号房间" placeholder="如:北京市朝阳区三里屯聚宝源 / 工体纯KTV 888包厢"
placeholder-class="ph" placeholder-class="ph"
/> />
</view> </view>
@@ -80,7 +80,7 @@
<view class="stepper-row"> <view class="stepper-row">
<view class="stepper-info"> <view class="stepper-info">
<text class="row-label">招募席位 (人)</text> <text class="row-label">招募席位 (人)</text>
<text class="sub-label">包含房主在内的车队总人数</text> <text class="sub-label">包含房主在内的组局总人数</text>
</view> </view>
<view class="stepper-box"> <view class="stepper-box">
<button class="step-btn btn-active" :disabled="form.maxParticipants <= 2" @click="changeParticipants(-1)">-</button> <button class="step-btn btn-active" :disabled="form.maxParticipants <= 2" @click="changeParticipants(-1)">-</button>
@@ -95,18 +95,18 @@
class="row-input" class="row-input"
type="digit" type="digit"
v-model="form.aaFee" v-model="form.aaFee"
placeholder="如:30" placeholder="如:50"
placeholder-class="ph" placeholder-class="ph"
/> />
</view> </view>
<view class="input-row" v-else> <view class="input-row" v-else>
<text class="row-label">悬赏总奖金池 (元)</text> <text class="row-label">房主买单预算 (元)</text>
<input <input
class="row-input" class="row-input"
type="digit" type="digit"
v-model="form.totalFee" v-model="form.totalFee"
placeholder="如:300" placeholder="如:500"
placeholder-class="ph" placeholder-class="ph"
/> />
</view> </view>
@@ -114,7 +114,7 @@
<view class="calc-preview"> <view class="calc-preview">
<text class="preview-label">预算测算:</text> <text class="preview-label">预算测算:</text>
<text class="preview-val highlight"> <text class="preview-val highlight">
{{ form.payType === 'AA' ? `总费用预估约 ¥${calcTotalFee} (人均 ¥${form.aaFee || 0})` : `参与者免费报名,全员争夺 ¥${form.totalFee || 0} 奖金池` }} {{ form.payType === 'AA' ? `总费用预估约 ¥${calcTotalFee} (人均 ¥${form.aaFee || 0})` : `参与者免费报名,房主请客全包约 ¥${form.totalFee || 0}` }}
</text> </text>
</view> </view>
</view> </view>
@@ -122,28 +122,43 @@
<!-- 底部发起操作栏 (适配安全区) --> <!-- 底部发起操作栏 (适配安全区) -->
<view class="bottom-bar"> <view class="bottom-bar">
<button class="btn-submit btn-active" @click="handleSubmit"> <button class="btn-submit btn-active" @click="handleSubmit">
立即发起组队活动 立即发起同城组局
</button> </button>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted, onUnmounted } from 'vue';
import { ICONS } from '@/common/icons.js'; import { ICONS } from '@/common/icons.js';
import { ACTIVITIES_LIST } from '@/common/mock.js'; import { ACTIVITIES_LIST } from '@/common/mock.js';
import { getCurrentThemeKey } from '@/common/theme.js';
const games = ['王者荣耀', '和平精英', '美食陪吃', '同城陪玩', '下午茶陪喝', '英雄联盟']; const games = ['美食陪吃', '同城陪唱', '下午茶陪喝', '同城微醺', '同城桌游', '热门电竞'];
const currentThemeKey = ref(getCurrentThemeKey());
const onThemeChanged = (key) => {
currentThemeKey.value = key;
};
onMounted(() => {
currentThemeKey.value = getCurrentThemeKey();
uni.$on('theme-change', onThemeChanged);
});
onUnmounted(() => {
uni.$off('theme-change', onThemeChanged);
});
const form = ref({ const form = ref({
payType: 'AA', payType: 'AA',
game: '王者荣耀', game: '美食陪吃',
title: '', title: '',
startTime: '今晚 20:30', startTime: '周六 18:30',
location: '开黑语音 1 号频道', location: '北京市朝阳区三里屯商圈',
maxParticipants: 5, maxParticipants: 4,
aaFee: 25, aaFee: 50,
totalFee: 125, totalFee: 200,
}); });
const calcTotalFee = computed(() => { const calcTotalFee = computed(() => {
@@ -159,7 +174,7 @@ const changeParticipants = (delta) => {
const handleSubmit = () => { const handleSubmit = () => {
if (!form.value.title.trim()) { if (!form.value.title.trim()) {
form.value.title = `【${form.value.game}】今晚极速开黑车队`; form.value.title = `【${form.value.game}】同城聚会组局`;
} }
const newActivity = { const newActivity = {
@@ -173,12 +188,12 @@ const handleSubmit = () => {
maxParticipants: form.value.maxParticipants, maxParticipants: form.value.maxParticipants,
startTime: form.value.startTime, startTime: form.value.startTime,
location: form.value.location, location: form.value.location,
creatorNickname: '超能电竞玩家', creatorNickname: '同城局长',
creatorAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop', creatorAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop',
participants: [ participants: [
{ {
id: 'u_888', id: 'u_888',
nickname: '超能电竞玩家', nickname: '同城局长',
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop', avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop',
checkedIn: true checkedIn: true
} }
@@ -188,7 +203,7 @@ const handleSubmit = () => {
ACTIVITIES_LIST.unshift(newActivity); ACTIVITIES_LIST.unshift(newActivity);
uni.showToast({ uni.showToast({
title: '活动发起成功!车队已就绪', title: '组局发起成功!等待好友加入',
icon: 'success', icon: 'success',
duration: 1500 duration: 1500
}); });
@@ -421,4 +436,33 @@ const handleSubmit = () => {
margin: 0; margin: 0;
} }
} }
.create-activity-container.theme-warm {
background-color: #140E0A;
.mode-switch-wrap .mode-item {
background-color: #221711;
border-color: rgba(249, 115, 22, 0.12);
&.active {
background: rgba(249, 115, 22, 0.15);
border-color: #F97316;
}
}
.form-card {
background-color: #221711;
border-color: rgba(249, 115, 22, 0.12);
}
.bottom-bar {
background-color: rgba(34, 23, 17, 0.95);
border-top-color: rgba(249, 115, 22, 0.15);
.btn-submit {
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
box-shadow: 0 4rpx 20rpx rgba(249, 115, 22, 0.45);
}
}
}
</style> </style>

查看文件

@@ -5,7 +5,7 @@
<view class="badge-row"> <view class="badge-row">
<text class="game-tag">{{ activity.game }}</text> <text class="game-tag">{{ activity.game }}</text>
<text class="mode-tag" :class="activity.payType === 'AA' ? 'aa-mode' : 'project-mode'"> <text class="mode-tag" :class="activity.payType === 'AA' ? 'aa-mode' : 'project-mode'">
{{ activity.payType === 'AA' ? 'AA制平摊车队' : '项目制悬赏' }} {{ activity.payType === 'AA' ? 'AA制平摊组局' : '房主请客组局' }}
</text> </text>
</view> </view>
<view class="act-title">{{ activity.title }}</view> <view class="act-title">{{ activity.title }}</view>
@@ -13,12 +13,12 @@
<!-- 费用看板 --> <!-- 费用看板 -->
<view class="fee-board"> <view class="fee-board">
<view class="fee-col"> <view class="fee-col">
<text class="col-label">总费用</text> <text class="col-label">总预算</text>
<text class="col-val">¥{{ activity.totalFee }}</text> <text class="col-val">¥{{ activity.totalFee }}</text>
</view> </view>
<view class="fee-divider"></view> <view class="fee-divider"></view>
<view class="fee-col"> <view class="fee-col">
<text class="col-label">{{ activity.payType === 'AA' ? '人均分摊' : '奖金池' }}</text> <text class="col-label">{{ activity.payType === 'AA' ? '人均分摊' : '房主请客' }}</text>
<text class="col-val highlight"> <text class="col-val highlight">
¥{{ activity.payType === 'AA' ? activity.aaFee : activity.totalFee }} ¥{{ activity.payType === 'AA' ? activity.aaFee : activity.totalFee }}
</text> </text>
@@ -31,19 +31,19 @@
</view> </view>
</view> </view>
<!-- 房主与开黑信息 --> <!-- 房主与阵地信息 -->
<view class="section-card"> <view class="section-card">
<view class="sec-header"> <view class="sec-header">
<text class="sec-title">开黑阵地与房主</text> <text class="sec-title">组局阵地与发起人</text>
</view> </view>
<view class="creator-box"> <view class="creator-box">
<image class="creator-avatar" :src="activity.creatorAvatar" mode="aspectFill" /> <image class="creator-avatar" :src="activity.creatorAvatar" mode="aspectFill" />
<view class="creator-info"> <view class="creator-info">
<view class="creator-name-row"> <view class="creator-name-row">
<text class="creator-name">{{ activity.creatorNickname }}</text> <text class="creator-name">{{ activity.creatorNickname }}</text>
<text class="host-badge">活动房主</text> <text class="host-badge">同城局长</text>
</view> </view>
<text class="creator-sub">前职业选手 · 胜率保障车头</text> <text class="creator-sub">同城组局召集人 · 氛围担当</text>
</view> </view>
<button class="btn-chat btn-active" @click="chatWithHost">私聊</button> <button class="btn-chat btn-active" @click="chatWithHost">私聊</button>
</view> </view>
@@ -54,7 +54,7 @@
<text class="info-val">{{ activity.startTime }}</text> <text class="info-val">{{ activity.startTime }}</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">语音频道</text> <text class="info-label">活动地点</text>
<view class="copy-val" @click="copyLocation"> <view class="copy-val" @click="copyLocation">
<text class="info-val">{{ activity.location }}</text> <text class="info-val">{{ activity.location }}</text>
<text class="copy-btn">复制</text> <text class="copy-btn">复制</text>
@@ -62,16 +62,16 @@
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">核销机制</text> <text class="info-label">核销机制</text>
<text class="info-val green-text">入队出示4位动态门票码核销</text> <text class="info-val green-text">现场出示4位动态门票码核销签到</text>
</view> </view>
</view> </view>
</view> </view>
<!-- 已报名队友成员列表 --> <!-- 已报名成员列表 -->
<view class="section-card"> <view class="section-card">
<view class="sec-header"> <view class="sec-header">
<text class="sec-title">已报名队友 ({{ activity.participants ? activity.participants.length : 0 }})</text> <text class="sec-title">已加入成员 ({{ activity.participants ? activity.participants.length : 0 }})</text>
<text class="sec-sub">满员即锁车</text> <text class="sec-sub">满员即成局</text>
</view> </view>
<view class="member-list" v-if="activity.participants && activity.participants.length > 0"> <view class="member-list" v-if="activity.participants && activity.participants.length > 0">
<view v-for="(p, index) in activity.participants" :key="p.id" class="member-item"> <view v-for="(p, index) in activity.participants" :key="p.id" class="member-item">
@@ -81,12 +81,12 @@
<text class="member-seat">#{{ index + 1 }}号位</text> <text class="member-seat">#{{ index + 1 }}号位</text>
</view> </view>
<view class="checkin-badge" :class="{ checked: p.checkedIn }"> <view class="checkin-badge" :class="{ checked: p.checkedIn }">
{{ p.checkedIn ? '已核销入队' : '已购票待验' }} {{ p.checkedIn ? '已核销到场' : '待现场核销' }}
</view> </view>
</view> </view>
</view> </view>
<view v-else class="empty-members"> <view v-else class="empty-members">
<text>暂无其他队员,快抢先上车!</text> <text>暂无其他成员,快抢先加入同城聚会!</text>
</view> </view>
</view> </view>
@@ -97,7 +97,7 @@
<text class="ticket-text">我的门票</text> <text class="ticket-text">我的门票</text>
</view> </view>
<button class="btn-join btn-active" @click="handleJoin"> <button class="btn-join btn-active" @click="handleJoin">
立即上车 (¥{{ activity.payType === 'AA' ? activity.aaFee : 0 }}) 立即加入 (¥{{ activity.payType === 'AA' ? activity.aaFee : 0 }})
</button> </button>
</view> </view>
</view> </view>
@@ -124,13 +124,13 @@ const copyLocation = () => {
uni.setClipboardData({ uni.setClipboardData({
data: activity.value.location, data: activity.value.location,
success: () => { success: () => {
uni.showToast({ title: '语音频道已复制', icon: 'success' }); uni.showToast({ title: '活动地点已复制', icon: 'success' });
} }
}); });
}; };
const chatWithHost = () => { const chatWithHost = () => {
uni.showToast({ title: '已建立与房主的加密通道', icon: 'none' }); uni.showToast({ title: '已建立与局长的私聊会话', icon: 'none' });
}; };
const goToMyTicket = () => { const goToMyTicket = () => {
@@ -142,8 +142,8 @@ const goToMyTicket = () => {
const handleJoin = () => { const handleJoin = () => {
uni.showModal({ uni.showModal({
title: '确认报名', title: '确认报名',
content: `报名该【${activity.value.payType === 'AA' ? 'AA制' : '项目制'}】活动,需支付分摊款 ¥${activity.value.aaFee}。支付成功后自动生成4位入队核销码。`, content: `报名该【${activity.value.payType === 'AA' ? 'AA制' : '房主请客'}】组局,${activity.value.payType === 'AA' ? `需支付分摊款 ¥${activity.value.aaFee}` : '由房主全额买单请客'}。报名后自动生成4位入局核销码。`,
confirmText: '微信支付', confirmText: '确认报名',
confirmColor: '#6366F1', confirmColor: '#6366F1',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {

查看文件

@@ -16,7 +16,7 @@
@click="switchTab('AA')" @click="switchTab('AA')"
> >
<image class="tab-svg" :src="ICONS.bolt" mode="aspectFit" /> <image class="tab-svg" :src="ICONS.bolt" mode="aspectFit" />
<text class="tab-text">AA制车队</text> <text class="tab-text">AA制拼桌</text>
<view class="tab-indicator" v-if="currentTab === 'AA'"></view> <view class="tab-indicator" v-if="currentTab === 'AA'"></view>
</view> </view>
<view <view
@@ -25,32 +25,52 @@
@click="switchTab('PROJECT')" @click="switchTab('PROJECT')"
> >
<image class="tab-svg" :src="ICONS.gameTrophy" mode="aspectFit" /> <image class="tab-svg" :src="ICONS.gameTrophy" mode="aspectFit" />
<text class="tab-text">项目制赛事</text> <text class="tab-text">房主请客</text>
<view class="tab-indicator" v-if="currentTab === 'PROJECT'"></view> <view class="tab-indicator" v-if="currentTab === 'PROJECT'"></view>
</view> </view>
</view> </view>
<!-- 核心品类快捷筛选胶囊栏 (陪吃·陪唱·陪喝) -->
<view class="category-capsule-bar">
<view
class="capsule-item btn-active"
:class="{ active: currentCategory === 'ALL' }"
@click="switchCategory('ALL')"
>
<text class="capsule-text">全部品类</text>
</view>
<view
class="capsule-item btn-active"
:class="{ active: currentCategory === 'MEAL' }"
@click="switchCategory('MEAL')"
>
<image class="capsule-svg" :src="ICONS.utensils" mode="aspectFit" />
<text class="capsule-text">美食陪吃</text>
</view>
<view
class="capsule-item btn-active"
:class="{ active: currentCategory === 'SING' }"
@click="switchCategory('SING')"
>
<image class="capsule-svg" :src="ICONS.micVocal" mode="aspectFit" />
<text class="capsule-text">同城陪唱</text>
</view>
<view
class="capsule-item btn-active"
:class="{ active: currentCategory === 'DRINK' }"
@click="switchCategory('DRINK')"
>
<image class="capsule-svg" :src="ICONS.coffee" mode="aspectFit" />
<text class="capsule-text">下午茶陪喝</text>
</view>
</view>
<!-- 顶部状态小提示 (矢量盾牌图标) --> <!-- 顶部状态小提示 (矢量盾牌图标) -->
<view class="tips-bar"> <view class="tips-bar">
<image class="tips-svg" :src="ICONS.shield" mode="aspectFit" /> <image class="tips-svg" :src="ICONS.shield" mode="aspectFit" />
<text class="tips-text">同城组局主打美食陪吃、同城陪唱、下午茶陪喝,凭4位核销码现场签到履约</text> <text class="tips-text">AA制聚会人均费用透明平摊,项目制为房主全额请客或悬赏组局,凭4位门票码现场核销</text>
</view> </view>
<!-- 品类横滑筛选胶囊 -->
<scroll-view scroll-x class="cat-filter-scroll" :show-scrollbar="false">
<view class="cat-filter-list">
<view
v-for="cat in categoryOptions"
:key="cat.key"
class="cat-chip btn-active"
:class="{ active: currentCategory === cat.key }"
@click="selectCategory(cat.key)"
>
<text class="chip-text">{{ cat.label }}</text>
</view>
</view>
</scroll-view>
<!-- 活动列表 --> <!-- 活动列表 -->
<view class="activity-list"> <view class="activity-list">
<view <view
@@ -61,9 +81,9 @@
> >
<!-- 卡片头部 --> <!-- 卡片头部 -->
<view class="card-header"> <view class="card-header">
<view class="game-tag">{{ act.game }}</view> <view class="game-tag" :class="getCategoryTagClass(act.game)">{{ act.game }}</view>
<view class="mode-tag" :class="act.payType === 'AA' ? 'aa-mode' : 'project-mode'"> <view class="mode-tag" :class="act.payType === 'AA' ? 'aa-mode' : 'project-mode'">
{{ act.payType === 'AA' ? 'AA制平摊' : '项目制悬赏' }} {{ act.payType === 'AA' ? 'AA制平摊' : '房主请客' }}
</view> </view>
<view class="status-pill" :class="act.status"> <view class="status-pill" :class="act.status">
{{ act.status === 'RECRUITING' ? '招募中' : '进行中' }} {{ act.status === 'RECRUITING' ? '招募中' : '进行中' }}
@@ -73,14 +93,14 @@
<!-- 卡片标题 --> <!-- 卡片标题 -->
<view class="activity-title">{{ act.title }}</view> <view class="activity-title">{{ act.title }}</view>
<!-- 详细信息条 (矢量时钟与麦克风) --> <!-- 详细信息条 (矢量时钟与商圈位置) -->
<view class="meta-row"> <view class="meta-row">
<view class="meta-item"> <view class="meta-item">
<image class="meta-svg" :src="ICONS.clock" mode="aspectFit" /> <image class="meta-svg" :src="ICONS.clock" mode="aspectFit" />
<text class="meta-val">{{ act.startTime }}</text> <text class="meta-val">{{ act.startTime }}</text>
</view> </view>
<view class="meta-item"> <view class="meta-item">
<image class="meta-svg" :src="ICONS.mic" mode="aspectFit" /> <image class="meta-svg" :src="ICONS.mapPin" mode="aspectFit" />
<text class="meta-val">{{ act.location }}</text> <text class="meta-val">{{ act.location }}</text>
</view> </view>
</view> </view>
@@ -88,7 +108,7 @@
<!-- 进度与头像 --> <!-- 进度与头像 -->
<view class="progress-section"> <view class="progress-section">
<view class="progress-info"> <view class="progress-info">
<text class="progress-label">组队进度</text> <text class="progress-label">组局进度</text>
<text class="progress-count tabular-num">{{ act.currentParticipants }} / {{ act.maxParticipants }}人</text> <text class="progress-count tabular-num">{{ act.currentParticipants }} / {{ act.maxParticipants }}人</text>
</view> </view>
<view class="progress-track"> <view class="progress-track">
@@ -103,7 +123,7 @@
<view class="card-footer"> <view class="card-footer">
<view class="creator-info"> <view class="creator-info">
<image class="creator-avatar" :src="act.creatorAvatar" mode="aspectFill" /> <image class="creator-avatar" :src="act.creatorAvatar" mode="aspectFill" />
<text class="creator-name">房主:{{ act.creatorNickname }}</text> <text class="creator-name">局长:{{ act.creatorNickname }}</text>
</view> </view>
<view class="fee-info tabular-num"> <view class="fee-info tabular-num">
@@ -112,18 +132,21 @@
<text class="fee-amount">¥{{ act.aaFee }}</text> <text class="fee-amount">¥{{ act.aaFee }}</text>
</template> </template>
<template v-else> <template v-else>
<text class="fee-label">奖金池</text> <text class="fee-label">房主请客</text>
<text class="fee-amount project-gold">¥{{ act.totalFee }}</text> <text class="fee-amount project-gold">¥{{ act.totalFee }}</text>
</template> </template>
</view> </view>
</view> </view>
</view> </view>
<view v-if="filteredActivities.length === 0" class="empty-tip">
<text>暂无符合条件的组局活动,快来发起吧!</text>
</view>
</view> </view>
<!-- 悬浮发起活动按钮 (脉冲微流光) --> <!-- 悬浮发起活动按钮 (脉冲微流光) -->
<view class="fab-btn btn-active" @click="createActivity"> <view class="fab-btn btn-active" @click="createActivity">
<text class="fab-icon">+</text> <text class="fab-icon">+</text>
<text class="fab-text">发起组队</text> <text class="fab-text">发起组局</text>
</view> </view>
</view> </view>
</template> </template>
@@ -152,42 +175,38 @@ onUnmounted(() => {
uni.$off('theme-change', onThemeChanged); uni.$off('theme-change', onThemeChanged);
}); });
const categoryOptions = [ const getCategoryTagClass = (game) => {
{ key: 'ALL', label: '全部品类' }, if (game === '美食陪吃') return 'tag-meal';
{ key: 'MEAL', label: '美食陪吃' }, if (game === '同城陪唱') return 'tag-sing';
{ key: 'SING', label: '同城陪唱' }, if (game === '下午茶陪喝' || game === '同城微醺') return 'tag-drink';
{ key: 'DRINK', label: '下午茶陪喝' }, return '';
{ key: 'GAME', label: '电竞开黑' },
];
const selectCategory = (key) => {
currentCategory.value = key;
}; };
const filteredActivities = computed(() => { const filteredActivities = computed(() => {
let list = activities.value; return activities.value.filter(a => {
if (currentTab.value !== 'ALL') { // 支付模式筛选
list = list.filter(a => a.payType === currentTab.value); const matchTab = currentTab.value === 'ALL' || a.payType === currentTab.value;
} // 品类筛选 (美食陪吃 / 同城陪唱 / 下午茶陪喝/微醺)
if (currentCategory.value !== 'ALL') { let matchCat = true;
const map = { if (currentCategory.value === 'MEAL') {
MEAL: '美食陪吃', matchCat = a.game === '美食陪吃';
SING: '同城陪唱', } else if (currentCategory.value === 'SING') {
DRINK: '下午茶陪喝', matchCat = a.game === '同城陪唱';
GAME: '王者荣耀', } else if (currentCategory.value === 'DRINK') {
}; matchCat = a.game === '下午茶陪喝' || a.game === '同城微醺';
const target = map[currentCategory.value];
if (target) {
list = list.filter(a => a.game === target);
} }
} return matchTab && matchCat;
return list; });
}); });
const switchTab = (tab) => { const switchTab = (tab) => {
currentTab.value = tab; currentTab.value = tab;
}; };
const switchCategory = (cat) => {
currentCategory.value = cat;
};
const goToDetail = (id) => { const goToDetail = (id) => {
uni.navigateTo({ uni.navigateTo({
url: `/pages/activity/detail?id=${id}` url: `/pages/activity/detail?id=${id}`
@@ -261,6 +280,47 @@ const createActivity = () => {
} }
} }
.category-capsule-bar {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
overflow-x: auto;
white-space: nowrap;
.capsule-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 24rpx;
background: #151D30;
border-radius: 30rpx;
border: 1px solid rgba(255, 255, 255, 0.08);
transition: all 0.2s ease;
.capsule-svg {
width: 26rpx;
height: 26rpx;
}
.capsule-text {
font-size: 24rpx;
color: #94A3B8;
font-weight: 500;
}
&.active {
background: rgba(99, 102, 241, 0.2);
border-color: #6366F1;
.capsule-text {
color: #FFFFFF;
font-weight: 700;
}
}
}
}
.tips-bar { .tips-bar {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -283,45 +343,18 @@ const createActivity = () => {
} }
} }
.cat-filter-scroll {
white-space: nowrap;
margin-bottom: 24rpx;
.cat-filter-list {
display: flex;
gap: 16rpx;
.cat-chip {
padding: 10rpx 24rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
transition: all 0.2s ease;
.chip-text {
font-size: 24rpx;
color: #94A3B8;
font-weight: 500;
}
&.active {
background: rgba(99, 102, 241, 0.2);
border-color: #6366F1;
.chip-text {
color: #FFF;
font-weight: 700;
}
}
}
}
}
.activity-list { .activity-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24rpx; gap: 24rpx;
.empty-tip {
text-align: center;
padding: 60rpx 0;
font-size: 26rpx;
color: #64748B;
}
.activity-card { .activity-card {
background-color: #151D30; background-color: #151D30;
border-radius: 24rpx; border-radius: 24rpx;
@@ -341,6 +374,22 @@ const createActivity = () => {
font-size: 22rpx; font-size: 22rpx;
padding: 4rpx 14rpx; padding: 4rpx 14rpx;
border-radius: 8rpx; border-radius: 8rpx;
&.tag-meal {
background: rgba(245, 158, 11, 0.15);
color: #FBBF24;
border: 1px solid rgba(245, 158, 11, 0.3);
}
&.tag-sing {
background: rgba(244, 63, 94, 0.15);
color: #FB7185;
border: 1px solid rgba(244, 63, 94, 0.3);
}
&.tag-drink {
background: rgba(16, 185, 129, 0.15);
color: #34D399;
border: 1px solid rgba(16, 185, 129, 0.3);
}
} }
.mode-tag { .mode-tag {
@@ -537,6 +586,14 @@ const createActivity = () => {
} }
} }
.category-capsule-bar .capsule-item.active {
background: linear-gradient(135deg, #F97316, #F59E0B);
box-shadow: 0 4rpx 14rpx rgba(249, 115, 22, 0.4);
.capsule-text {
color: #FFF7ED;
}
}
.tips-bar { .tips-bar {
background: rgba(249, 115, 22, 0.1); background: rgba(249, 115, 22, 0.1);
border-color: rgba(249, 115, 22, 0.25); border-color: rgba(249, 115, 22, 0.25);
@@ -545,14 +602,6 @@ const createActivity = () => {
} }
} }
.cat-filter-list .cat-chip.active {
background: linear-gradient(135deg, #F97316, #F59E0B);
box-shadow: 0 4rpx 14rpx rgba(249, 115, 22, 0.4);
.chip-text {
color: #FFF7ED;
}
}
.activity-list .activity-card { .activity-list .activity-card {
background-color: #221711; background-color: #221711;
border-color: rgba(249, 115, 22, 0.12); border-color: rgba(249, 115, 22, 0.12);

查看文件

@@ -5,7 +5,7 @@
<view class="ticket-header"> <view class="ticket-header">
<view class="game-badge">{{ ticketData.game }}</view> <view class="game-badge">{{ ticketData.game }}</view>
<view class="stamp-badge" :class="{ verified: isCheckedIn }"> <view class="stamp-badge" :class="{ verified: isCheckedIn }">
{{ isCheckedIn ? '已核销入队' : '待核销' }} {{ isCheckedIn ? '已核销到场' : '待现场核销' }}
</view> </view>
</view> </view>
@@ -13,15 +13,15 @@
<view class="ticket-meta"> <view class="ticket-meta">
<view class="meta-row"> <view class="meta-row">
<text class="meta-k">开黑时间</text> <text class="meta-k">聚会时间</text>
<text class="meta-v">{{ ticketData.startTime }}</text> <text class="meta-v">{{ ticketData.startTime }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-k">语音频道</text> <text class="meta-k">活动地点</text>
<text class="meta-v">{{ ticketData.location }}</text> <text class="meta-v">{{ ticketData.location }}</text>
</view> </view>
<view class="meta-row"> <view class="meta-row">
<text class="meta-k">房主昵称</text> <text class="meta-k">局长昵称</text>
<text class="meta-v">{{ ticketData.creatorNickname }}</text> <text class="meta-v">{{ ticketData.creatorNickname }}</text>
</view> </view>
</view> </view>
@@ -35,7 +35,7 @@
<!-- 核心亮点:4位数字核销码专属卡槽 --> <!-- 核心亮点:4位数字核销码专属卡槽 -->
<view class="code-section"> <view class="code-section">
<view class="code-tips">进房向房主出示 4 位入队核销码</view> <view class="code-tips">到场向局长出示 4 位组局核销码</view>
<view class="code-slots"> <view class="code-slots">
<view v-for="(char, idx) in verifyCodeDigits" :key="idx" class="digit-box"> <view v-for="(char, idx) in verifyCodeDigits" :key="idx" class="digit-box">
<text class="digit-text">{{ char }}</text> <text class="digit-text">{{ char }}</text>
@@ -64,7 +64,7 @@
<!-- 演示操作按钮 --> <!-- 演示操作按钮 -->
<view class="action-wrap"> <view class="action-wrap">
<button class="btn-verify-demo btn-active" @click="toggleCheckIn"> <button class="btn-verify-demo btn-active" @click="toggleCheckIn">
{{ isCheckedIn ? '重置为待核销状态' : '模拟房主核销此门票' }} {{ isCheckedIn ? '重置为待核销状态' : '模拟局长核销此门票' }}
</button> </button>
<button class="btn-back btn-active" @click="goBack">返回活动详情</button> <button class="btn-back btn-active" @click="goBack">返回活动详情</button>
</view> </view>

查看文件

@@ -625,6 +625,10 @@ const navTo = (url) => {
padding: 0 16rpx; padding: 0 16rpx;
margin-bottom: 16rpx; margin-bottom: 16rpx;
transition: all 0.2s ease; transition: all 0.2s ease;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex-shrink: 0;
&.playing { &.playing {
background: rgba(99, 102, 241, 0.3); background: rgba(99, 102, 241, 0.3);

查看文件

@@ -71,4 +71,22 @@ describe('UI/UX 规范门禁 (TDD 测试套件: 全面杜绝系统 Emoji)', () =
} }
}); });
}); });
describe('4. 首页卡片试听条单行防折行与文本截断规范校验', () => {
it('首页试听条必须配置单行不折行、弹性伸缩与省略号截断', () => {
const indexVuePath = path.join(uniPagesDir, 'index', 'index.vue');
const content = fs.readFileSync(indexVuePath, 'utf-8');
// 提取 .voice-bar 样式块
const voiceBarMatch = content.match(/\.voice-bar\s*\{([\s\S]*?)\n\s*\.card-footer/);
expect(voiceBarMatch).not.toBeNull();
const styleBlock = voiceBarMatch![1];
// 必须防止文字折行破坏胶囊造型
expect(styleBlock).toContain('white-space: nowrap');
expect(styleBlock).toContain('text-overflow: ellipsis');
expect(styleBlock).toContain('overflow: hidden');
expect(styleBlock).toContain('flex-shrink: 0');
});
});
}); });