文件
shanghaipeiwan/pwuniapp/pages/expert/apply.vue
xcbyy a249a5ee62 feat(voice): 录音时实时读秒,60 秒自动停止
- 入驻申请页/大神工作台:点击开始录音后,按钮显示已录秒数,状态栏显示剩余秒数(每秒刷新)

- 录音中按钮转为红橙渐变并呼吸闪烁,状态文字高亮;离开页面/出错自动清理计时器

- 60 秒到点自动 stop 录音,避免超长被服务端拒绝

- voice-intro-consistency 增补读秒护栏(107 套件/710 用例全绿)
2026-10-21 11:20:22 +08:00

1203 行
31 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="expert-apply-page" :class="{ 'theme-warm': currentThemeKey === 'warm' }">
<!-- 1. 状态一:已通过实名认证 (APPROVED) -->
<view class="status-card approved-card" v-if="certStatus === CERT_STATUS.APPROVED">
<view class="status-icon-wrap bg-green">
<image class="status-icon-svg" :src="ICONS.shield" mode="aspectFit" />
</view>
<text class="status-title">已通过大神实名认证</text>
<text class="status-desc">您已具备平台专属接单特权,可前往工作台开启接单服务与收益结算。</text>
<view class="status-info-box">
<view class="info-row">
<text class="info-k">认证身份</text>
<text class="info-v highlight">平台认证金牌陪伴师</text>
</view>
<view class="info-row">
<text class="info-k">权限状态</text>
<text class="info-v green">接单功能已就绪</text>
</view>
</view>
<button class="btn-action btn-primary btn-active" @click="navToWorkspace">
进入接单工作台
</button>
<button class="btn-action btn-sub btn-active" @click="navBack">
返回个人中心
</button>
</view>
<!-- 2. 状态二:认证审核中 (PENDING) -->
<view class="status-card pending-card" v-else-if="certStatus === CERT_STATUS.PENDING">
<view class="status-icon-wrap bg-amber">
<image class="status-icon-svg" :src="ICONS.clock" mode="aspectFit" />
</view>
<text class="status-title">实名认证审核中</text>
<text class="status-desc">您的入驻申请已成功提交至平台审核中心,运营团队将在 24 小时内完成人工核验。</text>
<view class="audit-timeline">
<view class="timeline-step done">
<view class="step-dot"></view>
<text class="step-txt">提交申请</text>
</view>
<view class="timeline-line active"></view>
<view class="timeline-step processing">
<view class="step-dot"></view>
<text class="step-txt">官方核验中</text>
</view>
<view class="timeline-line"></view>
<view class="timeline-step">
<view class="step-dot"></view>
<text class="step-txt">审核开通</text>
</view>
</view>
<view class="status-info-box">
<view class="info-row">
<text class="info-k">审核说明</text>
<text class="info-v">核验通过后系统将自动开通接单模式</text>
</view>
<view class="info-row">
<text class="info-k">服务保障</text>
<text class="info-v">平台资金托管与 7x24h 履约安全保障</text>
</view>
</view>
<button class="btn-action btn-primary btn-active" @click="contactService">
联系官方在线客服
</button>
<button class="btn-action btn-sub btn-active" @click="navBack">
返回个人中心
</button>
</view>
<!-- 3. 状态三:未认证 / 重新申请 (NOT_APPLIED / REJECTED) -->
<view class="form-container" v-else>
<!-- 审核驳回反馈横幅 (当后台驳回时展示具体原因并引导修改重提) -->
<view class="notice-banner reject-banner" v-if="certStatus === CERT_STATUS.REJECTED">
<image class="banner-icon-svg" :src="ICONS.alert" mode="aspectFit" />
<view class="banner-content">
<text class="banner-title">上次入驻审核未通过,请修改后重新提交</text>
<text class="banner-desc">驳回原因:{{ rejectReason || '提交的资质或语音未通过人工核验,请核对后重新提交' }}</text>
</view>
</view>
<!-- 平台合规与安全保障提示横幅 -->
<view class="notice-banner">
<image class="banner-icon-svg" :src="ICONS.shield" mode="aspectFit" />
<view class="banner-content">
<text class="banner-title">实名认证与合规入驻指引</text>
<text class="banner-desc">为构建健康绿色的同城陪伴生态,申请者须提交真实有效的身份信息,平台全面落实微信内容安全与用户隐私保障。</text>
</view>
</view>
<!-- 入驻表单卡片 -->
<view class="apply-card">
<!-- 真实姓名 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">真实姓名</text>
<text class="required-star">*</text>
</view>
<input
class="input-control"
v-model="applyForm.realName"
maxlength="20"
placeholder="请输入与身份证完全一致的姓名"
placeholder-class="ph-style"
/>
</view>
<!-- 身份证号码 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">身份证号码</text>
<text class="required-star">*</text>
</view>
<input
class="input-control"
v-model="applyForm.idCard"
maxlength="18"
placeholder="请输入 18 位二代居民身份证号"
placeholder-class="ph-style"
/>
</view>
<!-- 主营陪伴品类 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">主营陪伴品类</text>
<text class="required-star">*</text>
</view>
<view class="track-grid">
<view
v-for="trk in TRACK_OPTIONS"
:key="trk"
class="track-item btn-active"
:class="{ active: applyForm.track === trk }"
@click="applyForm.track = trk"
>
{{ trk }}
</view>
</view>
</view>
<!-- 期望服务单价 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">期望服务单价 (元/小时)</text>
<text class="required-star">*</text>
<text class="label-hint">限价 ¥{{ expertPriceMin }} - ¥{{ expertPriceMax }}</text>
</view>
<view class="price-input-wrap">
<text class="price-unit">¥</text>
<input
class="price-control"
type="number"
v-model="applyForm.hourlyRate"
:placeholder="`推荐单价 ¥${expertDefaultRate}`"
placeholder-class="ph-style"
/>
</view>
</view>
<!-- 技能专长标签 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">技能专长标签</text>
<text class="label-hint">(最多任选 6 项,展示在个人接单主页)</text>
</view>
<view class="tags-cloud">
<view
v-for="tag in candidateTags"
:key="tag"
class="tag-pill btn-active"
:class="{ active: applyForm.tags.includes(tag) }"
@click="toggleTag(tag)"
>
{{ tag }}
</view>
</view>
</view>
<!-- 语音自我介绍(可选):设计文档「在线录制试听」落地 -->
<view class="form-item">
<view class="item-label-row">
<text class="item-label">语音自我介绍</text>
<text class="label-hint">(可选,≤60 秒;审核与买家试听用)</text>
</view>
<view class="voice-row">
<button class="voice-btn btn-active" :class="{ recording: voiceRecording }" :disabled="voiceUploading" @click="toggleRecord">
{{ voiceRecording ? `停止录音 ${voiceSeconds}s` : (voiceRecorded ? '重新录制' : '开始录音') }}
</button>
<button v-if="voiceRecorded && !voiceRecording" class="voice-btn btn-active" @click="playRecorded">
{{ voicePlaying ? '停止播放' : '试听' }}
</button>
<text class="voice-status" :class="{ recording: voiceRecording }">{{ voiceStatusText }}</text>
</view>
<text class="voice-tip">语音沟通在游戏内或微信进行,请勿在录音中透露联系方式。</text>
</view>
</view>
<!-- 协议勾选与服务承诺 -->
<view class="agreement-row" @click="agreed = !agreed">
<view class="check-box" :class="{ checked: agreed }">
<view class="check-inner" v-if="agreed"></view>
</view>
<view class="agreement-text">
<text>我已阅读并完全同意</text>
<text class="link-text" @click.stop="openAgreement">《陪伴师入驻规范与安全履约协议》</text>
<text>,保证所提交全部材料真实合法。</text>
</view>
</view>
<!-- 底部固定提交按钮卡片 -->
<view class="bottom-submit-bar">
<button
class="btn-submit btn-active"
:class="{ disabled: isSubmitting }"
@click="submitApply"
>
{{ isSubmitting ? '正在提交入驻材料...' : '立即提交实名认证申请' }}
</button>
</view>
</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 { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getExpertCertStatus, setExpertCertStatus, CERT_STATUS } from '@/common/role.js';
import { applyExpertCert, getPlatformConfigs, getUserProfile, uploadVoiceIntro, normalizeUserId } from '@/common/api.js';
const currentThemeKey = ref(getCurrentThemeKey());
const certStatus = ref(getExpertCertStatus());
const isSubmitting = ref(false);
const agreed = ref(true);
// 语音自我介绍(可选,≤60s)
const voiceRecording = ref(false);
const voiceRecorded = ref(false);
const voicePlaying = ref(false);
const voiceUploading = ref(false);
const voiceDuration = ref(0);
const voiceSeconds = ref(0);
let voiceTempPath = '';
let recorderManager = null;
let voiceAudioCtx = null;
let voiceTimer = null;
const voiceStatusText = computed(() => {
if (voiceUploading.value) return '上传中…';
if (voiceRecording.value) return `录音中 · 剩余 ${Math.max(0, 60 - voiceSeconds.value)}s`;
if (voiceRecorded.value) return `已录制 ${voiceDuration.value} 秒(提交后自动上传)`;
return '未录制(可跳过)';
});
const stopVoiceTimer = () => {
if (voiceTimer) {
clearInterval(voiceTimer);
voiceTimer = null;
}
};
const startVoiceTimer = () => {
stopVoiceTimer();
voiceSeconds.value = 0;
voiceTimer = setInterval(() => {
voiceSeconds.value += 1;
if (voiceSeconds.value >= 60) {
stopVoiceTimer();
try { recorderManager && recorderManager.stop(); } catch (_) {}
}
}, 1000);
};
const ensureRecorder = () => {
if (recorderManager) return recorderManager;
recorderManager = uni.getRecorderManager();
recorderManager.onStop((res) => {
stopVoiceTimer();
voiceRecording.value = false;
voiceTempPath = res.tempFilePath || '';
voiceDuration.value = Math.max(1, Math.round(Number(res.duration || 0) / 1000) || 1);
voiceRecorded.value = Boolean(voiceTempPath);
});
recorderManager.onError(() => {
stopVoiceTimer();
voiceRecording.value = false;
uni.showToast({ title: '录音失败,请检查麦克风权限', icon: 'none' });
});
return recorderManager;
};
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;
startVoiceTimer();
};
uni.authorize({
scope: 'scope.record',
success: start,
fail: () => uni.showToast({ title: '请先允许使用麦克风', icon: 'none' })
});
};
const playRecorded = () => {
if (!voiceTempPath) 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 = voiceTempPath;
voiceAudioCtx.play();
voicePlaying.value = true;
};
onUnmounted(() => {
stopVoiceTimer();
try { if (recorderManager && voiceRecording.value) recorderManager.stop(); } catch (_) {}
try { if (voiceAudioCtx) { voiceAudioCtx.destroy(); voiceAudioCtx = null; } } catch (_) {}
});
const TRACK_OPTIONS = [
'同城陪吃',
'KTV陪唱',
'同城陪喝',
'线上电竞',
'同城桌游',
'运动健身'
];
const platformConfigs = ref({
expert_rules: {
minHourlyRate: 30,
maxHourlyRate: 500,
defaultHourlyRate: 88,
candidateTags: [
'国服王者', '声优达人', '幽默风趣', '随叫随到', '暖心倾听',
'百星野王', '金牌探店', '量贩麦霸', '微醺搭子', '绅士礼貌', '活力开朗'
]
},
customer_service: {
hotline: '400-888-6666',
wechat: 'peiwan_vip_kefu'
}
});
const applyForm = ref({
realName: '',
idCard: '',
track: '同城陪吃',
hourlyRate: 88,
tags: ['幽默风趣', '随叫随到']
});
const expertPriceMin = computed(() => {
return Number(platformConfigs.value.expert_rules?.minHourlyRate) || 30;
});
const expertPriceMax = computed(() => {
return Number(platformConfigs.value.expert_rules?.maxHourlyRate) || 500;
});
const expertDefaultRate = computed(() => {
return Number(platformConfigs.value.expert_rules?.defaultHourlyRate) || 88;
});
const candidateTags = computed(() => {
const tags = platformConfigs.value.expert_rules?.candidateTags;
if (Array.isArray(tags) && tags.length > 0) {
return tags;
}
return [
'国服王者', '声优达人', '幽默风趣', '随叫随到', '暖心倾听',
'百星野王', '金牌探店', '量贩麦霸', '微醺搭子', '绅士礼貌', '活力开朗'
];
});
const loadInitialConfigs = async () => {
try {
const res = await getPlatformConfigs();
if (res && typeof res === 'object') {
platformConfigs.value = {
...platformConfigs.value,
...res
};
if (platformConfigs.value.expert_rules?.defaultHourlyRate) {
applyForm.value.hourlyRate = platformConfigs.value.expert_rules.defaultHourlyRate;
}
}
} catch (err) {
console.warn('[Apply] 加载系统规则降级:', err);
}
};
const toggleTag = (tag) => {
const idx = applyForm.value.tags.indexOf(tag);
if (idx > -1) {
applyForm.value.tags.splice(idx, 1);
} else {
if (applyForm.value.tags.length >= 6) {
uni.showToast({ title: '最多选择 6 个技能标签', icon: 'none' });
return;
}
applyForm.value.tags.push(tag);
}
};
const openAgreement = () => {
uni.showModal({
title: '陪伴师入驻与安全规范',
content: '1. 陪伴师在线下履约约局时,须前往合规公开商业街区场所;\n2. 碰面后必须由用户提供 4 位数字核销码进行现场签到核销;\n3. 严禁任何私下脱离平台交易、索要微信或违规低俗行为,平台保留实时追责与仲裁扣除保证金权利。',
showCancel: false,
confirmText: '我知道了'
});
};
const submitApply = async () => {
if (isSubmitting.value) return;
if (!applyForm.value.realName || !applyForm.value.realName.trim()) {
uni.showToast({ title: '请输入真实姓名', icon: 'none' });
return;
}
if (!applyForm.value.idCard || applyForm.value.idCard.trim().length < 15) {
uni.showToast({ title: '请输入有效的身份证号码', icon: 'none' });
return;
}
const rate = Number(applyForm.value.hourlyRate) || expertDefaultRate.value;
if (rate < expertPriceMin.value || rate > expertPriceMax.value) {
uni.showToast({
title: `单价需在 ¥${expertPriceMin.value} - ¥${expertPriceMax.value} 之间`,
icon: 'none'
});
return;
}
if (!agreed.value) {
uni.showToast({ title: '请先勾选并同意服务协议', icon: 'none' });
return;
}
isSubmitting.value = true;
uni.showLoading({ title: '正在提交入驻材料...' });
// 身份与登录态保持一致:有 token 时由服务端按 token 识别(缺失 userId 传 0)
const uid = normalizeUserId();
const profileUid = uid || 1001;
try {
let avatarUrl = '';
let gender = '男';
try {
const userProfile = await getUserProfile(profileUid);
if (userProfile) {
avatarUrl = userProfile.avatar || '';
gender = userProfile.gender === 'female' ? '女' : '男';
}
} catch (_) {}
const applied = await applyExpertCert({
userId: uid,
name: applyForm.value.realName.trim(),
idCard: applyForm.value.idCard.trim(),
track: applyForm.value.track,
hourlyRate: rate,
tags: applyForm.value.tags,
avatar: avatarUrl,
gender: gender
});
// 已录制的语音介绍,在申请成功(拿到大神档案 id)后自动上传;失败不影响申请结果
if (voiceTempPath && applied && applied.id) {
voiceUploading.value = true;
try {
await uploadVoiceIntro(applied.id, voiceTempPath, voiceDuration.value);
voiceRecorded.value = false;
voiceTempPath = '';
} catch (e) {
console.warn('[Apply] 语音介绍上传失败:', e);
uni.showToast({ title: '入驻已提交,语音上传失败可在工作台重录', icon: 'none', duration: 2600 });
} finally {
voiceUploading.value = false;
}
}
setExpertCertStatus(CERT_STATUS.PENDING);
certStatus.value = CERT_STATUS.PENDING;
uni.hideLoading();
isSubmitting.value = false;
uni.showModal({
title: '入驻申请提交成功',
content: '您的入驻资料已送达官方审核中心。平台运营团队将在 24 小时内完成人工核准,核准后将自动激活接单权限。',
showCancel: false,
confirmText: '我知道了'
});
} catch (err) {
uni.hideLoading();
isSubmitting.value = false;
uni.showToast({
title: err.message || '提交失败,请检查输入或稍后重试',
icon: 'none',
duration: 2500
});
}
};
const navToWorkspace = () => {
uni.navigateTo({ url: '/pages/expert/hall' });
};
const navBack = () => {
uni.navigateBack({
fail: () => {
uni.switchTab({ url: '/pages/user/index' });
}
});
};
const contactService = () => {
const cs = platformConfigs.value.customer_service || {};
const hotline = cs.hotline || '400-888-6666';
uni.showActionSheet({
itemList: [
`拨打平台客服专线 (${hotline})`,
'查看平台官方履约指引'
],
success: (res) => {
if (res.tapIndex === 0) {
uni.makePhoneCall({
phoneNumber: hotline.replace(/[^\d]/g, ''),
fail: () => {
uni.showToast({ title: `客服热线: ${hotline}`, icon: 'none' });
}
});
} else {
openAgreement();
}
}
});
};
const rejectReason = ref('');
const syncRemoteCertStatus = async () => {
try {
const cachedUser = uni.getStorageSync('PW_USER_INFO') || {};
const uid = Number(cachedUser.id) || 1001;
const profile = await getUserProfile(uid);
if (profile && profile.certStatus !== undefined) {
const remoteCert = Number(profile.certStatus);
setExpertCertStatus(remoteCert);
certStatus.value = remoteCert;
rejectReason.value = profile.rejectReason || '';
uni.setStorageSync('PW_USER_INFO', {
...cachedUser,
...profile
});
}
} catch (e) {
// 保持本地缓存兜底
}
};
onMounted(() => {
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
certStatus.value = getExpertCertStatus();
loadInitialConfigs();
syncRemoteCertStatus();
});
onShow(() => {
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
certStatus.value = getExpertCertStatus();
syncRemoteCertStatus();
});
</script>
<style lang="scss">
.expert-apply-page {
min-height: 100vh;
background-color: #0A0F1D;
box-sizing: border-box;
padding: 28rpx 32rpx calc(140rpx + env(safe-area-inset-bottom));
}
/* 顶部安全提示横幅 */
.notice-banner {
display: flex;
align-items: flex-start;
gap: 16rpx;
background: rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.25);
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 28rpx;
&.reject-banner {
background: rgba(244, 63, 94, 0.12);
border-color: rgba(244, 63, 94, 0.35);
.banner-title {
color: #FDA4AF;
}
.banner-desc {
color: #FECDD3;
}
}
.banner-icon-svg {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
margin-top: 4rpx;
}
.banner-content {
flex: 1;
.banner-title {
font-size: 26rpx;
font-weight: 700;
color: #818CF8;
display: block;
margin-bottom: 6rpx;
}
.banner-desc {
font-size: 22rpx;
color: #94A3B8;
line-height: 1.5;
display: block;
}
}
}
/* 语音自我介绍(可选) */
.voice-row {
display: flex;
align-items: center;
gap: 16rpx;
flex-wrap: wrap;
}
.voice-btn {
margin: 0;
padding: 0 28rpx;
height: 64rpx;
line-height: 64rpx;
border-radius: 32rpx;
font-size: 24rpx;
color: #FFFFFF;
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%);
}
.voice-btn.recording {
background: linear-gradient(135deg, #EF4444 0%, #F97316 100%);
animation: voice-pulse 1.2s ease-in-out infinite;
}
@keyframes voice-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.72; }
}
.voice-status {
font-size: 22rpx;
color: var(--text-muted, #94A3B8);
}
.voice-status.recording {
color: #F97316;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.voice-tip {
display: block;
margin-top: 12rpx;
font-size: 22rpx;
color: var(--text-muted, #64748B);
line-height: 1.6;
}
/* 主表单卡片 */
.apply-card {
background: #151D30;
border-radius: 24rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
padding: 32rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.35);
.form-item {
margin-bottom: 32rpx;
&:last-child {
margin-bottom: 0;
}
.item-label-row {
display: flex;
align-items: center;
gap: 8rpx;
margin-bottom: 16rpx;
.item-label {
font-size: 26rpx;
font-weight: 600;
color: #F8FAFC;
}
.required-star {
font-size: 26rpx;
color: #EF4444;
font-weight: 700;
}
.label-hint {
font-size: 22rpx;
color: #64748B;
margin-left: 8rpx;
}
}
.input-control {
width: 100%;
height: 84rpx;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #F8FAFC;
}
.price-input-wrap {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16rpx;
padding: 0 24rpx;
height: 84rpx;
.price-unit {
font-size: 32rpx;
font-weight: 700;
color: #10B981;
margin-right: 12rpx;
}
.price-control {
flex: 1;
font-size: 32rpx;
font-weight: 600;
color: #10B981;
}
}
.ph-style {
color: #64748B;
font-size: 26rpx;
}
.track-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
.track-item {
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16rpx;
font-size: 24rpx;
color: #CBD5E1;
font-weight: 500;
transition: all 0.2s ease;
&.active {
background: rgba(99, 102, 241, 0.18);
border-color: #6366F1;
color: #818CF8;
font-weight: 700;
}
}
}
.tags-cloud {
display: flex;
flex-wrap: wrap;
gap: 14rpx;
.tag-pill {
padding: 10rpx 22rpx;
border-radius: 28rpx;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
font-size: 24rpx;
color: #94A3B8;
transition: all 0.2s ease;
&.active {
background: rgba(16, 185, 129, 0.18);
border-color: #10B981;
color: #10B981;
font-weight: 600;
}
}
}
}
}
/* 协议行 */
.agreement-row {
display: flex;
align-items: center;
gap: 14rpx;
margin-top: 24rpx;
padding: 0 8rpx;
.check-box {
width: 36rpx;
height: 36rpx;
border-radius: 8rpx;
border: 2px solid #64748B;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&.checked {
background: #6366F1;
border-color: #6366F1;
.check-inner {
width: 14rpx;
height: 14rpx;
background: #FFFFFF;
border-radius: 4rpx;
}
}
}
.agreement-text {
font-size: 22rpx;
color: #94A3B8;
line-height: 1.5;
.link-text {
color: #818CF8;
font-weight: 600;
}
}
}
/* 底部提交悬浮条 */
.bottom-submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx 32rpx calc(20rpx + env(safe-area-inset-bottom));
background: rgba(21, 29, 48, 0.95);
backdrop-filter: blur(12px);
border-top: 1px solid rgba(255, 255, 255, 0.08);
max-width: 450px;
margin: 0 auto;
box-sizing: border-box;
z-index: 100;
.btn-submit {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
color: #FFFFFF;
font-size: 30rpx;
font-weight: 700;
border: none;
box-shadow: 0 6rpx 20rpx rgba(99, 102, 241, 0.4);
&.disabled {
opacity: 0.6;
}
}
}
/* 状态结果卡片 (已认证/审核中) */
.status-card {
background: #151D30;
border-radius: 28rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
padding: 48rpx 36rpx;
text-align: center;
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.4);
margin-top: 24rpx;
.status-icon-wrap {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin: 0 auto 28rpx;
display: flex;
align-items: center;
justify-content: center;
&.bg-green {
background: rgba(16, 185, 129, 0.15);
border: 2px solid rgba(16, 185, 129, 0.35);
}
&.bg-amber {
background: rgba(245, 158, 11, 0.15);
border: 2px solid rgba(245, 158, 11, 0.35);
}
.status-icon-svg {
width: 60rpx;
height: 60rpx;
}
}
.status-title {
font-size: 34rpx;
font-weight: 700;
color: #F8FAFC;
display: block;
margin-bottom: 12rpx;
}
.status-desc {
font-size: 24rpx;
color: #94A3B8;
line-height: 1.6;
display: block;
margin-bottom: 36rpx;
}
.audit-timeline {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
padding: 0 20rpx;
.timeline-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
.step-dot {
width: 18rpx;
height: 18rpx;
border-radius: 50%;
background: #475569;
}
.step-txt {
font-size: 20rpx;
color: #64748B;
}
&.done {
.step-dot {
background: #10B981;
}
.step-txt {
color: #10B981;
font-weight: 600;
}
}
&.processing {
.step-dot {
background: #F59E0B;
box-shadow: 0 0 10rpx #F59E0B;
}
.step-txt {
color: #F59E0B;
font-weight: 600;
}
}
}
.timeline-line {
flex: 1;
height: 4rpx;
background: #334155;
margin: 0 12rpx 28rpx;
&.active {
background: #F59E0B;
}
}
}
.status-info-box {
background: rgba(255, 255, 255, 0.03);
border-radius: 18rpx;
padding: 24rpx;
margin-bottom: 40rpx;
.info-row {
display: flex;
justify-content: space-between;
font-size: 24rpx;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.info-k {
color: #94A3B8;
}
.info-v {
color: #F8FAFC;
font-weight: 500;
&.highlight {
color: #F59E0B;
font-weight: 600;
}
&.green {
color: #10B981;
font-weight: 600;
}
}
}
}
.btn-action {
height: 84rpx;
line-height: 84rpx;
border-radius: 42rpx;
font-size: 28rpx;
font-weight: 600;
margin-bottom: 20rpx;
&.btn-primary {
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
color: #FFFFFF;
box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.35);
border: none;
}
&.btn-sub {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #94A3B8;
}
}
}
/* 浅色暖阳晨曦模式适配 */
.theme-warm {
background-color: #FAF8F5;
.notice-banner {
background: #FFF7ED;
border-color: #FED7AA;
.banner-title {
color: #EA580C;
}
.banner-desc {
color: #78716C;
}
}
.apply-card {
background: #FFFFFF;
border-color: #E7E5E4;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
.form-item {
.item-label-row {
.item-label {
color: #1C1917;
}
.label-hint {
color: #78716C;
}
}
.input-control,
.price-input-wrap {
background: #FDFBF7;
border-color: #E7E5E4;
color: #1C1917;
}
.ph-style {
color: #A8A29E;
}
.price-input-wrap .price-unit,
.price-input-wrap .price-control {
color: #EA580C;
}
.track-grid .track-item {
background: #FDFBF7;
border-color: #E7E5E4;
color: #44403C;
&.active {
background: #FFF7ED;
border-color: #EA580C;
color: #EA580C;
}
}
.tags-cloud .tag-pill {
background: #FDFBF7;
border-color: #E7E5E4;
color: #78716C;
&.active {
background: #FFF7ED;
border-color: #EA580C;
color: #EA580C;
}
}
}
}
.agreement-row {
.check-box {
border-color: #D6D3D1;
&.checked {
background: #EA580C;
border-color: #EA580C;
}
}
.agreement-text {
color: #78716C;
.link-text {
color: #EA580C;
}
}
}
.bottom-submit-bar {
background: rgba(255, 255, 255, 0.96);
border-top-color: #E7E5E4;
.btn-submit {
background: linear-gradient(135deg, #EA580C 0%, #C2410C 100%);
box-shadow: 0 6rpx 20rpx rgba(234, 88, 12, 0.35);
}
}
.status-card {
background: #FFFFFF;
border-color: #E7E5E4;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
.status-title {
color: #1C1917;
}
.status-desc {
color: #78716C;
}
.status-info-box {
background: #FDFBF7;
.info-row {
.info-k {
color: #78716C;
}
.info-v {
color: #1C1917;
}
}
}
.btn-action.btn-sub {
background: #FDFBF7;
border-color: #E7E5E4;
color: #57534E;
}
}
}
</style>