feat(expert): create dedicated expert apply page and navigate from user center
这个提交包含在:
@@ -56,6 +56,15 @@
|
||||
"backgroundColor": "#0A0F1D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/expert/apply",
|
||||
"style": {
|
||||
"navigationBarTitleText": "大神入驻认证",
|
||||
"navigationBarBackgroundColor": "#0A0F1D",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#0A0F1D"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/create",
|
||||
"style": {
|
||||
|
||||
983
pwuniapp/pages/expert/apply.vue
普通文件
983
pwuniapp/pages/expert/apply.vue
普通文件
@@ -0,0 +1,983 @@
|
||||
<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">
|
||||
<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>
|
||||
|
||||
<!-- 协议勾选与服务承诺 -->
|
||||
<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 } 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 } from '@/common/api.js';
|
||||
|
||||
const currentThemeKey = ref(getCurrentThemeKey());
|
||||
const certStatus = ref(getExpertCertStatus());
|
||||
const isSubmitting = ref(false);
|
||||
const agreed = ref(true);
|
||||
|
||||
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: '正在提交入驻材料...' });
|
||||
const uid = Number(uni.getStorageSync('userId')) || 1001;
|
||||
|
||||
try {
|
||||
let avatarUrl = '';
|
||||
let gender = '男';
|
||||
try {
|
||||
const userProfile = await getUserProfile(uid);
|
||||
if (userProfile) {
|
||||
avatarUrl = userProfile.avatar || '';
|
||||
gender = userProfile.gender === 'female' ? '女' : '男';
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
currentThemeKey.value = getCurrentThemeKey();
|
||||
applyThemeToNativeUI(currentThemeKey.value);
|
||||
certStatus.value = getExpertCertStatus();
|
||||
loadInitialConfigs();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
currentThemeKey.value = getCurrentThemeKey();
|
||||
applyThemeToNativeUI(currentThemeKey.value);
|
||||
certStatus.value = getExpertCertStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 主表单卡片 */
|
||||
.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>
|
||||
@@ -261,74 +261,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 大神实名认证与资质入驻申请弹窗 (任务四:真实资料与后台审核) -->
|
||||
<view class="profile-edit-modal-mask" v-if="showExpertCertModal" @click="showExpertCertModal = false">
|
||||
<view class="profile-edit-modal expert-cert-modal" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">陪伴师 / 大神入驻实名申请</text>
|
||||
<view class="btn-close btn-active" @click="showExpertCertModal = false">
|
||||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<view class="cert-alert">
|
||||
<text class="cert-alert-text">提交真实有效资料,平台将在 24 小时内由运营团队人工核验后开通接单工作台。</text>
|
||||
</view>
|
||||
|
||||
<view class="edit-field">
|
||||
<text class="field-label">真实姓名</text>
|
||||
<input class="field-input" v-model="expertCertForm.realName" placeholder="如:张三 (须与身份证一致)" placeholder-class="field-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="edit-field">
|
||||
<text class="field-label">身份证号码</text>
|
||||
<input class="field-input" v-model="expertCertForm.idCard" maxlength="18" placeholder="18位二代身份证号" placeholder-class="field-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="edit-field">
|
||||
<text class="field-label">主营陪伴品类</text>
|
||||
<view class="track-chips">
|
||||
<view
|
||||
v-for="trk in ['同城陪吃', 'KTV陪唱', '同城陪喝', '线上电竞', '同城桌游']"
|
||||
:key="trk"
|
||||
class="track-chip btn-active"
|
||||
:class="{ active: expertCertForm.track === trk }"
|
||||
@click="expertCertForm.track = trk"
|
||||
>
|
||||
{{ trk }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="edit-field">
|
||||
<text class="field-label">期望服务单价 (元/小时,平台限价: ¥{{ expertPriceMin }} - ¥{{ expertPriceMax }})</text>
|
||||
<input class="field-input price-num-input" type="number" v-model="expertCertForm.hourlyRate" :placeholder="`推荐单价 ¥${expertDefaultRate}`" placeholder-class="field-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="edit-field" v-if="expertCandidateTags.length > 0">
|
||||
<text class="field-label">技能专长标签 (最多选6项)</text>
|
||||
<view class="tags-chips">
|
||||
<view
|
||||
v-for="tg in expertCandidateTags"
|
||||
:key="tg"
|
||||
class="tag-pill btn-active"
|
||||
:class="{ active: expertCertForm.tags.includes(tg) }"
|
||||
@click="toggleCertTag(tg)"
|
||||
>
|
||||
{{ tg }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="modal-footer">
|
||||
<button class="btn-modal btn-cancel btn-active" @click="showExpertCertModal = false">取消</button>
|
||||
<button class="btn-modal btn-save btn-active" @click="submitExpertCertForm">提交审核</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部自定义导航栏 (动态自适应发单人4项 / 接单人3项) -->
|
||||
<AppTabBar current="user" />
|
||||
|
||||
@@ -346,7 +278,7 @@ import { ICONS } from '@/common/icons.js';
|
||||
import { getCurrentThemeKey, setTheme, THEME_KEYS, applyThemeToNativeUI } from '@/common/theme.js';
|
||||
import { getCurrentRole, setCurrentRole, ROLE_TYPES, toggleRole as toggleUserRole, isExpertCertified, getExpertCertStatus, setExpertCertStatus, CERT_STATUS } from '@/common/role.js';
|
||||
import { getNavBarInfo } from '@/common/nav.js';
|
||||
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs } from '@/common/api.js';
|
||||
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert } from '@/common/api.js';
|
||||
|
||||
const DEFAULT_GUEST_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
|
||||
const isLoggedIn = ref(true);
|
||||
@@ -895,115 +827,14 @@ const showInvitePoster = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 大神实名入驻认证已全面升级重构为独立专享页面 /pages/expert/apply
|
||||
const showExpertCertModal = ref(false);
|
||||
const expertCertForm = ref({
|
||||
realName: '',
|
||||
idCard: '',
|
||||
track: '同城陪吃',
|
||||
hourlyRate: 88,
|
||||
tags: []
|
||||
});
|
||||
|
||||
const expertPriceMin = computed(() => {
|
||||
return platformConfigs.value.expert_rules?.minHourlyRate || 30;
|
||||
});
|
||||
const expertPriceMax = computed(() => {
|
||||
return platformConfigs.value.expert_rules?.maxHourlyRate || 500;
|
||||
});
|
||||
const expertDefaultRate = computed(() => {
|
||||
return platformConfigs.value.expert_rules?.defaultHourlyRate || 88;
|
||||
});
|
||||
const expertCandidateTags = computed(() => {
|
||||
return platformConfigs.value.expert_rules?.candidateTags || [
|
||||
'国服王者', '声优达人', '幽默风趣', '随叫随到', '暖心倾听',
|
||||
'百星野王', '金牌探店', '量贩麦霸', '微醺搭子'
|
||||
];
|
||||
});
|
||||
|
||||
const toggleCertTag = (tag) => {
|
||||
const idx = expertCertForm.value.tags.indexOf(tag);
|
||||
if (idx > -1) {
|
||||
expertCertForm.value.tags.splice(idx, 1);
|
||||
} else {
|
||||
if (expertCertForm.value.tags.length >= 6) {
|
||||
uni.showToast({ title: '最多选择6个技能标签', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
expertCertForm.value.tags.push(tag);
|
||||
}
|
||||
};
|
||||
|
||||
const submitExpertCertForm = async () => {
|
||||
if (!expertCertForm.value.realName || !expertCertForm.value.realName.trim()) {
|
||||
uni.showToast({ title: '请输入真实姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!expertCertForm.value.idCard || expertCertForm.value.idCard.length < 15) {
|
||||
uni.showToast({ title: '请输入有效身份证号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const rate = Number(expertCertForm.value.hourlyRate) || expertDefaultRate.value;
|
||||
if (rate < expertPriceMin.value || rate > expertPriceMax.value) {
|
||||
uni.showToast({ title: `单价需在 ¥${expertPriceMin.value} - ¥${expertPriceMax.value} 之间`, icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '正在提交认证...' });
|
||||
try {
|
||||
await applyExpertCert({
|
||||
userId: user.value.id || 1001,
|
||||
name: expertCertForm.value.realName.trim(),
|
||||
idCard: expertCertForm.value.idCard.trim(),
|
||||
track: expertCertForm.value.track,
|
||||
hourlyRate: rate,
|
||||
tags: expertCertForm.value.tags,
|
||||
avatar: user.value.avatar,
|
||||
gender: user.value.gender === 'male' ? '男' : '女'
|
||||
});
|
||||
setExpertCertStatus(CERT_STATUS.PENDING);
|
||||
showExpertCertModal.value = false;
|
||||
uni.hideLoading();
|
||||
uni.showModal({
|
||||
title: '申请已提交',
|
||||
content: '您的入驻申请已成功提交至平台审核中心,运营团队核准后即可自动开通接单工作台。',
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
});
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: e.message || '提交申请失败', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const applyExpert = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
const certStatus = getExpertCertStatus();
|
||||
if (certStatus === CERT_STATUS.APPROVED) {
|
||||
uni.showToast({
|
||||
title: '您已通过大神实名认证,接单权限正常',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (certStatus === CERT_STATUS.PENDING) {
|
||||
uni.showModal({
|
||||
title: '认证审核中',
|
||||
content: '您的入驻申请正在平台运营端审核中,请耐心等待核准。',
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
expertCertForm.value = {
|
||||
realName: '',
|
||||
idCard: '',
|
||||
track: '同城陪吃',
|
||||
hourlyRate: expertDefaultRate.value,
|
||||
tags: []
|
||||
};
|
||||
showExpertCertModal.value = true;
|
||||
uni.navigateTo({
|
||||
url: '/pages/expert/apply'
|
||||
});
|
||||
};
|
||||
|
||||
const contactService = () => {
|
||||
@@ -1854,73 +1685,6 @@ const navTo = (url) => {
|
||||
}
|
||||
}
|
||||
|
||||
.expert-cert-modal {
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.cert-alert {
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
border: 1px solid rgba(99, 102, 241, 0.2);
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.cert-alert-text {
|
||||
font-size: 24rpx;
|
||||
color: #6366F1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.track-chips, .tags-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14rpx;
|
||||
margin-top: 10rpx;
|
||||
|
||||
.track-chip, .tag-pill {
|
||||
font-size: 24rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 30rpx;
|
||||
background: var(--bg-card, #1E293B);
|
||||
color: #94A3B8;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
background: #6366F1;
|
||||
color: #FFFFFF;
|
||||
border-color: #6366F1;
|
||||
box-shadow: 0 4rpx 12rpx rgba(99, 102, 241, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-warm .expert-cert-modal {
|
||||
.cert-alert {
|
||||
background: #FFF7ED;
|
||||
border-color: #FDBA74;
|
||||
|
||||
.cert-alert-text {
|
||||
color: #EA580C;
|
||||
}
|
||||
}
|
||||
|
||||
.track-chips .track-chip, .tags-chips .tag-pill {
|
||||
background: #FDFBF7;
|
||||
color: #78716C;
|
||||
border-color: #E7E2D8;
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #EA580C, #F97316);
|
||||
color: #FFFFFF;
|
||||
border-color: #EA580C;
|
||||
box-shadow: 0 4rpx 14rpx rgba(234, 88, 12, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.guest-badge {
|
||||
font-size: 20rpx;
|
||||
color: #94A3B8;
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
const raw = fs.readFileSync(pagesJsonPath, 'utf-8');
|
||||
const config = JSON.parse(raw);
|
||||
expect(Array.isArray(config.pages)).toBe(true);
|
||||
expect(config.pages.length).toBe(13);
|
||||
expect(config.pages.length).toBe(14);
|
||||
|
||||
for (const pageItem of config.pages) {
|
||||
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);
|
||||
@@ -407,5 +407,44 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
expect(emojiRegex.test(text)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('11. 大神实名认证专属独立页面与多态流程验证 (pages/expert/apply.vue)', () => {
|
||||
it('pages.json 必须成功注册 pages/expert/apply 且物理文件必须存在', () => {
|
||||
const applyPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'apply.vue');
|
||||
expect(fs.existsSync(applyPath)).toBe(true);
|
||||
const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json');
|
||||
const pagesJson = fs.readFileSync(pagesJsonPath, 'utf-8');
|
||||
expect(pagesJson).toContain('pages/expert/apply');
|
||||
expect(pagesJson).toContain('大神入驻认证');
|
||||
});
|
||||
|
||||
it('大神入驻申请独立页面必须包含完整表单、协议声明与提交接口', () => {
|
||||
const applyPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'apply.vue');
|
||||
const content = fs.readFileSync(applyPath, 'utf-8');
|
||||
expect(content).toContain('applyForm');
|
||||
expect(content).toContain('applyExpertCert');
|
||||
expect(content).toContain('TRACK_OPTIONS');
|
||||
expect(content).toContain('expertPriceMin');
|
||||
expect(content).toContain('expertPriceMax');
|
||||
expect(content).toContain('candidateTags');
|
||||
expect(content).toContain('submitApply');
|
||||
expect(content).toContain('实名认证审核中');
|
||||
expect(content).toContain('已通过大神实名认证');
|
||||
});
|
||||
|
||||
it('个人中心的大神入驻入口必须路由跳转至 /pages/expert/apply', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain('/pages/expert/apply');
|
||||
});
|
||||
|
||||
it('新页面与改造代码严禁包含任何系统 Emoji', () => {
|
||||
const applyPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'apply.vue');
|
||||
const content = fs.readFileSync(applyPath, 'utf-8');
|
||||
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
|
||||
expect(emojiRegex.test(content)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户