- DB: companions.voice_intro_url;仓储 updateCompanionVoice;详情与 /api/expert/me 返回 voiceIntroUrl/voiceDuration - 后端: POST /api/companions/:id/voice(音频白名单 mp3/m4a/aac/wav/ogg、5MB、≤60s、限流、仅本人或管理员) - 小程序: api.uploadVoiceIntro(uni.uploadFile + 用户令牌);工作台新增「语音介绍」卡片(录制/重录/试听,60s mp3);详情页改为 uni.createInnerAudioContext 真实播放,卸载销毁 - 修复 uploadImage 未携带用户令牌(生产 fail-closed 后会导致上传失败) - 新增 voice-intro.spec.ts(104 套件/701 用例全绿);冒烟 7/7
2364 行
66 KiB
Vue
2364 行
66 KiB
Vue
<template>
|
||
<view class="workspace-container" :class="['theme-' + currentThemeKey]">
|
||
<!-- 顶部状态栏与在线接单开关 -->
|
||
<view class="expert-status-card">
|
||
<view class="status-left">
|
||
<image class="expert-avatar" :src="expert.avatar" mode="aspectFill" />
|
||
<view class="expert-info">
|
||
<view class="name-row">
|
||
<text class="expert-name">{{ expert.nickname }}</text>
|
||
<text class="badge-gold">金牌导师</text>
|
||
</view>
|
||
<view class="status-indicator">
|
||
<view class="dot-status" :class="{ online: isReceivingOrders }"></view>
|
||
<text class="status-text">{{ isReceivingOrders ? '接单中 (派单池活跃)' : '已打烊休息' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="switch-wrap btn-active" @click="toggleReceivingStatus">
|
||
<text class="switch-label">{{ isReceivingOrders ? '停止接单' : '开启接单' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 今日经营看板 -->
|
||
<view class="stats-board">
|
||
<view class="stat-col">
|
||
<text class="stat-label">今日预计收益 (元)</text>
|
||
<text class="stat-val green-val">¥{{ todayIncome }}</text>
|
||
</view>
|
||
<view class="stat-divider"></view>
|
||
<view class="stat-col">
|
||
<text class="stat-label">今日履约单数</text>
|
||
<text class="stat-val">{{ todayOrdersCount }} 单</text>
|
||
</view>
|
||
<view class="stat-divider"></view>
|
||
<view class="stat-col">
|
||
<text class="stat-label">待签到核销</text>
|
||
<text class="stat-val gold-val">{{ waitingVerifyCount }} 人</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 多维收益走势统计图表面板 (近7天/近4周/近6个月) -->
|
||
<view class="section-card revenue-trends-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.chart" mode="aspectFit" />
|
||
<text class="sec-title">收益走势统计分析</text>
|
||
</view>
|
||
<!-- 周期切换胶囊 -->
|
||
<view class="trend-tabs">
|
||
<view
|
||
class="trend-tab-btn btn-active"
|
||
:class="{ active: trendPeriod === 'daily' }"
|
||
@click="trendPeriod = 'daily'"
|
||
>
|
||
近7天
|
||
</view>
|
||
<view
|
||
class="trend-tab-btn btn-active"
|
||
:class="{ active: trendPeriod === 'weekly' }"
|
||
@click="trendPeriod = 'weekly'"
|
||
>
|
||
近4周
|
||
</view>
|
||
<view
|
||
class="trend-tab-btn btn-active"
|
||
:class="{ active: trendPeriod === 'monthly' }"
|
||
@click="trendPeriod = 'monthly'"
|
||
>
|
||
近6个月
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 周期汇总指标看板 -->
|
||
<view class="period-summary-row">
|
||
<view class="sum-item">
|
||
<text class="sum-label">周期总收益</text>
|
||
<text class="sum-val green-val">¥{{ currentPeriodSummary.totalIncome }}</text>
|
||
</view>
|
||
<view class="sum-divider"></view>
|
||
<view class="sum-item">
|
||
<text class="sum-label">完成总单数</text>
|
||
<text class="sum-val">{{ currentPeriodSummary.totalOrders }} 单</text>
|
||
</view>
|
||
<view class="sum-divider"></view>
|
||
<view class="sum-item">
|
||
<text class="sum-label">平均客单价</text>
|
||
<text class="sum-val gold-val">¥{{ currentPeriodSummary.avgPrice }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 指标维度切换器 -->
|
||
<view class="metric-switch-row">
|
||
<view
|
||
class="metric-pill btn-active"
|
||
:class="{ active: trendMetric === 'income' }"
|
||
@click="trendMetric = 'income'"
|
||
>
|
||
接单总额 (元)
|
||
</view>
|
||
<view
|
||
class="metric-pill btn-active"
|
||
:class="{ active: trendMetric === 'orders' }"
|
||
@click="trendMetric = 'orders'"
|
||
>
|
||
完单量 (单)
|
||
</view>
|
||
<view
|
||
class="metric-pill btn-active"
|
||
:class="{ active: trendMetric === 'avgPrice' }"
|
||
@click="trendMetric = 'avgPrice'"
|
||
>
|
||
客单价 (元/单)
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 纯矢量响应式可视化柱状图表 -->
|
||
<view class="chart-container">
|
||
<view class="chart-bars-wrap">
|
||
<view
|
||
v-for="(item, idx) in currentTrendsList"
|
||
:key="idx"
|
||
class="chart-bar-col"
|
||
@click="activeTooltip = activeTooltip === idx ? null : idx"
|
||
>
|
||
<!-- 激活时的数据气泡 Tooltip -->
|
||
<view class="tooltip-bubble" v-if="activeTooltip === idx">
|
||
<text class="tip-val">{{ trendMetric === 'income' ? `¥${item.income}` : (trendMetric === 'orders' ? `${item.orders}单` : `¥${item.avgPrice}`) }}</text>
|
||
</view>
|
||
|
||
<!-- 柱子顶部实时数值 -->
|
||
<text class="bar-val-text">{{ trendMetric === 'income' ? item.income : (trendMetric === 'orders' ? item.orders : item.avgPrice) }}</text>
|
||
|
||
<!-- 柱子主体 -->
|
||
<view class="bar-track">
|
||
<view
|
||
class="bar-fill"
|
||
:class="trendMetric"
|
||
:style="{ height: `${Math.max(12, Math.round(((Number(item[trendMetric]) || 0) / maxTrendValue) * 100))}%` }"
|
||
></view>
|
||
</view>
|
||
|
||
<!-- 底部时间轴标签 -->
|
||
<text class="bar-label">{{ item.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 大神专属服务包与接单类目管理卡片 -->
|
||
<view class="section-card services-manage-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.expertBadge" mode="aspectFit" />
|
||
<text class="sec-title">陪玩服务管理 ({{ expertServices.length }})</text>
|
||
</view>
|
||
<view class="service-actions">
|
||
<button class="btn-wd-records btn-active" @click="openServiceForm()">新增服务</button>
|
||
<button class="btn-wd-records btn-active" @click="goToWithdrawPage">收益提现</button>
|
||
<button class="btn-wd-records btn-active" @click="openWithdrawRecords">提现记录</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="services-list" v-if="expertServices.length > 0">
|
||
<view v-for="srv in expertServices" :key="srv.id" class="service-item-row">
|
||
<view class="srv-info">
|
||
<view class="srv-title-row">
|
||
<text class="srv-title">{{ srv.title }}</text>
|
||
<text class="srv-badge">{{ srv.category }}</text>
|
||
<text class="srv-mode">{{ srv.mode }}</text>
|
||
</view>
|
||
<text class="srv-desc">{{ srv.desc }}</text>
|
||
<view class="srv-price-row">
|
||
<text class="srv-price">¥{{ srv.price }}</text>
|
||
<text class="srv-unit">/{{ srv.unit }}</text>
|
||
<view class="srv-ops">
|
||
<text class="srv-op btn-active" @click.stop="openServiceForm(srv)">编辑</text>
|
||
<text class="srv-op srv-op-danger btn-active" @click.stop="removeService(srv)">删除</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 独立上下架切换开关 -->
|
||
<view
|
||
class="srv-switch-btn btn-active"
|
||
:class="{ active: srv.status }"
|
||
@click="toggleServiceStatus(srv)"
|
||
>
|
||
<view class="dot"></view>
|
||
<text class="switch-txt">{{ srv.status ? '接单中' : '已打烊' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 语音介绍(录音试听):仅上传自己的录音,不做实时语音 -->
|
||
<view class="section-card voice-intro-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.mic" mode="aspectFit" />
|
||
<text class="sec-title">语音介绍(试听)</text>
|
||
</view>
|
||
<text class="sec-sub">≤60 秒 · 仅本人录音</text>
|
||
</view>
|
||
<view class="voice-row">
|
||
<button class="btn-wd-records btn-active" :disabled="voiceUploading" @click="toggleRecord">
|
||
{{ voiceRecording ? '停止录音' : (expert.voiceIntroUrl ? '重新录制' : '录制语音') }}
|
||
</button>
|
||
<button v-if="expert.voiceIntroUrl" class="btn-wd-records btn-active" @click="playVoice">
|
||
{{ voicePlaying ? '停止播放' : '播放试听' }}
|
||
</button>
|
||
<text class="voice-meta">
|
||
{{ voiceUploading ? '上传中…' : (expert.voiceIntroUrl ? `已上传 · ${expert.voiceDuration || 0} 秒` : '暂未上传') }}
|
||
</text>
|
||
</view>
|
||
<text class="voice-tip">语音仅用于买家试听介绍,平台不提供实时语音房;实际语音沟通在游戏内或微信进行。</text>
|
||
</view>
|
||
|
||
<!-- 核心亮点功能:4 位数字门票现场核销中心 (严格依 UIUX 规范 4.2 落地) -->
|
||
<view class="verify-section-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.ticket" mode="aspectFit" />
|
||
<text class="sec-title">4位活动门票现场核销工具</text>
|
||
</view>
|
||
<text class="sec-sub">车队房主专属</text>
|
||
</view>
|
||
|
||
<view class="verify-box">
|
||
<text class="input-tips">输入参与人出示的 4 位门票码完成入队签到:</text>
|
||
|
||
<!-- 4位独立方块卡槽 PIN Code -->
|
||
<view class="pin-slots-wrap" :class="{ shake: isCodeError }">
|
||
<view
|
||
v-for="i in 4"
|
||
:key="i"
|
||
class="pin-slot"
|
||
:class="{
|
||
filled: inputDigits.length >= i,
|
||
active: inputDigits.length === i - 1,
|
||
error: isCodeError
|
||
}"
|
||
>
|
||
<text class="pin-char">{{ inputDigits[i - 1] || '' }}</text>
|
||
<view class="cursor-line" v-if="inputDigits.length === i - 1"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 自定义九宫格大按键数字键盘 (触控热区 >= 88rpx,无原生键盘遮挡) -->
|
||
<view class="keypad-grid">
|
||
<view v-for="num in [1, 2, 3, 4, 5, 6, 7, 8, 9]" :key="num" class="key-btn btn-active" @click="pressKey(num)">
|
||
<text class="key-num">{{ num }}</text>
|
||
</view>
|
||
<view class="key-btn action-key btn-active" @click="clearCode">
|
||
<image class="key-svg" :src="ICONS.clearKey" mode="aspectFit" />
|
||
<text class="action-text">清空</text>
|
||
</view>
|
||
<view class="key-btn btn-active" @click="pressKey(0)">
|
||
<text class="key-num">0</text>
|
||
</view>
|
||
<view class="key-btn action-key btn-active" @click="backspace">
|
||
<image class="key-svg" :src="ICONS.deleteKey" mode="aspectFit" />
|
||
<text class="action-text">删除</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="quick-demo" @click="fillDemoCode">
|
||
<text>点击自动填入演示码 [8392] 并触发核销</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 参与人身份核验确认卡弹窗 (Modal) -->
|
||
<view class="verify-modal-mask" v-if="showConfirmModal" @click="showConfirmModal = false">
|
||
<view class="verify-modal-content" @click.stop="stopPropagation">
|
||
<view class="modal-header">
|
||
<text class="modal-title">参与人身份核验确认</text>
|
||
<view class="badge-ready">门票有效</view>
|
||
</view>
|
||
|
||
<view class="participant-card">
|
||
<image class="part-avatar" :src="verifiedParticipant.avatar" mode="aspectFill" />
|
||
<view class="part-info">
|
||
<view class="part-name-row">
|
||
<text class="part-name">{{ verifiedParticipant.nickname }}</text>
|
||
<text class="part-tag">VIP 黄金玩家</text>
|
||
</view>
|
||
<text class="part-activity">活动:{{ verifiedParticipant.activityTitle }}</text>
|
||
<view class="part-code-row">
|
||
<text class="label">核销码:</text>
|
||
<text class="code-val highlight">{{ verifiedParticipant.code }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="verify-fee-box">
|
||
<text class="fee-k">已付 AA 分摊款:</text>
|
||
<text class="fee-v">¥{{ verifiedParticipant.fee }}</text>
|
||
</view>
|
||
|
||
<view class="modal-actions">
|
||
<button class="btn-cancel btn-active" @click="showConfirmModal = false">取消</button>
|
||
<button class="btn-confirm-checkin btn-active" @click="confirmCheckin">确认核销签到</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 待抢订单池 (即时匹配派单) -->
|
||
<view class="section-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.bolt" mode="aspectFit" />
|
||
<text class="sec-title">即时抢单派单池 ({{ poolOrders.length }})</text>
|
||
</view>
|
||
<view class="pulse-live">
|
||
<view class="pulse-circle"></view>
|
||
<text class="live-text">实时派单中</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="pool-list" v-if="poolOrders.length > 0">
|
||
<view v-for="order in poolOrders" :key="order.id" class="pool-card">
|
||
<view class="pool-top">
|
||
<text class="game-tag">{{ order.game }}</text>
|
||
<text class="pool-price">¥{{ order.price }}</text>
|
||
</view>
|
||
<text class="pool-desc">{{ order.desc }}</text>
|
||
<view class="pool-foot">
|
||
<text class="pool-time">倒计时: {{ order.countdown }}s</text>
|
||
<view class="pool-btn-group">
|
||
<button class="btn-reject btn-active" @click="handleRejectOrder(order)">拒单</button>
|
||
<button class="btn-grab btn-active" @click="grabOrder(order)">立即抢单</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-pool">
|
||
<text>暂无待抢订单,保持在线有更高派单权重</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 进行中的服务履约订单 -->
|
||
<view class="section-card">
|
||
<view class="sec-head">
|
||
<view class="title-wrap">
|
||
<image class="sec-svg" :src="ICONS.orderList" mode="aspectFit" />
|
||
<text class="sec-title">进行中履约服务 ({{ activeOrders.length }})</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="active-list">
|
||
<view v-for="order in activeOrders" :key="order.id" class="active-order-card">
|
||
<view class="order-buyer">
|
||
<image class="buyer-avatar" :src="order.buyerAvatar" mode="aspectFill" />
|
||
<view class="buyer-info">
|
||
<text class="buyer-name">{{ order.buyerName }}</text>
|
||
<text class="service-tag">{{ order.serviceTitle }} · {{ order.hours }}小时</text>
|
||
</view>
|
||
<text class="order-amount">¥{{ order.totalAmount }}</text>
|
||
</view>
|
||
|
||
<view class="order-progress-row">
|
||
<text class="prog-text">状态:{{ order.statusText }}</text>
|
||
<view class="order-btns">
|
||
<button class="btn-chat btn-active" @click="chatWithBuyer">联系老板</button>
|
||
<button class="btn-complete btn-active" @click="completeOrder(order.id)">
|
||
申请验收结算
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提现历史与审批记录跟踪弹窗 -->
|
||
<view class="verify-modal-mask" v-if="showWithdrawRecordsModal" @click="showWithdrawRecordsModal = false">
|
||
<view class="verify-modal-content withdraw-records-modal" @click.stop="stopPropagation">
|
||
<view class="modal-header">
|
||
<text class="modal-title">提现记录与审批状态</text>
|
||
<view class="btn-close btn-active" @click="showWithdrawRecordsModal = false">
|
||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="records-scroll-list" v-if="withdrawRecords.length > 0">
|
||
<view v-for="rec in withdrawRecords" :key="rec.id" class="wd-record-item">
|
||
<view class="wd-rec-top">
|
||
<text class="wd-no">{{ rec.withdrawNo }}</text>
|
||
<text
|
||
class="wd-status-badge"
|
||
:class="{
|
||
pending: rec.status === '待审核',
|
||
success: rec.status === '已打款',
|
||
rejected: rec.status === '已驳回'
|
||
}"
|
||
>
|
||
{{ rec.status }}
|
||
</text>
|
||
</view>
|
||
<view class="wd-rec-main">
|
||
<text class="wd-amount">¥{{ rec.amount }}</text>
|
||
<text class="wd-channel">{{ rec.channel }}</text>
|
||
</view>
|
||
<view class="wd-rec-footer">
|
||
<text class="wd-time">{{ rec.applyTime }}</text>
|
||
<text class="wd-reason" v-if="rec.reason">驳回理由:{{ rec.reason }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
<view class="empty-records" v-else>
|
||
<text>暂无提现历史申请记录</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 服务包新增/编辑表单弹窗 -->
|
||
<view class="verify-modal-mask" v-if="serviceFormVisible" @click="serviceFormVisible = false">
|
||
<view class="verify-modal-content" @click.stop="stopPropagation">
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ serviceForm.id ? '编辑服务包' : '新增服务包' }}</text>
|
||
</view>
|
||
<view class="service-form-body">
|
||
<view class="form-row">
|
||
<text class="form-label">服务名称</text>
|
||
<input class="form-input" v-model="serviceForm.title" maxlength="40" placeholder="如:排位保星连胜局" />
|
||
</view>
|
||
<view class="form-row">
|
||
<text class="form-label">服务类目</text>
|
||
<input class="form-input" v-model="serviceForm.category" maxlength="32" placeholder="如:王者荣耀 / 同城陪伴" />
|
||
</view>
|
||
<view class="form-row">
|
||
<text class="form-label">单价(元/{{ serviceForm.unit || '小时' }})</text>
|
||
<input class="form-input" v-model="serviceForm.price" type="digit" placeholder="如:45" />
|
||
</view>
|
||
<view class="form-row">
|
||
<text class="form-label">计价单位</text>
|
||
<input class="form-input" v-model="serviceForm.unit" maxlength="16" placeholder="小时 / 局 / 次" />
|
||
</view>
|
||
<view class="form-row">
|
||
<text class="form-label">服务模式</text>
|
||
<input class="form-input" v-model="serviceForm.mode" maxlength="32" placeholder="如:排位赛 / 一对一复盘" />
|
||
</view>
|
||
<view class="form-row">
|
||
<text class="form-label">服务说明</text>
|
||
<textarea class="form-textarea" v-model="serviceForm.desc" maxlength="200" placeholder="介绍服务内容与亮点(最多 200 字)" />
|
||
</view>
|
||
<text class="form-hint">单价需符合平台定价规范(可在平台配置中查看上下限)。</text>
|
||
</view>
|
||
<view class="modal-actions">
|
||
<button class="btn-modal-cancel btn-active" @click="serviceFormVisible = false">取消</button>
|
||
<button class="btn-modal-primary btn-active" :disabled="serviceSaving" @click="submitServiceForm">
|
||
{{ serviceSaving ? '提交中…' : '保存' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部切回玩家端 -->
|
||
<view class="bottom-switch-wrap">
|
||
<button class="btn-back-player btn-active" @click="switchBackToPlayer">
|
||
退出工作台 · 切回玩家大厅
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { isExpertCertified } from '@/common/role.js';
|
||
import { getCurrentThemeKey } from '@/common/theme.js';
|
||
import {
|
||
queryCheckinCode,
|
||
universalVerifyCheckin,
|
||
getOrders,
|
||
getOrdersPool,
|
||
getExpertStats,
|
||
claimOrder,
|
||
rejectOrder,
|
||
applyCompleteOrder,
|
||
completeOrder as completeOrderApi,
|
||
updateCompanionProfile,
|
||
getExpertRevenueTrends,
|
||
getExpertServices,
|
||
updateExpertServiceStatus,
|
||
getMyExpertProfile,
|
||
createExpertService,
|
||
updateExpertService,
|
||
deleteExpertService,
|
||
uploadVoiceIntro,
|
||
getUserWithdrawals
|
||
} from '@/common/api.js';
|
||
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
|
||
const expert = ref({
|
||
id: 101,
|
||
nickname: '认证大神',
|
||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300'
|
||
});
|
||
const isReceivingOrders = ref(uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0');
|
||
const todayIncome = ref('0.00');
|
||
const todayOrdersCount = ref(0);
|
||
const waitingVerifyCount = ref(0);
|
||
|
||
const stopPropagation = (e) => {
|
||
if (e && typeof e.stopPropagation === 'function') {
|
||
e.stopPropagation();
|
||
}
|
||
};
|
||
|
||
// 多维收益统计走势状态
|
||
const trendPeriod = ref('daily');
|
||
const trendMetric = ref('income');
|
||
const rawTrendsData = ref({ daily: [], weekly: [], monthly: [] });
|
||
const activeTooltip = ref(null);
|
||
|
||
const currentTrendsList = computed(() => {
|
||
const list = rawTrendsData.value[trendPeriod.value] || [];
|
||
if (list.length > 0) return list;
|
||
if (trendPeriod.value === 'daily') {
|
||
return [
|
||
{ label: '10-07', income: 120, orders: 3, avgPrice: 40 },
|
||
{ label: '10-08', income: 85, orders: 2, avgPrice: 42.5 },
|
||
{ label: '10-09', income: 160, orders: 4, avgPrice: 40 },
|
||
{ label: '10-10', income: 210, orders: 5, avgPrice: 42 },
|
||
{ label: '10-11', income: 140, orders: 3, avgPrice: 46.7 },
|
||
{ label: '10-12', income: 290, orders: 6, avgPrice: 48.3 },
|
||
{ label: '10-13', income: 180, orders: 4, avgPrice: 45 }
|
||
];
|
||
} else if (trendPeriod.value === 'weekly') {
|
||
return [
|
||
{ label: '3周前', income: 950, orders: 22, avgPrice: 43.2 },
|
||
{ label: '2周前', income: 1120, orders: 25, avgPrice: 44.8 },
|
||
{ label: '上周', income: 1350, orders: 28, avgPrice: 48.2 },
|
||
{ label: '本周', income: 890, orders: 19, avgPrice: 46.8 }
|
||
];
|
||
} else {
|
||
return [
|
||
{ label: '5月', income: 3200, orders: 75, avgPrice: 42.7 },
|
||
{ label: '6月', income: 3800, orders: 86, avgPrice: 44.2 },
|
||
{ label: '7月', income: 4500, orders: 98, avgPrice: 45.9 },
|
||
{ label: '8月', income: 4200, orders: 90, avgPrice: 46.7 },
|
||
{ label: '9月', income: 5100, orders: 105, avgPrice: 48.6 },
|
||
{ label: '10月', income: 2800, orders: 58, avgPrice: 48.3 }
|
||
];
|
||
}
|
||
});
|
||
|
||
const currentPeriodSummary = computed(() => {
|
||
const list = currentTrendsList.value;
|
||
const totalInc = list.reduce((sum, item) => sum + (Number(item.income) || 0), 0);
|
||
const totalOrd = list.reduce((sum, item) => sum + (Number(item.orders) || 0), 0);
|
||
const avg = totalOrd > 0 ? (totalInc / totalOrd).toFixed(2) : '0.00';
|
||
return {
|
||
totalIncome: totalInc.toFixed(2),
|
||
totalOrders: totalOrd,
|
||
avgPrice: avg
|
||
};
|
||
});
|
||
|
||
const maxTrendValue = computed(() => {
|
||
const list = currentTrendsList.value;
|
||
const vals = list.map(item => Number(item[trendMetric.value]) || 0);
|
||
return Math.max(...vals, 1);
|
||
});
|
||
|
||
// 大神服务包管理状态
|
||
const expertServices = ref([]);
|
||
const serviceFormVisible = ref(false);
|
||
const serviceSaving = ref(false);
|
||
const serviceForm = ref({ id: '', title: '', category: '', price: '', unit: '小时', mode: '', desc: '' });
|
||
|
||
const currentExpertId = () => Number(expert.value.id) || 0;
|
||
|
||
// 解析当前登录用户的大神档案(避免工作台固定使用演示 id)
|
||
const loadExpertProfile = async () => {
|
||
try {
|
||
const profile = await getMyExpertProfile();
|
||
if (profile && profile.id) {
|
||
expert.value = {
|
||
...expert.value,
|
||
id: Number(profile.id),
|
||
nickname: profile.name || expert.value.nickname,
|
||
avatar: profile.avatar || expert.value.avatar,
|
||
track: profile.track || '',
|
||
hourlyRate: Number(profile.hourlyRate) || expert.value.hourlyRate,
|
||
voiceIntroUrl: profile.voiceIntroUrl || '',
|
||
voiceDuration: Number(profile.voiceDuration) || 0
|
||
};
|
||
}
|
||
} catch (_) {}
|
||
};
|
||
|
||
const loadExpertServices = async () => {
|
||
try {
|
||
const list = await getExpertServices(currentExpertId());
|
||
expertServices.value = Array.isArray(list) ? list : [];
|
||
} catch (_) {
|
||
expertServices.value = [];
|
||
}
|
||
};
|
||
|
||
// 语音介绍(录音试听)状态与处理
|
||
const voiceRecording = ref(false);
|
||
const voiceUploading = ref(false);
|
||
const voicePlaying = ref(false);
|
||
let recorderManager = null;
|
||
let voiceAudioCtx = null;
|
||
|
||
const ensureRecorder = () => {
|
||
if (recorderManager) return recorderManager;
|
||
recorderManager = uni.getRecorderManager();
|
||
recorderManager.onStop((res) => {
|
||
voiceRecording.value = false;
|
||
const duration = Math.max(1, Math.round(Number(res.duration || 0) / 1000) || 1);
|
||
uploadRecordedVoice(res.tempFilePath, duration);
|
||
});
|
||
recorderManager.onError(() => {
|
||
voiceRecording.value = false;
|
||
uni.showToast({ title: '录音失败,请检查麦克风权限', icon: 'none' });
|
||
});
|
||
return recorderManager;
|
||
};
|
||
|
||
const uploadRecordedVoice = async (filePath, duration) => {
|
||
if (!filePath) return;
|
||
voiceUploading.value = true;
|
||
try {
|
||
const r = await uploadVoiceIntro(currentExpertId(), filePath, duration);
|
||
expert.value = { ...expert.value, voiceIntroUrl: r.url, voiceDuration: r.duration };
|
||
uni.showToast({ title: '语音介绍已更新', icon: 'none' });
|
||
} catch (e) {
|
||
uni.showToast({ title: e.message || '语音上传失败', icon: 'none' });
|
||
} finally {
|
||
voiceUploading.value = false;
|
||
}
|
||
};
|
||
|
||
const toggleRecord = () => {
|
||
const rm = ensureRecorder();
|
||
if (voiceRecording.value) {
|
||
rm.stop();
|
||
return;
|
||
}
|
||
const start = () => {
|
||
rm.start({ duration: 60000, format: 'mp3', sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 48000 });
|
||
voiceRecording.value = true;
|
||
uni.showToast({ title: '开始录音,最长 60 秒', icon: 'none' });
|
||
};
|
||
uni.authorize({
|
||
scope: 'scope.record',
|
||
success: start,
|
||
fail: () => uni.showToast({ title: '请先允许使用麦克风', icon: 'none' })
|
||
});
|
||
};
|
||
|
||
const playVoice = () => {
|
||
const url = expert.value.voiceIntroUrl;
|
||
if (!url) return;
|
||
if (voicePlaying.value) {
|
||
try { voiceAudioCtx && voiceAudioCtx.stop(); } catch (_) {}
|
||
voicePlaying.value = false;
|
||
return;
|
||
}
|
||
if (!voiceAudioCtx) {
|
||
voiceAudioCtx = uni.createInnerAudioContext();
|
||
voiceAudioCtx.onEnded(() => { voicePlaying.value = false; });
|
||
voiceAudioCtx.onError(() => { voicePlaying.value = false; });
|
||
}
|
||
voiceAudioCtx.src = url;
|
||
voiceAudioCtx.play();
|
||
voicePlaying.value = true;
|
||
};
|
||
|
||
onUnmounted(() => {
|
||
try { if (recorderManager && voiceRecording.value) recorderManager.stop(); } catch (_) {}
|
||
try { if (voiceAudioCtx) { voiceAudioCtx.destroy(); voiceAudioCtx = null; } } catch (_) {}
|
||
});
|
||
|
||
const openServiceForm = (srv) => {
|
||
if (srv) {
|
||
serviceForm.value = {
|
||
id: srv.id,
|
||
title: srv.title,
|
||
category: srv.category,
|
||
price: String(srv.price),
|
||
unit: srv.unit || '小时',
|
||
mode: srv.mode || '',
|
||
desc: srv.desc || ''
|
||
};
|
||
} else {
|
||
serviceForm.value = { id: '', title: '', category: '', price: '', unit: '小时', mode: '', desc: '' };
|
||
}
|
||
serviceFormVisible.value = true;
|
||
};
|
||
|
||
const submitServiceForm = async () => {
|
||
const f = serviceForm.value;
|
||
if (!String(f.title || '').trim()) {
|
||
uni.showToast({ title: '请填写服务名称', icon: 'none' });
|
||
return;
|
||
}
|
||
const price = Number(f.price);
|
||
if (!price || price <= 0) {
|
||
uni.showToast({ title: '请填写有效单价', icon: 'none' });
|
||
return;
|
||
}
|
||
const payload = {
|
||
title: String(f.title).trim(),
|
||
category: String(f.category || '').trim(),
|
||
price,
|
||
unit: String(f.unit || '').trim() || '小时',
|
||
mode: String(f.mode || '').trim(),
|
||
desc: String(f.desc || '').trim()
|
||
};
|
||
serviceSaving.value = true;
|
||
try {
|
||
if (f.id) {
|
||
await updateExpertService(currentExpertId(), f.id, payload);
|
||
} else {
|
||
await createExpertService(currentExpertId(), payload);
|
||
}
|
||
serviceFormVisible.value = false;
|
||
uni.showToast({ title: f.id ? '服务包已更新' : '服务包已创建', icon: 'none' });
|
||
await loadExpertServices();
|
||
} catch (err) {
|
||
uni.showToast({ title: err.message || '保存失败', icon: 'none' });
|
||
} finally {
|
||
serviceSaving.value = false;
|
||
}
|
||
};
|
||
|
||
const removeService = (srv) => {
|
||
uni.showModal({
|
||
title: '删除服务包',
|
||
content: `确认删除「${srv.title}」?删除后买家将无法预约该服务。`,
|
||
confirmColor: '#FF5F57',
|
||
success: async (res) => {
|
||
if (!res.confirm) return;
|
||
try {
|
||
await deleteExpertService(currentExpertId(), srv.id);
|
||
uni.showToast({ title: '已删除', icon: 'none' });
|
||
await loadExpertServices();
|
||
} catch (err) {
|
||
uni.showToast({ title: err.message || '删除失败', icon: 'none' });
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const toggleServiceStatus = async (item) => {
|
||
const newStatus = !item.status;
|
||
try {
|
||
await updateExpertServiceStatus(currentExpertId(), item.id, newStatus);
|
||
item.status = newStatus;
|
||
uni.showToast({
|
||
title: newStatus ? `【${item.title}】已上架接单` : `【${item.title}】已暂停下架`,
|
||
icon: 'none'
|
||
});
|
||
} catch (e) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
// 提现记录弹窗状态
|
||
const showWithdrawRecordsModal = ref(false);
|
||
const withdrawRecords = ref([]);
|
||
|
||
const goToWithdrawPage = () => {
|
||
uni.navigateTo({
|
||
url: '/pages/user/withdraw'
|
||
});
|
||
};
|
||
|
||
const openWithdrawRecords = async () => {
|
||
try {
|
||
const list = await getUserWithdrawals(1001);
|
||
withdrawRecords.value = list || [];
|
||
} catch (_) {}
|
||
showWithdrawRecordsModal.value = true;
|
||
};
|
||
const inputDigits = ref([]);
|
||
const isCodeError = ref(false);
|
||
const showConfirmModal = ref(false);
|
||
|
||
|
||
const verifiedParticipant = ref({
|
||
nickname: '电竞萌新小可爱',
|
||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&h=150&fit=crop',
|
||
activityTitle: 'AA制五排王者百星车队',
|
||
code: '8392',
|
||
fee: '25.00'
|
||
});
|
||
|
||
const poolOrders = ref([
|
||
{
|
||
id: 'pool_101',
|
||
game: '王者荣耀',
|
||
price: 35,
|
||
desc: 'QQ区/星耀1晋级赛冲王者/需开黑配合/射手或辅助',
|
||
countdown: 48
|
||
},
|
||
{
|
||
id: 'pool_102',
|
||
game: '和平精英',
|
||
price: 45,
|
||
desc: '微信区/四排海岛/刚枪上分/kd保4以上',
|
||
countdown: 32
|
||
},
|
||
{
|
||
id: 'pool_103',
|
||
game: '美食陪吃',
|
||
price: 68,
|
||
desc: '朝阳大悦城海底捞/周末饭搭子/消费现场AA/需守时礼貌',
|
||
countdown: 56
|
||
}
|
||
]);
|
||
|
||
const activeOrders = ref([
|
||
{
|
||
id: 'ord_active_1',
|
||
buyerName: '超能电竞玩家',
|
||
buyerAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop',
|
||
serviceTitle: '排位包胜率',
|
||
hours: 2,
|
||
totalAmount: 70,
|
||
statusText: '正在开黑对局履约中'
|
||
},
|
||
{
|
||
id: 'ord_active_2',
|
||
buyerName: '北京吃货小张',
|
||
buyerAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&h=150&fit=crop',
|
||
serviceTitle: '火锅探店饭搭子',
|
||
hours: 2,
|
||
totalAmount: 136,
|
||
statusText: '已核销4位码入座,服务中'
|
||
}
|
||
]);
|
||
|
||
const toggleReceivingStatus = async () => {
|
||
const next = !isReceivingOrders.value;
|
||
const newStatusStr = next ? '接单中' : '休息中';
|
||
try {
|
||
await updateCompanionProfile(expert.value.id || 101, { status: newStatusStr });
|
||
} catch (err) {
|
||
uni.showToast({
|
||
title: (err && err.message) || '接单状态切换失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
isReceivingOrders.value = next;
|
||
uni.setStorageSync('EXPERT_RECEIVING_STATUS', next ? '1' : '0');
|
||
uni.showToast({
|
||
title: next ? '已开启在线接单通道!' : '已打烊停止接单',
|
||
icon: 'none'
|
||
});
|
||
};
|
||
|
||
const pressKey = (num) => {
|
||
if (inputDigits.value.length >= 4) return;
|
||
inputDigits.value.push(num);
|
||
if (inputDigits.value.length === 4) {
|
||
verifyInputCode();
|
||
}
|
||
};
|
||
|
||
const backspace = () => {
|
||
if (inputDigits.value.length > 0) {
|
||
inputDigits.value.pop();
|
||
isCodeError.value = false;
|
||
}
|
||
};
|
||
|
||
const clearCode = () => {
|
||
inputDigits.value = [];
|
||
isCodeError.value = false;
|
||
};
|
||
|
||
const fillDemoCode = () => {
|
||
inputDigits.value = [8, 3, 9, 2];
|
||
verifyInputCode();
|
||
};
|
||
|
||
const loadWorkspaceOrders = async () => {
|
||
try {
|
||
// 先解析真实大神档案,避免工作台使用演示 id
|
||
await loadExpertProfile();
|
||
const eid = currentExpertId() || 101;
|
||
const stats = await getExpertStats(eid);
|
||
if (stats) {
|
||
if (stats.income !== undefined) todayIncome.value = stats.income;
|
||
if (stats.orders !== undefined) todayOrdersCount.value = stats.orders;
|
||
if (stats.waitingVerify !== undefined) waitingVerifyCount.value = stats.waitingVerify;
|
||
}
|
||
|
||
const pool = await getOrdersPool();
|
||
if (pool && pool.length > 0) {
|
||
poolOrders.value = pool.map(p => ({
|
||
id: p.id,
|
||
game: p.category || '同城陪伴',
|
||
price: p.reward || 68,
|
||
desc: p.title || '同城线下生活陪伴,需准时到场',
|
||
countdown: p.countdown || 50
|
||
}));
|
||
}
|
||
|
||
const list = await getOrders();
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
const active = list.filter(o => o.status === 'IN_SERVICE').map(o => ({
|
||
id: o.id,
|
||
buyerName: o.expert.nickname || '客户老板',
|
||
buyerAvatar: o.expert.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150',
|
||
serviceTitle: o.serviceTitle,
|
||
hours: o.quantity,
|
||
totalAmount: o.totalAmount,
|
||
statusText: o.verifyCode ? `现场码[${o.verifyCode}]到场核销服务中` : '正在履约服务中'
|
||
}));
|
||
if (active.length > 0) {
|
||
activeOrders.value = active;
|
||
}
|
||
}
|
||
|
||
try {
|
||
const trends = await getExpertRevenueTrends(currentExpertId() || 101);
|
||
if (trends && (trends.daily || trends.weekly || trends.monthly)) {
|
||
rawTrendsData.value = trends;
|
||
}
|
||
} catch (_) {}
|
||
|
||
loadExpertServices();
|
||
} catch (err) {
|
||
console.warn('[Workspace] 加载服务列表异常:', err);
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
if (isExpertCertified()) {
|
||
loadWorkspaceOrders();
|
||
}
|
||
});
|
||
|
||
onShow(() => {
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
if (!isExpertCertified()) {
|
||
uni.showToast({
|
||
title: '非认证大神禁止访问工作台',
|
||
icon: 'none',
|
||
duration: 1500
|
||
});
|
||
setTimeout(() => {
|
||
uni.reLaunch({ url: '/pages/user/index' });
|
||
}, 400);
|
||
return;
|
||
}
|
||
isReceivingOrders.value = uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0';
|
||
loadWorkspaceOrders();
|
||
});
|
||
|
||
const verifyInputCode = async () => {
|
||
const codeStr = inputDigits.value.join('');
|
||
uni.showLoading({ title: '核验核销码中...' });
|
||
try {
|
||
const info = await queryCheckinCode(codeStr);
|
||
uni.hideLoading();
|
||
if (info.checkedIn) {
|
||
isCodeError.value = true;
|
||
uni.showToast({
|
||
title: `核销码 [${codeStr}] 已核销完成,请勿重复签到`,
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => { isCodeError.value = false; }, 1200);
|
||
return;
|
||
}
|
||
verifiedParticipant.value = {
|
||
id: info.id,
|
||
nickname: info.nickname || '同城搭子',
|
||
avatar: info.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150',
|
||
activityTitle: info.title || '同城生活组局',
|
||
code: info.code,
|
||
fee: info.fee || '50.00',
|
||
};
|
||
isCodeError.value = false;
|
||
showConfirmModal.value = true;
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
isCodeError.value = true;
|
||
uni.showToast({
|
||
title: err.message || `核销码 [${codeStr}] 无效`,
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => { isCodeError.value = false; }, 1000);
|
||
}
|
||
};
|
||
|
||
const confirmCheckin = async () => {
|
||
const codeStr = verifiedParticipant.value.code;
|
||
uni.showLoading({ title: '确认核销中...' });
|
||
try {
|
||
await universalVerifyCheckin(codeStr, 1001);
|
||
uni.hideLoading();
|
||
showConfirmModal.value = false;
|
||
const gainedFee = Number(verifiedParticipant.value.fee) || 25;
|
||
todayIncome.value = (Number(todayIncome.value) + gainedFee).toFixed(2);
|
||
inputDigits.value = [];
|
||
uni.showToast({
|
||
title: `核销成功!¥${gainedFee}已释放到账`,
|
||
icon: 'success'
|
||
});
|
||
loadWorkspaceOrders();
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: err.message || '核销异常',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
const grabOrder = async (order) => {
|
||
try {
|
||
await claimOrder(order.id, {
|
||
expertId: currentExpertId() || 101,
|
||
companionName: '我 (接单大神)',
|
||
expertAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150'
|
||
});
|
||
} catch (err) {
|
||
uni.showToast({
|
||
title: (err && err.message) || '抢单失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
loadWorkspaceOrders();
|
||
return;
|
||
}
|
||
uni.showToast({
|
||
title: '抢单成功已接单',
|
||
icon: 'success'
|
||
});
|
||
poolOrders.value = poolOrders.value.filter(o => o.id !== order.id);
|
||
loadWorkspaceOrders();
|
||
};
|
||
|
||
const handleRejectOrder = (order) => {
|
||
uni.showModal({
|
||
title: '确认拒单并退款',
|
||
content: '拒单后系统将自动将该订单款项原路全额退还给买家,确定拒单吗?',
|
||
confirmColor: '#EF4444',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
await rejectOrder(order.id, {
|
||
expertId: expert.value.id || 101,
|
||
reason: '大神当前档期冲突拒单'
|
||
});
|
||
} catch (err) {
|
||
uni.showToast({
|
||
title: (err && err.message) || '拒单失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
loadWorkspaceOrders();
|
||
return;
|
||
}
|
||
poolOrders.value = poolOrders.value.filter(o => o.id !== order.id);
|
||
uni.showToast({
|
||
title: '已拒单并自动退款',
|
||
icon: 'none'
|
||
});
|
||
loadWorkspaceOrders();
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
|
||
const chatWithBuyer = () => {
|
||
uni.showActionSheet({
|
||
itemList: [
|
||
'复制开黑联络码',
|
||
'联系 7x24h 官方客服'
|
||
],
|
||
success: (res) => {
|
||
if (res.tapIndex === 0) {
|
||
uni.setClipboardData({
|
||
data: `BUYER-${Math.floor(100000 + Math.random() * 900000)}`,
|
||
success: () => {
|
||
uni.showToast({ title: '联络码已复制', icon: 'success' });
|
||
}
|
||
});
|
||
} else if (res.tapIndex === 1) {
|
||
uni.showActionSheet({
|
||
itemList: [
|
||
'拨打官方服务热线 (400-888-6666)',
|
||
'复制官方 VIP 客服微信 (peiwan_vip_kefu)'
|
||
],
|
||
success: (cRes) => {
|
||
if (cRes.tapIndex === 0) {
|
||
uni.makePhoneCall({
|
||
phoneNumber: '4008886666',
|
||
fail: () => {
|
||
uni.showToast({ title: '服务热线: 400-888-6666', icon: 'none' });
|
||
}
|
||
});
|
||
} else if (cRes.tapIndex === 1) {
|
||
uni.setClipboardData({
|
||
data: 'peiwan_vip_kefu',
|
||
success: () => {
|
||
uni.showToast({ title: '官方客服微信号已复制', icon: 'success' });
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const completeOrder = (id) => {
|
||
uni.showModal({
|
||
title: '确认提交服务验收',
|
||
content: '服务已完成?提交后订单将进入【待确认】状态,买家确认或48小时后自动解冻资金结算至您的佣金钱包。',
|
||
confirmColor: '#10B981',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
try {
|
||
await applyCompleteOrder(id, { expertId: expert.value.id || 101 });
|
||
} catch (err) {
|
||
uni.showToast({
|
||
title: (err && err.message) || '提交失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
loadWorkspaceOrders();
|
||
return;
|
||
}
|
||
activeOrders.value = activeOrders.value.filter(o => o.id !== id);
|
||
uni.showToast({ title: '已提交待买家确认', icon: 'success' });
|
||
loadWorkspaceOrders();
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const switchBackToPlayer = () => {
|
||
uni.switchTab({
|
||
url: '/pages/user/index'
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
.workspace-container {
|
||
min-height: 100vh;
|
||
background-color: var(--bg-primary);
|
||
padding: 24rpx 32rpx calc(60rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.expert-status-card {
|
||
background: linear-gradient(135deg, var(--bg-surface-elevated) 0%, var(--bg-surface) 100%);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 28rpx;
|
||
padding: 28rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 8rpx 28rpx rgba(99, 102, 241, 0.25);
|
||
|
||
.status-left {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
.expert-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50rpx;
|
||
border: 4rpx solid var(--color-brand);
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.expert-info {
|
||
.name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 8rpx;
|
||
|
||
.expert-name {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #FFF;
|
||
}
|
||
|
||
.badge-gold {
|
||
font-size: 18rpx;
|
||
background: linear-gradient(135deg, #F59E0B, #D97706);
|
||
color: #FFF;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.status-indicator {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.dot-status {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-radius: 50%;
|
||
background: var(--text-muted);
|
||
|
||
&.online {
|
||
background: #10B981;
|
||
box-shadow: 0 0 10rpx #10B981;
|
||
}
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 22rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.switch-wrap {
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 32rpx;
|
||
padding: 10rpx 24rpx;
|
||
|
||
.switch-label {
|
||
font-size: 22rpx;
|
||
color: var(--text-primary);
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.stats-board {
|
||
background-color: var(--bg-surface);
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1px solid var(--border-subtle);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
|
||
.stat-col {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
.stat-label {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.stat-val {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
font-variant-numeric: tabular-nums;
|
||
|
||
&.green-val {
|
||
color: #10B981;
|
||
}
|
||
&.gold-val {
|
||
color: #F59E0B;
|
||
}
|
||
}
|
||
}
|
||
|
||
.stat-divider {
|
||
width: 1px;
|
||
height: 60rpx;
|
||
background: var(--bg-surface-elevated);
|
||
}
|
||
}
|
||
|
||
/* 收益走势统计图表面板 */
|
||
.revenue-trends-card {
|
||
margin-bottom: 24rpx;
|
||
|
||
.trend-tabs {
|
||
display: flex;
|
||
background: var(--bg-primary);
|
||
padding: 4rpx;
|
||
border-radius: 20rpx;
|
||
border: 1px solid var(--border-subtle);
|
||
|
||
.trend-tab-btn {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 16rpx;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active {
|
||
background: var(--color-brand);
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
box-shadow: 0 2rpx 8rpx rgba(99, 102, 241, 0.4);
|
||
}
|
||
}
|
||
}
|
||
|
||
.period-summary-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 16rpx;
|
||
padding: 16rpx 12rpx;
|
||
margin-bottom: 20rpx;
|
||
|
||
.sum-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
.sum-label {
|
||
font-size: 18rpx;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 6rpx;
|
||
}
|
||
.sum-val {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
font-family: monospace;
|
||
|
||
&.green-val { color: #10B981; }
|
||
&.gold-val { color: #F59E0B; }
|
||
}
|
||
}
|
||
|
||
.sum-divider {
|
||
width: 1px;
|
||
height: 40rpx;
|
||
background: var(--bg-surface-elevated);
|
||
}
|
||
}
|
||
|
||
.metric-switch-row {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-bottom: 24rpx;
|
||
|
||
.metric-pill {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 12rpx;
|
||
padding: 6rpx 16rpx;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active {
|
||
color: var(--color-brand-light);
|
||
background: rgba(99, 102, 241, 0.15);
|
||
border-color: rgba(99, 102, 241, 0.4);
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
|
||
.chart-container {
|
||
height: 220rpx;
|
||
position: relative;
|
||
padding: 10rpx 0 0;
|
||
|
||
.chart-bars-wrap {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: space-between;
|
||
height: 100%;
|
||
gap: 8rpx;
|
||
|
||
.chart-bar-col {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
height: 100%;
|
||
position: relative;
|
||
cursor: pointer;
|
||
|
||
.tooltip-bubble {
|
||
position: absolute;
|
||
top: -36rpx;
|
||
background: var(--color-brand);
|
||
color: #FFF;
|
||
font-size: 18rpx;
|
||
font-weight: 700;
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
white-space: nowrap;
|
||
z-index: 10;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
.bar-val-text {
|
||
font-size: 16rpx;
|
||
color: var(--text-muted);
|
||
margin-bottom: 6rpx;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.bar-track {
|
||
flex: 1;
|
||
width: 28rpx;
|
||
background: var(--bg-surface-elevated);
|
||
border-radius: 14rpx;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
overflow: hidden;
|
||
|
||
.bar-fill {
|
||
width: 100%;
|
||
border-radius: 14rpx;
|
||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
&.income {
|
||
background: linear-gradient(180deg, #10B981 0%, #059669 100%);
|
||
box-shadow: 0 0 10rpx rgba(16, 185, 129, 0.3);
|
||
}
|
||
&.orders {
|
||
background: var(--color-brand-gradient);
|
||
box-shadow: 0 0 10rpx rgba(99, 102, 241, 0.3);
|
||
}
|
||
&.avgPrice {
|
||
background: linear-gradient(180deg, #F59E0B 0%, #D97706 100%);
|
||
box-shadow: 0 0 10rpx rgba(245, 158, 11, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
.bar-label {
|
||
font-size: 18rpx;
|
||
color: var(--text-secondary);
|
||
margin-top: 8rpx;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 大神服务包管理卡片 */
|
||
.services-manage-card {
|
||
margin-bottom: 24rpx;
|
||
|
||
.service-actions {
|
||
.btn-wd-records {
|
||
margin: 0;
|
||
font-size: 20rpx;
|
||
color: var(--color-brand);
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 12rpx;
|
||
padding: 6rpx 16rpx;
|
||
line-height: 1.4;
|
||
|
||
&::after { display: none; }
|
||
}
|
||
}
|
||
|
||
.services-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
|
||
.service-item-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 18rpx;
|
||
padding: 20rpx;
|
||
|
||
.srv-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
margin-right: 16rpx;
|
||
|
||
.srv-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
margin-bottom: 6rpx;
|
||
|
||
.srv-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
}
|
||
.srv-badge {
|
||
font-size: 18rpx;
|
||
color: var(--color-brand-light);
|
||
background: rgba(99, 102, 241, 0.12);
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
.srv-mode {
|
||
font-size: 18rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
}
|
||
|
||
.srv-desc {
|
||
font-size: 20rpx;
|
||
color: var(--text-muted);
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.srv-price-row {
|
||
.srv-price {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
font-family: monospace;
|
||
}
|
||
.srv-unit {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
}
|
||
}
|
||
|
||
.srv-switch-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 24rpx;
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
|
||
.dot {
|
||
width: 10rpx;
|
||
height: 10rpx;
|
||
border-radius: 50%;
|
||
background: var(--text-muted);
|
||
}
|
||
|
||
.switch-txt {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
&.active {
|
||
background: rgba(16, 185, 129, 0.15);
|
||
border-color: rgba(16, 185, 129, 0.35);
|
||
|
||
.dot {
|
||
background: #10B981;
|
||
box-shadow: 0 0 8rpx #10B981;
|
||
}
|
||
.switch-txt {
|
||
color: #10B981;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.verify-section-card {
|
||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(21, 29, 48, 1) 100%);
|
||
border: 1px solid rgba(16, 185, 129, 0.35);
|
||
border-radius: 24rpx;
|
||
padding: 28rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.2);
|
||
|
||
.sec-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.title-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.sec-svg {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
|
||
.sec-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
}
|
||
}
|
||
|
||
.sec-sub {
|
||
font-size: 20rpx;
|
||
color: var(--text-muted);
|
||
}
|
||
}
|
||
|
||
.verify-box {
|
||
.input-tips {
|
||
font-size: 22rpx;
|
||
color: var(--text-secondary);
|
||
display: block;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.pin-slots-wrap {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
margin: 20rpx 0 28rpx;
|
||
|
||
&.shake {
|
||
animation: errorShake 0.4s ease-in-out;
|
||
}
|
||
|
||
.pin-slot {
|
||
width: 104rpx;
|
||
height: 116rpx;
|
||
background: var(--bg-primary);
|
||
border: 2rpx solid var(--border-subtle);
|
||
border-radius: 18rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
transition: all 0.2s ease;
|
||
|
||
&.filled {
|
||
border-color: rgba(16, 185, 129, 0.6);
|
||
background: rgba(16, 185, 129, 0.08);
|
||
}
|
||
|
||
&.active {
|
||
border-color: #10B981;
|
||
box-shadow: 0 0 16rpx rgba(16, 185, 129, 0.4);
|
||
}
|
||
|
||
&.error {
|
||
border-color: #EF4444;
|
||
background: rgba(239, 68, 68, 0.1);
|
||
color: #EF4444;
|
||
}
|
||
|
||
.pin-char {
|
||
font-size: 46rpx;
|
||
font-weight: 800;
|
||
color: #10B981;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.cursor-line {
|
||
width: 4rpx;
|
||
height: 48rpx;
|
||
background: #10B981;
|
||
border-radius: 2rpx;
|
||
animation: cursorBlink 1s infinite;
|
||
}
|
||
}
|
||
}
|
||
|
||
.keypad-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16rpx;
|
||
|
||
.key-btn {
|
||
height: 96rpx;
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.key-num {
|
||
font-size: 38rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
font-family: 'Courier New', Courier, monospace;
|
||
}
|
||
|
||
&.action-key {
|
||
background: var(--bg-surface-elevated);
|
||
|
||
.key-svg {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 18rpx;
|
||
color: var(--text-secondary);
|
||
margin-top: 4rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-demo {
|
||
margin-top: 20rpx;
|
||
text-align: center;
|
||
font-size: 22rpx;
|
||
color: var(--color-brand-light);
|
||
padding: 10rpx;
|
||
background: rgba(99, 102, 241, 0.1);
|
||
border: 1px dashed rgba(99, 102, 241, 0.3);
|
||
border-radius: 12rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 参与人身份核验确认卡弹窗样式 */
|
||
.verify-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.75);
|
||
backdrop-filter: blur(8px);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
z-index: 999;
|
||
animation: fadeIn 0.2s ease-out;
|
||
|
||
.verify-modal-content {
|
||
width: 100%;
|
||
background: var(--bg-surface);
|
||
border-top: 2rpx solid rgba(16, 185, 129, 0.45);
|
||
border-radius: 36rpx 36rpx 0 0;
|
||
padding: 36rpx 32rpx calc(40rpx + env(safe-area-inset-bottom));
|
||
box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.6);
|
||
animation: slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 28rpx;
|
||
|
||
.modal-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.badge-ready {
|
||
font-size: 20rpx;
|
||
background: rgba(16, 185, 129, 0.15);
|
||
border: 1px solid rgba(16, 185, 129, 0.35);
|
||
color: #10B981;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.participant-card {
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.part-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50rpx;
|
||
border: 2rpx solid #10B981;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.part-info {
|
||
flex: 1;
|
||
|
||
.part-name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 8rpx;
|
||
|
||
.part-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #FFF;
|
||
}
|
||
|
||
.part-tag {
|
||
font-size: 18rpx;
|
||
background: linear-gradient(135deg, #F59E0B, #D97706);
|
||
color: #FFF;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
|
||
.part-activity {
|
||
font-size: 22rpx;
|
||
color: var(--text-secondary);
|
||
display: block;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.part-code-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.label {
|
||
font-size: 20rpx;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.code-val {
|
||
font-size: 28rpx;
|
||
font-weight: 800;
|
||
color: #10B981;
|
||
letter-spacing: 2rpx;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.verify-fee-box {
|
||
background: rgba(16, 185, 129, 0.08);
|
||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||
border-radius: 16rpx;
|
||
padding: 20rpx 24rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 32rpx;
|
||
|
||
.fee-k {
|
||
font-size: 24rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.fee-v {
|
||
font-size: 34rpx;
|
||
font-weight: 800;
|
||
color: #10B981;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
|
||
.modal-actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
|
||
.btn-cancel {
|
||
flex: 1;
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
color: var(--text-secondary);
|
||
font-size: 26rpx;
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.btn-confirm-checkin {
|
||
flex: 2;
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
color: #FFF;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.4);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes cursorBlink {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0; }
|
||
}
|
||
|
||
@keyframes errorShake {
|
||
0%, 100% { transform: translateX(0); }
|
||
20%, 60% { transform: translateX(-10rpx); }
|
||
40%, 80% { transform: translateX(10rpx); }
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from { transform: translateY(100%); }
|
||
to { transform: translateY(0); }
|
||
}
|
||
|
||
.section-card {
|
||
background-color: var(--bg-surface);
|
||
border-radius: 24rpx;
|
||
padding: 28rpx;
|
||
margin-bottom: 24rpx;
|
||
border: 1px solid var(--border-subtle);
|
||
|
||
.sec-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.title-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
|
||
.sec-svg {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
|
||
.sec-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
}
|
||
}
|
||
|
||
.pulse-live {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
|
||
.pulse-circle {
|
||
width: 10rpx;
|
||
height: 10rpx;
|
||
border-radius: 50%;
|
||
background-color: #10B981;
|
||
box-shadow: 0 0 8rpx #10B981;
|
||
}
|
||
.live-text {
|
||
font-size: 20rpx;
|
||
color: #10B981;
|
||
}
|
||
}
|
||
}
|
||
|
||
.pool-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18rpx;
|
||
|
||
.pool-card {
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 18rpx;
|
||
padding: 20rpx;
|
||
|
||
.pool-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10rpx;
|
||
|
||
.game-tag {
|
||
font-size: 20rpx;
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: var(--color-brand-light);
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
}
|
||
|
||
.pool-price {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
|
||
.pool-desc {
|
||
font-size: 22rpx;
|
||
color: var(--text-secondary);
|
||
line-height: 1.4;
|
||
margin-bottom: 14rpx;
|
||
display: block;
|
||
}
|
||
|
||
.pool-foot {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.pool-time {
|
||
font-size: 20rpx;
|
||
color: #EF4444;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.pool-btn-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.btn-reject {
|
||
background: rgba(239, 68, 68, 0.14);
|
||
border: 1px solid rgba(239, 68, 68, 0.35);
|
||
color: #F87171;
|
||
font-size: 22rpx;
|
||
font-weight: 600;
|
||
border-radius: 28rpx;
|
||
height: 56rpx;
|
||
line-height: 56rpx;
|
||
padding: 0 22rpx;
|
||
margin: 0;
|
||
&::after { display: none; }
|
||
}
|
||
|
||
.btn-grab {
|
||
background: var(--color-brand-gradient);
|
||
color: #FFF;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
border-radius: 28rpx;
|
||
height: 56rpx;
|
||
line-height: 56rpx;
|
||
padding: 0 28rpx;
|
||
margin: 0;
|
||
box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
|
||
&::after { display: none; }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-pool {
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
font-size: 22rpx;
|
||
padding: 24rpx 0;
|
||
}
|
||
|
||
.active-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18rpx;
|
||
|
||
.active-order-card {
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 18rpx;
|
||
padding: 20rpx;
|
||
|
||
.order-buyer {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.buyer-avatar {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 36rpx;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.buyer-info {
|
||
flex: 1;
|
||
|
||
.buyer-name {
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
display: block;
|
||
}
|
||
|
||
.service-tag {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
}
|
||
|
||
.order-amount {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
}
|
||
|
||
.order-progress-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-top: 1px solid var(--border-subtle);
|
||
padding-top: 14rpx;
|
||
|
||
.prog-text {
|
||
font-size: 22rpx;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.order-btns {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
|
||
.btn-chat {
|
||
background: rgba(99, 102, 241, 0.15);
|
||
color: var(--color-brand-light);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
font-size: 20rpx;
|
||
height: 52rpx;
|
||
line-height: 50rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 26rpx;
|
||
margin: 0;
|
||
}
|
||
|
||
.btn-complete {
|
||
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
|
||
color: #FFF;
|
||
font-size: 20rpx;
|
||
font-weight: 600;
|
||
height: 52rpx;
|
||
line-height: 52rpx;
|
||
padding: 0 22rpx;
|
||
border-radius: 26rpx;
|
||
margin: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-switch-wrap {
|
||
margin-top: 40rpx;
|
||
|
||
.btn-back-player {
|
||
background: var(--bg-surface);
|
||
color: var(--text-secondary);
|
||
font-size: 26rpx;
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
border-radius: 42rpx;
|
||
border: 1px solid var(--border-subtle);
|
||
}
|
||
}
|
||
|
||
/* 提现记录弹窗 */
|
||
.withdraw-records-modal {
|
||
max-height: 80vh;
|
||
|
||
.modal-header {
|
||
.btn-close {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.close-svg {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
}
|
||
|
||
.records-scroll-list {
|
||
max-height: 60vh;
|
||
|
||
.wd-record-item {
|
||
background: var(--bg-surface-elevated);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.wd-rec-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
|
||
.wd-no {
|
||
font-size: 20rpx;
|
||
color: var(--text-secondary);
|
||
font-family: monospace;
|
||
}
|
||
|
||
.wd-status-badge {
|
||
font-size: 18rpx;
|
||
padding: 2rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
font-weight: 600;
|
||
|
||
&.pending {
|
||
color: #F59E0B;
|
||
background: rgba(245, 158, 11, 0.12);
|
||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||
}
|
||
&.success {
|
||
color: #10B981;
|
||
background: rgba(16, 185, 129, 0.12);
|
||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||
}
|
||
&.rejected {
|
||
color: #EF4444;
|
||
background: rgba(239, 68, 68, 0.12);
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
}
|
||
}
|
||
}
|
||
|
||
.wd-rec-main {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8rpx;
|
||
|
||
.wd-amount {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: var(--text-primary);
|
||
font-family: monospace;
|
||
}
|
||
.wd-channel {
|
||
font-size: 22rpx;
|
||
color: var(--text-secondary);
|
||
}
|
||
}
|
||
|
||
.wd-rec-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 18rpx;
|
||
color: var(--text-muted);
|
||
|
||
.wd-reason {
|
||
color: #EF4444;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-records {
|
||
padding: 60rpx 0;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: var(--text-muted);
|
||
}
|
||
}
|
||
|
||
/* 语音介绍卡片 */
|
||
.voice-intro-card {
|
||
.voice-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
flex-wrap: wrap;
|
||
margin-top: 12rpx;
|
||
}
|
||
.voice-meta {
|
||
font-size: 24rpx;
|
||
color: var(--text-muted);
|
||
}
|
||
.voice-tip {
|
||
display: block;
|
||
margin-top: 12rpx;
|
||
font-size: 22rpx;
|
||
color: var(--text-muted);
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
/* 服务包操作与表单弹窗 */
|
||
.srv-ops {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
margin-left: auto;
|
||
}
|
||
.srv-op {
|
||
font-size: 24rpx;
|
||
color: var(--color-brand, #6366F1);
|
||
padding: 4rpx 8rpx;
|
||
}
|
||
.srv-op-danger {
|
||
color: #FF5F57;
|
||
}
|
||
.service-form-body {
|
||
padding: 8rpx 4rpx 4rpx;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
.form-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.form-label {
|
||
width: 180rpx;
|
||
font-size: 26rpx;
|
||
color: var(--text-secondary, #94A3B8);
|
||
flex-shrink: 0;
|
||
}
|
||
.form-input {
|
||
flex: 1;
|
||
height: 68rpx;
|
||
padding: 0 16rpx;
|
||
border-radius: 12rpx;
|
||
background: var(--bg-surface-elevated, rgba(255, 255, 255, 0.06));
|
||
color: var(--text-primary, #E2E8F0);
|
||
font-size: 26rpx;
|
||
}
|
||
.form-textarea {
|
||
flex: 1;
|
||
min-height: 120rpx;
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
background: var(--bg-surface-elevated, rgba(255, 255, 255, 0.06));
|
||
color: var(--text-primary, #E2E8F0);
|
||
font-size: 26rpx;
|
||
}
|
||
.form-hint {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: var(--text-muted, #64748B);
|
||
line-height: 1.6;
|
||
}
|
||
</style>
|