2697 行
73 KiB
Vue
2697 行
73 KiB
Vue
<template>
|
||
<view class="detail-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }">
|
||
<!-- 组局已解散状态警示横幅 -->
|
||
<view class="cancelled-banner" v-if="activity.status === 'CANCELLED'">
|
||
<image class="cancelled-icon" :src="ICONS.shield" mode="aspectFit" />
|
||
<view class="cancelled-text-box">
|
||
<text class="cancelled-title">本组局已由局长解散</text>
|
||
<text class="cancelled-desc">组局招募已终止,所有已报名搭子款项均已全额原路退还至钱包余额。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 头部活动基本信息 -->
|
||
<view class="hero-card" :class="{ 'is-cancelled': activity.status === 'CANCELLED' }">
|
||
<view class="badge-row">
|
||
<text class="game-tag">{{ activity.game }}</text>
|
||
<text class="mode-tag" :class="activity.payType === 'AA' ? 'aa-mode' : 'project-mode'">
|
||
{{ activity.payType === 'AA' ? 'AA制平摊组局' : '房主请客组局' }}
|
||
</text>
|
||
<text class="status-tag" :class="activity.status || 'RECRUITING'">
|
||
{{ getStatusText(activity.status) }}
|
||
</text>
|
||
</view>
|
||
<view class="act-title">{{ activity.title }}</view>
|
||
|
||
<!-- 费用看板 -->
|
||
<view class="fee-board">
|
||
<view class="fee-col">
|
||
<text class="col-label">总预算支出</text>
|
||
<text class="col-val">¥{{ activity.totalFee }}</text>
|
||
</view>
|
||
<view class="fee-divider"></view>
|
||
<view class="fee-col">
|
||
<text class="col-label">{{ activity.payType === 'AA' ? '人均分摊' : (activity.pricingMode === 'PER_PERSON' || activity.perPersonFee ? '单人出价' : '房主请客') }}</text>
|
||
<text class="col-val highlight">
|
||
¥{{ activity.payType === 'AA' ? activity.aaFee : (activity.perPersonFee || activity.totalFee) }}{{ activity.payType !== 'AA' && (activity.pricingMode === 'PER_PERSON' || activity.perPersonFee) ? '/人' : '' }}
|
||
</text>
|
||
</view>
|
||
<view class="fee-divider"></view>
|
||
<view class="fee-col">
|
||
<text class="col-label">招募席位</text>
|
||
<text class="col-val">{{ activity.currentParticipants }}/{{ activity.maxParticipants }}人</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 房主追加金额与手动改价操作栏 (支持随时灵活追加赏金) -->
|
||
<view class="dynamic-price-banner" v-if="activity.payType === 'PROJECT' || activity.allowDynamicPrice">
|
||
<view class="dp-info">
|
||
<view class="dp-tag-row">
|
||
<text class="dp-badge">支持随时追加金额</text>
|
||
<text class="dp-rule-tag">
|
||
{{ activity.priceAdjustRule === 'TIERED' ? '阶梯差价制' : '全员同享追加新高价 (自动补差)' }}
|
||
</text>
|
||
</view>
|
||
<text class="dp-status-text">
|
||
当前单人出价 ¥{{ activity.perPersonFee || 0 }}/人 · 招募{{ activity.maxParticipants }}人 · 已报名{{ joinedCount }}人
|
||
</text>
|
||
</view>
|
||
<view class="dp-btn-group">
|
||
<button class="btn-adjust-price btn-append btn-active" @click="openAppendModal">
|
||
追加金额
|
||
</button>
|
||
<button class="btn-adjust-price btn-edit btn-active" @click="openAdjustModal">
|
||
改价
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 房主与阵地信息 -->
|
||
<view class="section-card">
|
||
<view class="sec-header">
|
||
<text class="sec-title">组局阵地与发起人</text>
|
||
</view>
|
||
<view class="creator-box">
|
||
<image v-if="activity.creatorAvatar" class="creator-avatar btn-active" :src="activity.creatorAvatar" mode="aspectFill" @click="openHostProfile" />
|
||
<view v-else class="creator-avatar creator-avatar-empty btn-active" @click="openHostProfile">
|
||
<text class="creator-avatar-placeholder">?</text>
|
||
</view>
|
||
<view class="creator-info btn-active" @click="openHostProfile">
|
||
<view class="creator-name-row">
|
||
<text class="creator-name">{{ activity.creatorNickname }}</text>
|
||
<text class="host-badge">发起人</text>
|
||
</view>
|
||
<text class="creator-sub">同城组局召集人</text>
|
||
</view>
|
||
<button class="btn-chat btn-active" @click.stop="openSupportSheet">联系客服</button>
|
||
</view>
|
||
|
||
<view class="info-list">
|
||
<view class="info-row">
|
||
<text class="info-label">活动时段</text>
|
||
<text class="info-val">{{ activity.startTime }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">活动地点</text>
|
||
<view class="copy-val" @click="copyLocation">
|
||
<text class="info-val">{{ activity.location }}</text>
|
||
<text class="copy-btn">复制</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">性别限制</text>
|
||
<text
|
||
class="info-val"
|
||
:class="{
|
||
'female-text': activity.genderRequirement === 'FEMALE_ONLY' || activity.genderRequirement === '仅女生',
|
||
'male-text': activity.genderRequirement === 'MALE_ONLY' || activity.genderRequirement === '仅男生'
|
||
}"
|
||
>
|
||
{{ formatGenderRequirement(activity.genderRequirement) }}
|
||
</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">核销机制</text>
|
||
<text class="info-val green-text">现场出示4位动态门票码核销签到</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 局长现场核销搭子签到专属卡片 -->
|
||
<view class="section-card host-verify-card" v-if="isHost && activity.status !== 'CANCELLED'">
|
||
<view class="sec-header">
|
||
<view class="title-with-badge">
|
||
<text class="sec-title">现场核销搭子签到</text>
|
||
<text class="host-pill">局长专属</text>
|
||
</view>
|
||
<text class="sec-sub">现场验证4位数字门票码</text>
|
||
</view>
|
||
<view class="verify-action-row">
|
||
<view class="verify-input-wrap">
|
||
<image class="verify-svg" :src="ICONS.ticket" mode="aspectFit" />
|
||
<input
|
||
class="verify-code-input tabular-num"
|
||
type="number"
|
||
maxlength="4"
|
||
v-model="verifyCodeInput"
|
||
placeholder="输入搭子4位核销码"
|
||
placeholder-class="ph"
|
||
/>
|
||
</view>
|
||
<button class="btn-verify-now btn-active" @click="handleHostVerify">
|
||
核销签到
|
||
</button>
|
||
</view>
|
||
<text class="verify-helper-tip">搭子在「我的门票」出示4位动态入局码,局长核验后标记为已核销并锁定履约。</text>
|
||
</view>
|
||
|
||
<!-- 已报名成员列表 -->
|
||
<view class="section-card">
|
||
<view class="sec-header">
|
||
<text class="sec-title">已加入成员 ({{ activity.participants ? activity.participants.length : 0 }})</text>
|
||
<text class="sec-sub">满员即成局</text>
|
||
</view>
|
||
<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" :class="{ 'my-member-item': Number(p.userId) === currentUserId }">
|
||
<image class="member-avatar" :src="p.avatar" mode="aspectFill" />
|
||
<view class="member-info">
|
||
<view class="member-top-line">
|
||
<text class="member-name">{{ p.nickname }}</text>
|
||
<text class="my-badge" v-if="Number(p.userId) === currentUserId">我的席位</text>
|
||
<text class="member-price-tag" v-if="p.priceAtJoin">
|
||
出价: ¥{{ p.priceAtJoin }}
|
||
</text>
|
||
</view>
|
||
<view class="member-sub-line">
|
||
<text class="member-seat">#{{ index + 1 }}号位</text>
|
||
<text class="member-comp-tag" v-if="p.diffCompensated && p.diffCompensated > 0">
|
||
已补差 +¥{{ p.diffCompensated }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
<view class="member-status-wrap">
|
||
<view class="checkin-badge" :class="{ checked: p.checkedIn }">
|
||
{{ p.checkedIn ? '已核销到场' : '待现场核销' }}
|
||
</view>
|
||
<text class="btn-micro-leave btn-active" v-if="Number(p.userId) === currentUserId && !p.checkedIn" @click.stop="handleLeave">
|
||
退席
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-members">
|
||
<text>暂无其他成员,快抢先加入同城聚会!</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 房主手动改价记录 -->
|
||
<view class="section-card history-card" v-if="activity.priceAdjustHistory && activity.priceAdjustHistory.length > 0">
|
||
<view class="sec-header">
|
||
<text class="sec-title">手动调价记录</text>
|
||
<text class="sec-sub">共 {{ activity.priceAdjustHistory.length }} 次调整</text>
|
||
</view>
|
||
<view class="history-list">
|
||
<view v-for="(h, idx) in activity.priceAdjustHistory" :key="idx" class="history-item">
|
||
<view class="history-left">
|
||
<text class="history-action" :class="h.diff >= 0 ? 'up' : 'down'">
|
||
{{ h.diff >= 0 ? '手动提价加急' : '手动下调出价' }}
|
||
</text>
|
||
<text class="history-time">{{ h.time }}</text>
|
||
</view>
|
||
<view class="history-right">
|
||
<text class="history-diff" :class="h.diff >= 0 ? 'diff-up' : 'diff-down'">
|
||
¥{{ h.oldPrice }} → ¥{{ h.newPrice }} ({{ h.diff >= 0 ? '+' : '' }}¥{{ h.diff }}/人)
|
||
</text>
|
||
<text class="history-note" v-if="h.note">{{ h.note }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 房主追加金额/改价弹窗 -->
|
||
<view class="adjust-modal-mask" v-if="adjustModalVisible" @click="closeAdjustModal">
|
||
<view class="adjust-modal-content" @click.stop="stopPropagation">
|
||
<view class="sheet-grabber"></view>
|
||
<view class="modal-header">
|
||
<view class="modal-title-box">
|
||
<text class="modal-title">{{ modalMode === 'APPEND' ? '房主追加金额' : '房主手动调整出价' }}</text>
|
||
<text class="modal-subtitle">
|
||
{{ modalMode === 'APPEND' ? '向本局追加单人赏金,加急吸引附近搭子加入' : '手动调整单人出价,支持提价加急或降价调整' }}
|
||
</text>
|
||
</view>
|
||
<view class="modal-close-btn btn-active" @click="closeAdjustModal">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 模式切换 Tab -->
|
||
<view class="modal-mode-tabs">
|
||
<view
|
||
class="mode-tab btn-active"
|
||
:class="{ active: modalMode === 'APPEND' }"
|
||
@click="switchModalMode('APPEND')"
|
||
>
|
||
追加赏金 (加急成局)
|
||
</view>
|
||
<view
|
||
class="mode-tab btn-active"
|
||
:class="{ active: modalMode === 'EXACT' }"
|
||
@click="switchModalMode('EXACT')"
|
||
>
|
||
直接设定出价
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-body">
|
||
<!-- 当前出价看板 -->
|
||
<view class="modal-stat-box">
|
||
<view class="stat-item">
|
||
<text class="stat-label">当前单人出价</text>
|
||
<text class="stat-val highlight">¥{{ currentPerPerson }}<text class="unit">/人</text></text>
|
||
</view>
|
||
<view class="stat-divider"></view>
|
||
<view class="stat-item">
|
||
<text class="stat-label">当前总预算</text>
|
||
<text class="stat-val">¥{{ activity.totalFee || (currentPerPerson * (activity.maxParticipants || 4)) }}</text>
|
||
</view>
|
||
<view class="stat-divider"></view>
|
||
<view class="stat-item">
|
||
<text class="stat-label">当前报名人数</text>
|
||
<text class="stat-val">{{ joinedCount }}/{{ activity.maxParticipants || 4 }}<text class="unit">人</text></text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 追加金额模式 -->
|
||
<view v-if="modalMode === 'APPEND'">
|
||
<view class="modal-input-group">
|
||
<text class="group-label">每人追加赏金 (元/人)</text>
|
||
<view class="input-wrap">
|
||
<text class="currency-prefix">+¥</text>
|
||
<input
|
||
class="modal-input"
|
||
type="number"
|
||
v-model="appendDeltaInput"
|
||
placeholder="请输入追加金额,如 100"
|
||
placeholder-class="ph"
|
||
/>
|
||
<text class="input-unit">元/人</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 快捷追加面额 (双行对称卡片) -->
|
||
<view class="quick-options">
|
||
<view
|
||
v-for="opt in appendQuickOptions"
|
||
:key="opt.delta"
|
||
class="quick-btn btn-active"
|
||
:class="{ highlight: Number(appendDeltaInput) === opt.delta }"
|
||
@click="appendDeltaInput = String(opt.delta)"
|
||
>
|
||
<text class="q-val">+¥{{ opt.delta }}</text>
|
||
<text class="q-sub">{{ opt.sub }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 直接设定出价模式 -->
|
||
<view v-else>
|
||
<view class="modal-input-group">
|
||
<text class="group-label">设定目标单人出价 (元/人)</text>
|
||
<view class="input-wrap">
|
||
<text class="currency-prefix">¥</text>
|
||
<input
|
||
class="modal-input"
|
||
type="number"
|
||
v-model="newPriceInput"
|
||
placeholder="请输入新单人出价"
|
||
placeholder-class="ph"
|
||
/>
|
||
<text class="input-unit">元/人</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 快速设定选项 (双行对称卡片) -->
|
||
<view class="quick-options">
|
||
<view
|
||
v-for="opt in exactQuickOptions"
|
||
:key="opt.price"
|
||
class="quick-btn btn-active"
|
||
:class="{ highlight: Number(newPriceInput) === opt.price, down: opt.isDown }"
|
||
@click="setQuickPrice(opt.price)"
|
||
>
|
||
<text class="q-val">¥{{ opt.price }}</text>
|
||
<text class="q-sub">{{ opt.sub }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 实时测算展示 -->
|
||
<view class="calc-change-box" v-if="isValidAction">
|
||
<view class="calc-top">
|
||
<text class="calc-title">{{ modalMode === 'APPEND' ? '追加金额效果测算' : '改价差额测算' }}</text>
|
||
<text class="calc-pill" :class="effectiveDiff >= 0 ? 'up' : 'down'">
|
||
{{ effectiveDiff >= 0 ? `单人提升 +¥${effectiveDiff}/人` : `单人下调 -¥${Math.abs(effectiveDiff)}/人` }}
|
||
</text>
|
||
</view>
|
||
|
||
<view class="calc-summary-grid">
|
||
<view class="calc-summary-cell">
|
||
<text class="cell-label">调整后单人出价</text>
|
||
<view class="cell-val-row">
|
||
<text class="cell-price">¥{{ targetPerPerson }}<text class="cell-unit">/人</text></text>
|
||
<text class="cell-origin">原 ¥{{ currentPerPerson }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="calc-summary-divider"></view>
|
||
<view class="calc-summary-cell">
|
||
<text class="cell-label">调整后总预算约</text>
|
||
<view class="cell-val-row">
|
||
<text class="cell-price total">¥{{ estimatedTotal }}</text>
|
||
<text class="cell-delta" :class="effectiveDiff >= 0 ? 'up' : 'down'">
|
||
{{ estimatedTotal - (activity.totalFee || 0) >= 0 ? `+¥${estimatedTotal - (activity.totalFee || 0)}` : `-¥${Math.abs(estimatedTotal - (activity.totalFee || 0))}` }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="calc-notes">
|
||
<text class="note-row" v-if="effectiveDiff > 0 && activity.priceAdjustRule === 'UNIFIED_HIGH'">
|
||
规则说明:全员同享追加新高价。已报名的 {{ joinedCount }} 位老成员,每人自动补齐差额 ¥{{ effectiveDiff }}(老成员补差合计 ¥{{ effectiveDiff * joinedCount }})。
|
||
</text>
|
||
<text class="note-row" v-else-if="effectiveDiff > 0">
|
||
规则说明:阶梯差价招募。已报名的老成员按原出价结算,新加入席位按追加后单价 ¥{{ targetPerPerson }}/人 结算。
|
||
</text>
|
||
<text class="note-row" v-else-if="effectiveDiff < 0">
|
||
降价保护:已报名的 {{ joinedCount }} 位老成员出价保持原出价受保护,后续新席位按 ¥{{ targetPerPerson }}/人 执行。
|
||
</text>
|
||
<text class="note-row" v-else>
|
||
当前设定价格与原单人出价一致,未产生差额变动。
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="modal-footer">
|
||
<button class="footer-btn cancel-btn btn-active" @click="closeAdjustModal">取消</button>
|
||
<button class="footer-btn confirm-btn btn-active" @click="confirmAdjustPrice">
|
||
{{ modalMode === 'APPEND' ? '确认追加金额' : '确认修改出价' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部固定操作栏 -->
|
||
<view class="bottom-bar">
|
||
<!-- 1. 组局已解散状态 -->
|
||
<template v-if="activity.status === 'CANCELLED'">
|
||
<button class="btn-action-full btn-disabled">
|
||
该组局已解散 (款项已原路退还)
|
||
</button>
|
||
</template>
|
||
|
||
<!-- 2. 局长视角 (可追加出价 / 解散组局) -->
|
||
<template v-else-if="isHost">
|
||
<button class="btn-host-secondary btn-active" @click="openAppendModal">
|
||
追加赏金
|
||
</button>
|
||
<button class="btn-host-danger btn-active" @click="handleHostCancel">
|
||
解散组局
|
||
</button>
|
||
</template>
|
||
|
||
<!-- 3. 普通成员视角:已报名 (可查看门票 / 申请退席) -->
|
||
<template v-else-if="isJoined">
|
||
<view class="ticket-link btn-active" @click="goToMyTicket(myParticipantInfo ? myParticipantInfo.verifyCode : '')">
|
||
<image class="ticket-svg" :src="ICONS.ticket" mode="aspectFit" />
|
||
<text class="ticket-text">我的门票</text>
|
||
</view>
|
||
<button class="btn-leave btn-active" @click="handleLeave">
|
||
申请退席 (全额退款)
|
||
</button>
|
||
</template>
|
||
|
||
<!-- 4. 普通成员视角:未报名 -->
|
||
<template v-else>
|
||
<view class="ticket-link btn-active" @click="goToMyTicket('')">
|
||
<image class="ticket-svg" :src="ICONS.ticket" mode="aspectFit" />
|
||
<text class="ticket-text">我的门票</text>
|
||
</view>
|
||
<button class="btn-join btn-active" @click="handleJoin">
|
||
立即加入 (¥{{ activity.payType === 'AA' ? activity.aaFee : 0 }})
|
||
</button>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||
import {
|
||
getActivityDetail,
|
||
joinActivity,
|
||
adjustActivityPrice,
|
||
verifyActivityCheckin,
|
||
cancelActivity,
|
||
leaveActivity,
|
||
normalizeUserId
|
||
} from '@/common/api.js';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { openSupportSheet } from '@/common/support.js';
|
||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||
import { ensureLoginOrRedirect } from '@/common/role.js';
|
||
import { preloadSubscribeTemplates, requestSubscribeFor } from '@/common/subscribe.js';
|
||
import { useShare } from '@/common/share.js';
|
||
|
||
const currentUserId = computed(() => {
|
||
try {
|
||
const uid = uni.getStorageSync('userId');
|
||
return uid ? Number(uid) : 0;
|
||
} catch (_) {
|
||
return 0;
|
||
}
|
||
});
|
||
const currentActId = ref('1');
|
||
const verifyCodeInput = ref('');
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
|
||
const isHost = computed(() => {
|
||
if (!activity.value) return false;
|
||
return Number(activity.value.creatorId) === currentUserId.value;
|
||
});
|
||
|
||
const myParticipantInfo = computed(() => {
|
||
if (!activity.value || !activity.value.participants) return null;
|
||
const uid = currentUserId.value;
|
||
return activity.value.participants.find(p => Number(p.userId) === uid || Number(p.id) === uid);
|
||
});
|
||
|
||
const isJoined = computed(() => !!myParticipantInfo.value);
|
||
|
||
const getStatusText = (status) => {
|
||
if (status === 'CANCELLED') return '已解散';
|
||
if (status === 'COMPLETED') return '已完成';
|
||
if (status === 'FULL') return '已满员';
|
||
if (status === 'IN_PROGRESS' || status === 'RUNNING') return '进行中';
|
||
return '招募中';
|
||
};
|
||
const onThemeChanged = (key) => {
|
||
currentThemeKey.value = key;
|
||
applyThemeToNativeUI(key);
|
||
};
|
||
|
||
onMounted(() => {
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
preloadSubscribeTemplates();
|
||
});
|
||
|
||
onShow(() => {
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
if (currentActId.value) {
|
||
loadLiveDetail(currentActId.value);
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('theme-change', onThemeChanged);
|
||
});
|
||
|
||
const stopPropagation = (e) => {
|
||
if (e && typeof e.stopPropagation === 'function') {
|
||
e.stopPropagation();
|
||
}
|
||
};
|
||
|
||
const activity = ref({
|
||
id: '',
|
||
title: '',
|
||
game: '',
|
||
payType: 'AA',
|
||
totalFee: 0,
|
||
perPersonFee: 0,
|
||
maxParticipants: 0,
|
||
currentParticipants: 0,
|
||
genderRequirement: 'ALL',
|
||
startTime: '',
|
||
location: '',
|
||
creatorId: 0,
|
||
creatorNickname: '',
|
||
creatorAvatar: '',
|
||
participants: [],
|
||
priceAdjustHistory: []
|
||
});
|
||
|
||
const formatGenderRequirement = (req) => {
|
||
if (req === 'FEMALE_ONLY' || req === '仅女生') return '仅女生';
|
||
if (req === 'MALE_ONLY' || req === '仅男生') return '仅男生';
|
||
return '不限性别';
|
||
};
|
||
|
||
useShare(() => {
|
||
const act = activity.value;
|
||
const title = act && act.title ? `【组局】${act.title}` : '同城热门组局活动邀你加入';
|
||
const timelineTitle = act && act.title ? `【同城组局】${act.title}` : '同城热门组局活动邀你加入';
|
||
return {
|
||
title,
|
||
timelineTitle,
|
||
path: '/pages/activity/detail',
|
||
params: { id: currentActId.value },
|
||
imageUrl: (act && (act.coverImage || act.image)) || ''
|
||
};
|
||
});
|
||
|
||
const loadLiveDetail = async (id) => {
|
||
try {
|
||
const detail = await getActivityDetail(id);
|
||
if (detail) {
|
||
activity.value = detail;
|
||
} else {
|
||
uni.showToast({ title: '组局不存在或已下架', icon: 'none' });
|
||
}
|
||
} catch (err) {
|
||
console.warn('[ActivityDetail] 获取详情失败:', err);
|
||
uni.showToast({ title: err.message || '组局加载失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
onLoad((query) => {
|
||
const targetId = (query && query.id) ? query.id : '1';
|
||
currentActId.value = targetId;
|
||
loadLiveDetail(targetId);
|
||
});
|
||
|
||
const adjustModalVisible = ref(false);
|
||
const modalMode = ref('APPEND'); // 'APPEND' | 'EXACT'
|
||
const appendDeltaInput = ref('300');
|
||
const newPriceInput = ref('800');
|
||
|
||
const openAppendModal = () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) return;
|
||
modalMode.value = 'APPEND';
|
||
appendDeltaInput.value = '300';
|
||
adjustModalVisible.value = true;
|
||
};
|
||
|
||
const openAdjustModal = () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) return;
|
||
modalMode.value = 'EXACT';
|
||
newPriceInput.value = String(currentPerPerson.value + 300);
|
||
adjustModalVisible.value = true;
|
||
};
|
||
|
||
const closeAdjustModal = () => {
|
||
adjustModalVisible.value = false;
|
||
};
|
||
|
||
const switchModalMode = (mode) => {
|
||
modalMode.value = mode;
|
||
if (mode === 'APPEND') {
|
||
appendDeltaInput.value = '300';
|
||
} else {
|
||
newPriceInput.value = String(targetPerPerson.value);
|
||
}
|
||
};
|
||
|
||
const setQuickPrice = (val) => {
|
||
newPriceInput.value = String(val);
|
||
};
|
||
|
||
const joinedCount = computed(() => {
|
||
if (activity.value && Array.isArray(activity.value.participants)) {
|
||
return activity.value.participants.length;
|
||
}
|
||
return 0;
|
||
});
|
||
|
||
const currentPerPerson = computed(() => {
|
||
return Number(activity.value.perPersonFee) || 0;
|
||
});
|
||
|
||
const appendQuickOptions = computed(() => {
|
||
const base = currentPerPerson.value;
|
||
return [
|
||
{ delta: 100, sub: `加至¥${base + 100}` },
|
||
{ delta: 200, sub: `加至¥${base + 200}` },
|
||
{ delta: 300, sub: `加至¥${base + 300}` },
|
||
{ delta: 500, sub: '顶格加急' }
|
||
];
|
||
});
|
||
|
||
const exactQuickOptions = computed(() => {
|
||
const base = currentPerPerson.value;
|
||
const downPrice = Math.max(10, Math.round((base * 0.8) / 10) * 10);
|
||
return [
|
||
{ price: base + 100, sub: `提至¥${base + 100}`, isDown: false },
|
||
{ price: base + 200, sub: '推荐加急', isDown: false },
|
||
{ price: base + 300, sub: '极速成局', isDown: false },
|
||
{ price: downPrice, sub: '手动降价', isDown: true }
|
||
];
|
||
});
|
||
|
||
const targetPerPerson = computed(() => {
|
||
if (modalMode.value === 'APPEND') {
|
||
const delta = Number(appendDeltaInput.value) || 0;
|
||
return currentPerPerson.value + delta;
|
||
}
|
||
return Number(newPriceInput.value) || currentPerPerson.value;
|
||
});
|
||
|
||
const effectiveDiff = computed(() => {
|
||
return targetPerPerson.value - currentPerPerson.value;
|
||
});
|
||
|
||
const isValidAction = computed(() => {
|
||
if (modalMode.value === 'APPEND') {
|
||
const d = Number(appendDeltaInput.value);
|
||
return !isNaN(d) && d > 0;
|
||
}
|
||
const p = Number(newPriceInput.value);
|
||
return !isNaN(p) && p > 0;
|
||
});
|
||
|
||
const estimatedTotal = computed(() => {
|
||
const maxP = activity.value.maxParticipants || 4;
|
||
return targetPerPerson.value * maxP;
|
||
});
|
||
|
||
const confirmAdjustPrice = async () => {
|
||
const newP = targetPerPerson.value;
|
||
if (!newP || newP <= 0) {
|
||
uni.showToast({ title: '请输入有效的金额', icon: 'none' });
|
||
return;
|
||
}
|
||
const oldP = currentPerPerson.value;
|
||
const diff = newP - oldP;
|
||
|
||
try {
|
||
uni.showLoading({ title: '正在同步调整出价...' });
|
||
await adjustActivityPrice(activity.value.id, newP);
|
||
uni.hideLoading();
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '调价失败,请稍后重试', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
activity.value.perPersonFee = newP;
|
||
activity.value.totalFee = newP * (activity.value.maxParticipants || 4);
|
||
|
||
if (!activity.value.priceAdjustHistory) {
|
||
activity.value.priceAdjustHistory = [];
|
||
}
|
||
|
||
let note = '';
|
||
if (diff > 0 && activity.value.priceAdjustRule === 'UNIFIED_HIGH') {
|
||
const count = activity.value.participants ? activity.value.participants.length : 0;
|
||
note = `已为在榜 ${count} 位成员各补差 ¥${diff}`;
|
||
if (activity.value.participants) {
|
||
activity.value.participants.forEach(p => {
|
||
p.priceAtJoin = newP;
|
||
p.diffCompensated = (p.diffCompensated || 0) + diff;
|
||
});
|
||
}
|
||
} else if (diff < 0) {
|
||
note = '下调出价,老成员价格不受影响';
|
||
}
|
||
|
||
activity.value.priceAdjustHistory.unshift({
|
||
time: '刚刚',
|
||
action: diff > 0 ? (modalMode.value === 'APPEND' ? '追加赏金' : '手动提价') : '手动下调',
|
||
oldPrice: oldP,
|
||
newPrice: newP,
|
||
diff,
|
||
note
|
||
});
|
||
|
||
adjustModalVisible.value = false;
|
||
|
||
uni.showToast({
|
||
title: diff > 0
|
||
? `已成功追加金额!单人出价升至¥${newP}/人`
|
||
: `已成功调整出价为¥${newP}/人`,
|
||
icon: 'success',
|
||
duration: 2000
|
||
});
|
||
};
|
||
|
||
const copyLocation = () => {
|
||
uni.setClipboardData({
|
||
data: activity.value.location,
|
||
success: () => {
|
||
uni.showToast({ title: '活动地点已复制', icon: 'success' });
|
||
}
|
||
});
|
||
};
|
||
|
||
const openHostProfile = () => {
|
||
const hostId = Number(activity.value.creatorId) || 0;
|
||
if (!hostId) {
|
||
uni.showToast({ title: '发起人信息不完整', icon: 'none' });
|
||
return;
|
||
}
|
||
const hostName = activity.value.creatorNickname || '同城用户';
|
||
const hostAvatar = activity.value.creatorAvatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150';
|
||
const hostSnapshot = {
|
||
id: hostId,
|
||
userId: hostId,
|
||
nickname: hostName,
|
||
avatar: hostAvatar,
|
||
rankTag: '发起人',
|
||
levelTag: '',
|
||
game: activity.value.game || '',
|
||
city: activity.value.location || '',
|
||
bio: activity.value.title ? `【${activity.value.title}】发起人` : '',
|
||
photos: hostAvatar ? [hostAvatar] : []
|
||
};
|
||
uni.setStorageSync('ACTIVE_AUTHOR_PROFILE', hostSnapshot);
|
||
uni.navigateTo({
|
||
url: `/pages/expert/detail?id=${encodeURIComponent(String(hostId))}&name=${encodeURIComponent(hostName)}&avatar=${encodeURIComponent(hostAvatar)}&tag=${encodeURIComponent('发起人')}&city=${encodeURIComponent(hostSnapshot.city)}`
|
||
});
|
||
};
|
||
|
||
const goToMyTicket = (code) => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
|
||
return;
|
||
}
|
||
const c = code || ((activity.value.participants || []).find(p => Number(p.userId) === Number(currentUserId.value)) || {}).verifyCode || '';
|
||
uni.navigateTo({
|
||
url: `/pages/activity/my-ticket?id=${activity.value.id}${c ? `&code=${encodeURIComponent(c)}` : ''}`
|
||
});
|
||
};
|
||
|
||
const handleHostVerify = async () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
|
||
return;
|
||
}
|
||
const code = (verifyCodeInput.value || '').trim();
|
||
if (!code || code.length !== 4) {
|
||
uni.showToast({ title: '请输入4位数字核销码', icon: 'none' });
|
||
return;
|
||
}
|
||
try {
|
||
uni.showLoading({ title: '正在核销签到...' });
|
||
await verifyActivityCheckin(activity.value.id, code, currentUserId.value);
|
||
uni.hideLoading();
|
||
verifyCodeInput.value = '';
|
||
uni.showToast({ title: '核销成功!搭子已签到', icon: 'success' });
|
||
loadLiveDetail(activity.value.id);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '核销失败,核销码无效或已核销', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
const handleHostCancel = () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
|
||
return;
|
||
}
|
||
const participants = activity.value.participants || [];
|
||
const count = participants.length;
|
||
const isAA = activity.value.payType === 'AA';
|
||
const totalRefund = isAA ? count * Number(activity.value.aaFee || 0) : 0;
|
||
|
||
let content = '当前暂无搭子报名,解散后将立即撤回本组局。是否确认解散?';
|
||
if (count > 0) {
|
||
if (totalRefund > 0) {
|
||
content = `当前已有 ${count} 位搭子报名,解散后将全额退款共 ¥${totalRefund} 至搭子钱包余额。此操作不可撤销,是否确认解散?`;
|
||
} else {
|
||
content = `当前已有 ${count} 位搭子报名,解散后席位将自动释放并通知搭子。此操作不可撤销,是否确认解散?`;
|
||
}
|
||
}
|
||
|
||
uni.showModal({
|
||
title: '确认解散组局?',
|
||
content,
|
||
confirmText: '确认解散',
|
||
confirmColor: '#EF4444',
|
||
cancelText: '再想想',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
uni.showLoading({ title: '正在解散并全额退款...' });
|
||
await cancelActivity(activity.value.id, currentUserId.value, '局长主动解散组局');
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '组局已成功解散', icon: 'success' });
|
||
activity.value.status = 'CANCELLED';
|
||
loadLiveDetail(activity.value.id);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '解散失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleLeave = () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
|
||
return;
|
||
}
|
||
const isAA = activity.value.payType === 'AA';
|
||
const fee = isAA ? Number(activity.value.aaFee || 0) : 0;
|
||
|
||
uni.showModal({
|
||
title: '确认申请退席?',
|
||
content: `退席后席位将自动释放${fee > 0 ? `,已支付的分摊款 ¥${fee} 将全额原路退还至您的钱包余额` : ''}。是否确认退出?`,
|
||
confirmText: '确认退席',
|
||
confirmColor: '#EF4444',
|
||
cancelText: '取消',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
uni.showLoading({ title: '正在办理退席...' });
|
||
await leaveActivity(activity.value.id, currentUserId.value);
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '已退席,款项已退回钱包', icon: 'success' });
|
||
loadLiveDetail(activity.value.id);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '退出失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleJoin = () => {
|
||
if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) {
|
||
return;
|
||
}
|
||
const cachedUser = uni.getStorageSync('userInfo') || uni.getStorageSync('USER_INFO') || {};
|
||
const userGender = String(cachedUser.gender || '').trim();
|
||
const reqGender = activity.value.genderRequirement;
|
||
if ((reqGender === 'FEMALE_ONLY' || reqGender === '仅女生') && userGender === '男') {
|
||
uni.showToast({ title: '该组局仅限女生报名参与', icon: 'none' });
|
||
return;
|
||
}
|
||
if ((reqGender === 'MALE_ONLY' || reqGender === '仅男生') && userGender === '女') {
|
||
uni.showToast({ title: '该组局仅限男生报名参与', icon: 'none' });
|
||
return;
|
||
}
|
||
uni.showModal({
|
||
title: '确认报名',
|
||
content: `报名该【${activity.value.payType === 'AA' ? 'AA制' : '房主请客'}】组局,${activity.value.payType === 'AA' ? `需支付分摊款 ¥${activity.value.aaFee}` : '由房主全额买单请客'}。报名后自动生成4位入局核销码。`,
|
||
confirmText: '确认报名',
|
||
confirmColor: '#6366F1',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
// 报名前请求一次性订阅授权(组局报名通知)
|
||
requestSubscribeFor('activity');
|
||
uni.showLoading({ title: '正在报名生成核销码...' });
|
||
let code = '';
|
||
try {
|
||
const fee = activity.value.payType === 'AA' ? activity.value.aaFee : 0;
|
||
const activeUid = normalizeUserId();
|
||
const joinRes = await joinActivity(activity.value.id, {
|
||
userId: activeUid,
|
||
payAmount: fee,
|
||
});
|
||
code = joinRes && joinRes.verifyCode ? joinRes.verifyCode : '';
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '报名成功', icon: 'success' });
|
||
setTimeout(() => {
|
||
goToMyTicket(code);
|
||
}, 1000);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: err.message || '报名失败,请重试', icon: 'none' });
|
||
}
|
||
}
|
||
}
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.detail-container {
|
||
min-height: 100vh;
|
||
background-color: #0A0F1D;
|
||
padding: 24rpx 32rpx calc(160rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.cancelled-banner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
background: rgba(239, 68, 68, 0.15);
|
||
border: 1px solid rgba(239, 68, 68, 0.35);
|
||
border-radius: 20rpx;
|
||
padding: 20rpx 24rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.cancelled-icon {
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.cancelled-text-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
|
||
.cancelled-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #F87171;
|
||
}
|
||
|
||
.cancelled-desc {
|
||
font-size: 22rpx;
|
||
color: #FCA5A5;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
}
|
||
|
||
.hero-card {
|
||
background: linear-gradient(135deg, #1E1B4B 0%, #151D30 100%);
|
||
border: 1px solid rgba(99, 102, 241, 0.25);
|
||
border-radius: 28rpx;
|
||
padding: 32rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
&.is-cancelled {
|
||
opacity: 0.8;
|
||
filter: grayscale(0.15);
|
||
}
|
||
|
||
.badge-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.game-tag {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: #F8FAFC;
|
||
font-size: 22rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.mode-tag {
|
||
font-size: 22rpx;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
|
||
&.aa-mode {
|
||
background: rgba(16, 185, 129, 0.2);
|
||
color: #10B981;
|
||
border: 1px solid rgba(16, 185, 129, 0.4);
|
||
}
|
||
&.project-mode {
|
||
background: rgba(245, 158, 11, 0.2);
|
||
color: #F59E0B;
|
||
border: 1px solid rgba(245, 158, 11, 0.4);
|
||
}
|
||
}
|
||
|
||
.status-tag {
|
||
margin-left: auto;
|
||
font-size: 20rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
|
||
&.RECRUITING {
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #818CF8;
|
||
}
|
||
&.FULL {
|
||
background: rgba(245, 158, 11, 0.2);
|
||
color: #F59E0B;
|
||
}
|
||
&.IN_PROGRESS,
|
||
&.RUNNING {
|
||
background: rgba(16, 185, 129, 0.2);
|
||
color: #10B981;
|
||
}
|
||
&.COMPLETED {
|
||
background: rgba(148, 163, 184, 0.2);
|
||
color: #94A3B8;
|
||
}
|
||
&.CANCELLED {
|
||
background: rgba(239, 68, 68, 0.2);
|
||
color: #F87171;
|
||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||
}
|
||
}
|
||
}
|
||
|
||
.act-title {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
line-height: 1.4;
|
||
margin-bottom: 28rpx;
|
||
}
|
||
|
||
.fee-board {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
align-items: center;
|
||
background: rgba(10, 15, 29, 0.6);
|
||
border-radius: 18rpx;
|
||
padding: 20rpx 0;
|
||
|
||
.fee-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
.col-label {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
.col-val {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
|
||
&.highlight {
|
||
color: #10B981;
|
||
font-size: 34rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.fee-divider {
|
||
width: 1px;
|
||
height: 40rpx;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
}
|
||
|
||
.dynamic-price-banner {
|
||
margin-top: 24rpx;
|
||
padding: 22rpx 24rpx;
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 18rpx;
|
||
|
||
.dp-info {
|
||
width: 100%;
|
||
margin-right: 0;
|
||
|
||
.dp-tag-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 10rpx;
|
||
flex-wrap: nowrap;
|
||
|
||
.dp-badge {
|
||
background: #6366F1;
|
||
color: #FFF;
|
||
font-size: 20rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.dp-rule-tag {
|
||
color: #A5B4FC;
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
|
||
.dp-status-text {
|
||
font-size: 24rpx;
|
||
color: #F8FAFC;
|
||
font-weight: 500;
|
||
display: block;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
|
||
.dp-btn-group {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
align-items: center;
|
||
padding-top: 14rpx;
|
||
border-top: 1px dashed rgba(99, 102, 241, 0.22);
|
||
|
||
.btn-adjust-price {
|
||
flex: 1;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
height: 64rpx;
|
||
line-height: 64rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 32rpx;
|
||
margin: 0;
|
||
white-space: nowrap;
|
||
text-align: center;
|
||
|
||
&.btn-append {
|
||
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
|
||
color: #FFF;
|
||
box-shadow: 0 4rpx 12rpx rgba(217, 119, 6, 0.35);
|
||
}
|
||
|
||
&.btn-edit {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
color: #CBD5E1;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.section-card {
|
||
background-color: #151D30;
|
||
border-radius: 24rpx;
|
||
padding: 28rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.sec-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.sec-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
.sec-sub {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.creator-box {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border-radius: 16rpx;
|
||
padding: 16rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.creator-avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 40rpx;
|
||
margin-right: 16rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.creator-avatar-empty {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(148, 163, 184, 0.18);
|
||
border: 1px solid rgba(148, 163, 184, 0.3);
|
||
|
||
.creator-avatar-placeholder {
|
||
font-size: 30rpx;
|
||
color: #94A3B8;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
|
||
.creator-info {
|
||
flex: 1;
|
||
|
||
.creator-name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 6rpx;
|
||
|
||
.creator-name {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #F8FAFC;
|
||
}
|
||
.host-badge {
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #818CF8;
|
||
font-size: 18rpx;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
|
||
.creator-sub {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.btn-chat {
|
||
background: rgba(99, 102, 241, 0.15);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
color: #818CF8;
|
||
font-size: 22rpx;
|
||
padding: 0 24rpx;
|
||
height: 52rpx;
|
||
line-height: 50rpx;
|
||
border-radius: 26rpx;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.info-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
|
||
.info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 24rpx;
|
||
|
||
.info-label {
|
||
color: #64748B;
|
||
}
|
||
.info-val {
|
||
color: #E2E8F0;
|
||
font-weight: 500;
|
||
|
||
&.green-text {
|
||
color: #10B981;
|
||
}
|
||
|
||
&.female-text {
|
||
color: #EC4899;
|
||
font-weight: 600;
|
||
}
|
||
|
||
&.male-text {
|
||
color: #38BDF8;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.copy-val {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.copy-btn {
|
||
color: #6366F1;
|
||
font-size: 22rpx;
|
||
text-decoration: underline;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.member-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
|
||
.member-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12rpx 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.member-avatar {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 32rpx;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.member-info {
|
||
flex: 1;
|
||
|
||
.member-top-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.member-name {
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.member-price-tag {
|
||
font-size: 18rpx;
|
||
color: #F59E0B;
|
||
background: rgba(245, 158, 11, 0.15);
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
|
||
.member-sub-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-top: 4rpx;
|
||
|
||
.member-seat {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
|
||
.member-comp-tag {
|
||
font-size: 18rpx;
|
||
color: #10B981;
|
||
background: rgba(16, 185, 129, 0.15);
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
&.my-member-item {
|
||
background: rgba(99, 102, 241, 0.08);
|
||
border-radius: 12rpx;
|
||
padding: 12rpx 14rpx;
|
||
}
|
||
|
||
.my-badge {
|
||
font-size: 18rpx;
|
||
color: #6366F1;
|
||
background: rgba(99, 102, 241, 0.18);
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
font-weight: 600;
|
||
border: 1px solid rgba(99, 102, 241, 0.35);
|
||
}
|
||
|
||
.member-status-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.btn-micro-leave {
|
||
font-size: 20rpx;
|
||
color: #EF4444;
|
||
background: rgba(239, 68, 68, 0.12);
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 10rpx;
|
||
}
|
||
}
|
||
|
||
.checkin-badge {
|
||
font-size: 20rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 12rpx;
|
||
background: rgba(100, 116, 139, 0.2);
|
||
color: #94A3B8;
|
||
|
||
&.checked {
|
||
background: rgba(16, 185, 129, 0.15);
|
||
color: #10B981;
|
||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
&.host-verify-card {
|
||
border: 1px solid rgba(99, 102, 241, 0.35);
|
||
background: linear-gradient(135deg, rgba(30, 27, 75, 0.6) 0%, rgba(21, 29, 48, 0.95) 100%);
|
||
|
||
.title-with-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.host-pill {
|
||
font-size: 18rpx;
|
||
background: rgba(245, 158, 11, 0.18);
|
||
color: #F59E0B;
|
||
border: 1px solid rgba(245, 158, 11, 0.4);
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.verify-action-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin: 20rpx 0 14rpx;
|
||
|
||
.verify-input-wrap {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(10, 15, 29, 0.7);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 16rpx;
|
||
padding: 0 20rpx;
|
||
height: 76rpx;
|
||
|
||
.verify-svg {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
margin-right: 12rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.verify-code-input {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
}
|
||
|
||
.btn-verify-now {
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFFFFF;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
padding: 0 28rpx;
|
||
height: 76rpx;
|
||
line-height: 76rpx;
|
||
border-radius: 16rpx;
|
||
margin: 0;
|
||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.35);
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.verify-helper-tip {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
|
||
&.history-card {
|
||
.history-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14rpx;
|
||
|
||
.history-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 14rpx;
|
||
padding: 14rpx 18rpx;
|
||
|
||
.history-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.history-action {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
|
||
&.up {
|
||
color: #EF4444;
|
||
}
|
||
&.down {
|
||
color: #10B981;
|
||
}
|
||
}
|
||
|
||
.history-time {
|
||
font-size: 18rpx;
|
||
color: #64748B;
|
||
margin-top: 2rpx;
|
||
}
|
||
}
|
||
|
||
.history-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
|
||
.history-diff {
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
|
||
&.diff-up {
|
||
color: #F8FAFC;
|
||
}
|
||
&.diff-down {
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.history-note {
|
||
font-size: 18rpx;
|
||
color: #10B981;
|
||
margin-top: 2rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-members {
|
||
text-align: center;
|
||
padding: 30rpx 0;
|
||
color: #64748B;
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
.adjust-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.72);
|
||
backdrop-filter: blur(8px);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
|
||
.adjust-modal-content {
|
||
width: 100%;
|
||
max-width: 450px;
|
||
background: #151D30;
|
||
border-top-left-radius: 40rpx;
|
||
border-top-right-radius: 40rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
padding: 20rpx 32rpx calc(36rpx + env(safe-area-inset-bottom));
|
||
box-sizing: border-box;
|
||
|
||
.sheet-grabber {
|
||
width: 68rpx;
|
||
height: 8rpx;
|
||
border-radius: 4rpx;
|
||
background: rgba(255, 255, 255, 0.16);
|
||
margin: 0 auto 20rpx;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 24rpx;
|
||
|
||
.modal-title-box {
|
||
flex: 1;
|
||
padding-right: 16rpx;
|
||
|
||
.modal-title {
|
||
font-size: 34rpx;
|
||
font-weight: 800;
|
||
color: #F8FAFC;
|
||
display: block;
|
||
letter-spacing: 0.5rpx;
|
||
}
|
||
.modal-subtitle {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
margin-top: 6rpx;
|
||
display: block;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
|
||
.modal-close-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
|
||
.close-icon {
|
||
font-size: 36rpx;
|
||
line-height: 1;
|
||
color: #94A3B8;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-mode-tabs {
|
||
display: flex;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 18rpx;
|
||
padding: 6rpx;
|
||
margin-bottom: 24rpx;
|
||
gap: 8rpx;
|
||
|
||
.mode-tab {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #94A3B8;
|
||
padding: 15rpx 0;
|
||
border-radius: 14rpx;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active {
|
||
background: #6366F1;
|
||
color: #FFFFFF;
|
||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.35);
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-body {
|
||
.modal-stat-box {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 20rpx;
|
||
padding: 20rpx 12rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.stat-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.stat-label {
|
||
font-size: 21rpx;
|
||
color: #94A3B8;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
.stat-val {
|
||
font-size: 30rpx;
|
||
font-weight: 800;
|
||
color: #F8FAFC;
|
||
font-variant-numeric: tabular-nums;
|
||
|
||
&.highlight {
|
||
color: #6366F1;
|
||
}
|
||
.unit {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
color: #94A3B8;
|
||
margin-left: 2rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.stat-divider {
|
||
width: 1px;
|
||
height: 44rpx;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
}
|
||
|
||
.modal-input-group {
|
||
margin-bottom: 20rpx;
|
||
|
||
.group-label {
|
||
font-size: 25rpx;
|
||
font-weight: 700;
|
||
color: #CBD5E1;
|
||
display: block;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.input-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 2rpx solid rgba(99, 102, 241, 0.45);
|
||
border-radius: 18rpx;
|
||
padding: 0 24rpx;
|
||
height: 88rpx;
|
||
|
||
.currency-prefix {
|
||
font-size: 34rpx;
|
||
font-weight: 800;
|
||
color: #6366F1;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.modal-input {
|
||
flex: 1;
|
||
font-size: 36rpx;
|
||
font-weight: 800;
|
||
color: #F8FAFC;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.input-unit {
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
color: #94A3B8;
|
||
margin-left: 12rpx;
|
||
}
|
||
|
||
.ph {
|
||
color: #475569;
|
||
font-size: 26rpx;
|
||
font-weight: normal;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-options {
|
||
display: flex;
|
||
gap: 14rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.quick-btn {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4rpx;
|
||
padding: 14rpx 6rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16rpx;
|
||
color: #CBD5E1;
|
||
transition: all 0.18s ease;
|
||
|
||
.q-val {
|
||
font-size: 25rpx;
|
||
font-weight: 800;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.q-sub {
|
||
font-size: 19rpx;
|
||
font-weight: 500;
|
||
line-height: 1.2;
|
||
opacity: 0.78;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
&.highlight {
|
||
background: rgba(99, 102, 241, 0.16);
|
||
border-color: #6366F1;
|
||
color: #818CF8;
|
||
|
||
.q-sub {
|
||
opacity: 0.95;
|
||
}
|
||
}
|
||
&.down {
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-change-box {
|
||
background: rgba(99, 102, 241, 0.08);
|
||
border: 1px dashed rgba(99, 102, 241, 0.32);
|
||
border-radius: 20rpx;
|
||
padding: 20rpx 22rpx;
|
||
margin-bottom: 28rpx;
|
||
|
||
.calc-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.calc-title {
|
||
font-size: 25rpx;
|
||
font-weight: 700;
|
||
color: #818CF8;
|
||
}
|
||
|
||
.calc-pill {
|
||
font-size: 20rpx;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
font-weight: 700;
|
||
|
||
&.up {
|
||
background: rgba(239, 68, 68, 0.18);
|
||
color: #F87171;
|
||
}
|
||
&.down {
|
||
background: rgba(16, 185, 129, 0.18);
|
||
color: #10B981;
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-summary-grid {
|
||
display: flex;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
border-radius: 14rpx;
|
||
padding: 14rpx 18rpx;
|
||
margin-bottom: 14rpx;
|
||
|
||
.calc-summary-cell {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
|
||
.cell-label {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
}
|
||
|
||
.cell-val-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10rpx;
|
||
|
||
.cell-price {
|
||
font-size: 28rpx;
|
||
font-weight: 800;
|
||
color: #F8FAFC;
|
||
font-variant-numeric: tabular-nums;
|
||
|
||
&.total {
|
||
color: #10B981;
|
||
}
|
||
|
||
.cell-unit {
|
||
font-size: 19rpx;
|
||
font-weight: 500;
|
||
color: #94A3B8;
|
||
margin-left: 2rpx;
|
||
}
|
||
}
|
||
|
||
.cell-origin {
|
||
font-size: 19rpx;
|
||
color: #64748B;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.cell-delta {
|
||
font-size: 19rpx;
|
||
font-weight: 700;
|
||
padding: 1rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
|
||
&.up {
|
||
color: #10B981;
|
||
background: rgba(16, 185, 129, 0.12);
|
||
}
|
||
&.down {
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-summary-divider {
|
||
width: 1px;
|
||
height: 44rpx;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
margin: 0 18rpx;
|
||
}
|
||
}
|
||
|
||
.calc-notes {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
|
||
.note-row {
|
||
font-size: 21rpx;
|
||
color: #94A3B8;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-footer {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
|
||
.footer-btn {
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
border-radius: 42rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
margin: 0;
|
||
|
||
&::after {
|
||
border: none;
|
||
}
|
||
|
||
&.cancel-btn {
|
||
flex: 0.72;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: #94A3B8;
|
||
}
|
||
|
||
&.confirm-btn {
|
||
flex: 1.28;
|
||
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
|
||
color: #FFF;
|
||
box-shadow: 0 6rpx 18rpx rgba(99, 102, 241, 0.35);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: calc(120rpx + env(safe-area-inset-bottom));
|
||
padding: 0 32rpx env(safe-area-inset-bottom);
|
||
box-sizing: border-box;
|
||
background-color: rgba(21, 29, 48, 0.95);
|
||
backdrop-filter: blur(12px);
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
z-index: 100;
|
||
max-width: 450px;
|
||
margin: 0 auto;
|
||
|
||
.ticket-link {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 120rpx;
|
||
|
||
.ticket-svg {
|
||
width: 38rpx;
|
||
height: 38rpx;
|
||
}
|
||
.ticket-text {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
|
||
.btn-join {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border-radius: 44rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.35);
|
||
margin: 0;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.btn-host-secondary {
|
||
flex: 1;
|
||
background: rgba(99, 102, 241, 0.15);
|
||
color: #818CF8;
|
||
border: 1px solid rgba(99, 102, 241, 0.4);
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border-radius: 44rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
margin: 0;
|
||
}
|
||
|
||
.btn-host-danger {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
|
||
color: #FFFFFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border-radius: 44rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
margin: 0;
|
||
box-shadow: 0 4rpx 20rpx rgba(239, 68, 68, 0.4);
|
||
}
|
||
|
||
.btn-leave {
|
||
flex: 1;
|
||
background: rgba(239, 68, 68, 0.15);
|
||
color: #F87171;
|
||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
border-radius: 44rpx;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
margin: 0;
|
||
}
|
||
|
||
.btn-action-full {
|
||
flex: 1;
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
border-radius: 44rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
|
||
&.btn-disabled {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
color: #94A3B8;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-container.theme-warm {
|
||
background-color: #FDFBF7;
|
||
|
||
.cancelled-banner {
|
||
background: #FEF2F2;
|
||
border-color: #FECACA;
|
||
.cancelled-text-box {
|
||
.cancelled-title {
|
||
color: #DC2626;
|
||
}
|
||
.cancelled-desc {
|
||
color: #EF4444;
|
||
}
|
||
}
|
||
}
|
||
|
||
.hero-card {
|
||
background: linear-gradient(135deg, #FFF6EE 0%, #FFFFFF 100%);
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 6rpx 24rpx rgba(28, 25, 23, 0.05);
|
||
|
||
.badge-row {
|
||
.game-tag {
|
||
background: #FFEDD5;
|
||
color: #EA580C;
|
||
border: 1px solid #FED7AA;
|
||
font-weight: 600;
|
||
}
|
||
.mode-tag {
|
||
&.aa-mode {
|
||
background: rgba(16, 185, 129, 0.12);
|
||
color: #059669;
|
||
border-color: rgba(16, 185, 129, 0.35);
|
||
}
|
||
&.project-mode {
|
||
background: #FEF3C7;
|
||
color: #D97706;
|
||
border-color: #FDE68A;
|
||
}
|
||
}
|
||
.status-tag {
|
||
&.RECRUITING {
|
||
background: #FFF7ED;
|
||
color: #EA580C;
|
||
border: 1px solid #FDBA74;
|
||
}
|
||
&.COMPLETED {
|
||
background: #F5F5F4;
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.act-title {
|
||
color: #1C1917;
|
||
}
|
||
.fee-board {
|
||
background: #F8F5F0;
|
||
border: 1px solid #F0ECE4;
|
||
.fee-col {
|
||
.col-label {
|
||
color: #78716C;
|
||
}
|
||
.col-val {
|
||
color: #1C1917;
|
||
&.highlight {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
.fee-divider {
|
||
background: #E7E5E4;
|
||
}
|
||
}
|
||
.dynamic-price-banner {
|
||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||
border-color: #FDBA74;
|
||
.dp-info {
|
||
.dp-tag-row {
|
||
.dp-badge {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 2rpx 8rpx rgba(234, 88, 12, 0.25);
|
||
}
|
||
.dp-rule-tag {
|
||
color: #C2410C;
|
||
}
|
||
}
|
||
.dp-status-text {
|
||
color: #44403C;
|
||
}
|
||
}
|
||
.dp-btn-group {
|
||
border-top-color: rgba(234, 88, 12, 0.2);
|
||
.btn-adjust-price {
|
||
&.btn-append {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
color: #FFFFFF;
|
||
box-shadow: 0 4rpx 14rpx rgba(234, 88, 12, 0.3);
|
||
}
|
||
&.btn-edit {
|
||
background: #FFFFFF;
|
||
border-color: #FDBA74;
|
||
color: #C2410C;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.section-card {
|
||
background-color: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 6rpx 24rpx rgba(28, 25, 23, 0.05);
|
||
|
||
.sec-header {
|
||
.sec-title {
|
||
color: #1C1917;
|
||
}
|
||
.sec-sub {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
.creator-box {
|
||
background: #F8F5F0;
|
||
border: 1px solid #F0ECE4;
|
||
.creator-info {
|
||
.creator-name-row {
|
||
.creator-name {
|
||
color: #1C1917;
|
||
}
|
||
.host-badge {
|
||
background: #FFEDD5;
|
||
color: #EA580C;
|
||
border: 1px solid #FED7AA;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
.creator-sub {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
.btn-chat {
|
||
background: #FFF7ED;
|
||
border-color: #FDBA74;
|
||
color: #EA580C;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
.info-list .info-row {
|
||
.info-label {
|
||
color: #78716C;
|
||
}
|
||
.info-val {
|
||
color: #1C1917;
|
||
&.green-text {
|
||
color: #059669;
|
||
}
|
||
&.female-text {
|
||
color: #DB2777;
|
||
font-weight: 600;
|
||
}
|
||
&.male-text {
|
||
color: #2563EB;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
.copy-val .copy-btn {
|
||
color: #EA580C;
|
||
background: #FFF7ED;
|
||
border: 1px solid #FED7AA;
|
||
padding: 2rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
.member-list .member-item {
|
||
border-bottom-color: #F5F2EC;
|
||
.member-info .member-top-line .member-name {
|
||
color: #1C1917;
|
||
}
|
||
&.my-member-item {
|
||
background: #FFF7ED;
|
||
border: 1px solid #FFEDD5;
|
||
}
|
||
.my-badge {
|
||
color: #EA580C;
|
||
background: #FFEDD5;
|
||
border-color: #FDBA74;
|
||
}
|
||
.checkin-badge {
|
||
background: #F5F5F4;
|
||
color: #78716C;
|
||
&.checked {
|
||
background: rgba(16, 185, 129, 0.12);
|
||
color: #059669;
|
||
border-color: rgba(16, 185, 129, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
&.host-verify-card {
|
||
background: linear-gradient(135deg, #FFF7ED 0%, #FFFFFF 100%);
|
||
border: 1px solid #FED7AA;
|
||
box-shadow: 0 6rpx 24rpx rgba(234, 88, 12, 0.06);
|
||
|
||
.sec-header {
|
||
.sec-title {
|
||
color: #1C1917;
|
||
}
|
||
.sec-sub {
|
||
color: #9A3412;
|
||
}
|
||
}
|
||
|
||
.title-with-badge .host-pill {
|
||
background: #FFEDD5;
|
||
color: #EA580C;
|
||
border-color: #FDBA74;
|
||
}
|
||
|
||
.verify-action-row {
|
||
.verify-input-wrap {
|
||
background: #FFFFFF;
|
||
border: 1px solid #FDBA74;
|
||
box-shadow: inset 0 2rpx 6rpx rgba(234, 88, 12, 0.04);
|
||
|
||
.verify-code-input {
|
||
color: #1C1917;
|
||
}
|
||
.ph {
|
||
color: #A8A29E;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.btn-verify-now {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
color: #FFFFFF;
|
||
box-shadow: 0 6rpx 16rpx rgba(234, 88, 12, 0.28);
|
||
}
|
||
}
|
||
|
||
.verify-helper-tip {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
|
||
&.history-card .history-list .history-item {
|
||
background: #FAFAF9;
|
||
border-color: #E7E5E4;
|
||
.history-right .history-diff {
|
||
color: #1C1917;
|
||
}
|
||
}
|
||
}
|
||
|
||
.adjust-modal-mask .adjust-modal-content {
|
||
background: #FFFFFF;
|
||
border-top-color: #F0ECE4;
|
||
box-shadow: 0 -12rpx 40rpx rgba(28, 25, 23, 0.08);
|
||
|
||
.sheet-grabber {
|
||
background: #E7E5E4;
|
||
}
|
||
|
||
.modal-header {
|
||
.modal-title {
|
||
color: #1C1917;
|
||
}
|
||
.modal-subtitle {
|
||
color: #78716C;
|
||
}
|
||
.modal-close-btn {
|
||
background: #F5F2EC;
|
||
border: 1px solid #E7E5E4;
|
||
.close-icon {
|
||
color: #57534E;
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-mode-tabs {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
|
||
.mode-tab {
|
||
color: #78716C;
|
||
|
||
&.active {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
color: #FFFFFF;
|
||
box-shadow: 0 4rpx 14rpx rgba(234, 88, 12, 0.32);
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-body {
|
||
.modal-stat-box {
|
||
background: #FAF8F5;
|
||
border-color: #EFECE6;
|
||
|
||
.stat-item {
|
||
.stat-label {
|
||
color: #78716C;
|
||
}
|
||
.stat-val {
|
||
color: #1C1917;
|
||
&.highlight {
|
||
color: #EA580C;
|
||
}
|
||
.unit {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.stat-divider {
|
||
background: #E7E5E4;
|
||
}
|
||
}
|
||
|
||
.modal-input-group {
|
||
.group-label {
|
||
color: #1C1917;
|
||
}
|
||
.input-wrap {
|
||
background: #FFFBF7;
|
||
border-color: #F97316;
|
||
box-shadow: 0 2rpx 10rpx rgba(234, 88, 12, 0.06);
|
||
|
||
.currency-prefix {
|
||
color: #EA580C;
|
||
}
|
||
.modal-input {
|
||
color: #1C1917;
|
||
}
|
||
.input-unit {
|
||
color: #78716C;
|
||
}
|
||
.ph {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-options .quick-btn {
|
||
background: #F8F5F0;
|
||
border-color: #E7E5E4;
|
||
color: #44403C;
|
||
|
||
.q-sub {
|
||
color: #78716C;
|
||
}
|
||
|
||
&.highlight {
|
||
background: #FFF7ED;
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.12);
|
||
|
||
.q-sub {
|
||
color: #C2410C;
|
||
}
|
||
}
|
||
|
||
&.down {
|
||
color: #57534E;
|
||
}
|
||
}
|
||
|
||
.calc-change-box {
|
||
background: linear-gradient(135deg, #FFF7ED 0%, #FFFAF3 100%);
|
||
border-color: #FDBA74;
|
||
|
||
.calc-top {
|
||
.calc-title {
|
||
color: #C2410C;
|
||
}
|
||
.calc-pill {
|
||
&.up {
|
||
background: #FFEDD5;
|
||
color: #EA580C;
|
||
border: 1px solid #FDBA74;
|
||
}
|
||
&.down {
|
||
background: rgba(16, 185, 129, 0.12);
|
||
color: #059669;
|
||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-summary-grid {
|
||
background: #FFFFFF;
|
||
border-color: #FED7AA;
|
||
|
||
.calc-summary-cell {
|
||
.cell-label {
|
||
color: #78716C;
|
||
}
|
||
.cell-val-row {
|
||
.cell-price {
|
||
color: #1C1917;
|
||
&.total {
|
||
color: #EA580C;
|
||
}
|
||
.cell-unit {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
.cell-origin {
|
||
color: #A8A29E;
|
||
}
|
||
.cell-delta {
|
||
&.up {
|
||
color: #059669;
|
||
background: rgba(16, 185, 129, 0.12);
|
||
}
|
||
&.down {
|
||
color: #78716C;
|
||
background: #F5F5F4;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.calc-summary-divider {
|
||
background: #FED7AA;
|
||
}
|
||
}
|
||
|
||
.calc-notes {
|
||
.note-row {
|
||
color: #57534E;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.modal-footer {
|
||
.cancel-btn {
|
||
background: #F8F5F0;
|
||
border: 1px solid #E7E5E4;
|
||
color: #57534E;
|
||
}
|
||
.confirm-btn {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 6rpx 20rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
background-color: rgba(253, 251, 247, 0.96);
|
||
border-top-color: #F0ECE4;
|
||
.ticket-link .ticket-text {
|
||
color: #57534E;
|
||
}
|
||
.btn-join {
|
||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||
box-shadow: 0 4rpx 20rpx rgba(234, 88, 12, 0.35);
|
||
}
|
||
.btn-host-secondary {
|
||
background: #FFF7ED;
|
||
color: #EA580C;
|
||
border-color: #FDBA74;
|
||
box-shadow: 0 4rpx 12rpx rgba(234, 88, 12, 0.08);
|
||
}
|
||
}
|
||
}
|
||
/* ===== Premium visual layer: 组局详情 ===== */
|
||
.detail-container {
|
||
background:
|
||
radial-gradient(110% 42% at 50% -6%, rgba(99, 102, 241, 0.18), transparent 62%),
|
||
var(--surface-0);
|
||
|
||
.hero-card {
|
||
background:
|
||
radial-gradient(circle at 88% 8%, rgba(217, 164, 65, 0.12), transparent 46%),
|
||
linear-gradient(145deg, #1B2340 0%, #111827 58%, #0D1424 100%);
|
||
border-color: rgba(129, 140, 248, 0.22);
|
||
box-shadow:
|
||
inset 0 1rpx 0 rgba(255, 255, 255, 0.06),
|
||
0 18rpx 44rpx rgba(0, 0, 0, 0.28);
|
||
animation: detailSectionEnter 0.45s var(--ease-out) both;
|
||
|
||
.badge-row .game-tag,
|
||
.badge-row .mode-tag,
|
||
.badge-row .status-tag {
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.hero-title {
|
||
font-size: 38rpx;
|
||
line-height: 1.4;
|
||
letter-spacing: 0.3rpx;
|
||
}
|
||
|
||
.hero-price {
|
||
font-family: var(--font-num, monospace);
|
||
}
|
||
}
|
||
|
||
.section-card {
|
||
background: linear-gradient(180deg, rgba(22, 31, 51, 0.82) 0%, rgba(15, 22, 38, 0.9) 100%);
|
||
border-color: var(--hairline);
|
||
box-shadow: 0 12rpx 30rpx rgba(0, 0, 0, 0.16);
|
||
animation: detailSectionEnter 0.5s var(--ease-out) both;
|
||
|
||
.sec-header .sec-title {
|
||
letter-spacing: 0.2rpx;
|
||
}
|
||
|
||
.info-row {
|
||
border-bottom-color: rgba(255, 255, 255, 0.045);
|
||
}
|
||
}
|
||
|
||
.creator-box {
|
||
.creator-name {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.host-badge {
|
||
background: rgba(217, 164, 65, 0.1);
|
||
border: 1rpx solid rgba(217, 164, 65, 0.28);
|
||
color: #F1C878;
|
||
}
|
||
|
||
.btn-chat {
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border-color: var(--hairline);
|
||
color: #CBD5E1;
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
background: rgba(10, 15, 29, 0.9);
|
||
backdrop-filter: blur(20px);
|
||
border-top: 1rpx solid rgba(255, 255, 255, 0.07);
|
||
box-shadow: 0 -14rpx 36rpx rgba(0, 0, 0, 0.28);
|
||
|
||
.btn-join,
|
||
.btn-host-secondary,
|
||
.btn-host-danger,
|
||
.btn-leave,
|
||
.ticket-link {
|
||
border-radius: 999rpx;
|
||
transition: transform var(--motion-fast, 120ms) var(--ease-out, ease-out);
|
||
}
|
||
|
||
.btn-join {
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
box-shadow: 0 10rpx 24rpx rgba(16, 185, 129, 0.28);
|
||
}
|
||
|
||
.btn-join:active,
|
||
.btn-host-secondary:active,
|
||
.btn-host-danger:active,
|
||
.btn-leave:active {
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
}
|
||
|
||
.detail-container.theme-warm {
|
||
background:
|
||
radial-gradient(100% 40% at 50% -6%, rgba(234, 88, 12, 0.08), transparent 62%),
|
||
#FDFBF7;
|
||
|
||
.hero-card {
|
||
background:
|
||
radial-gradient(circle at 88% 8%, rgba(234, 88, 12, 0.06), transparent 46%),
|
||
linear-gradient(145deg, #FFFFFF 0%, #FFFBF5 100%);
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 16rpx 38rpx rgba(28, 25, 23, 0.06);
|
||
|
||
.hero-title {
|
||
color: #1C1917;
|
||
}
|
||
}
|
||
|
||
.section-card {
|
||
background: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 12rpx 28rpx rgba(28, 25, 23, 0.05);
|
||
}
|
||
|
||
.creator-box {
|
||
.btn-chat {
|
||
background: #F8F5F0;
|
||
border-color: #F0ECE4;
|
||
color: #57534E;
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
background: rgba(255, 255, 255, 0.92);
|
||
border-top-color: #F0ECE4;
|
||
box-shadow: 0 -12rpx 30rpx rgba(28, 25, 23, 0.07);
|
||
}
|
||
}
|
||
|
||
@keyframes detailSectionEnter {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(14rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
</style>
|