diff --git a/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue b/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue
index 9c055d2..5b2027d 100644
--- a/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue
+++ b/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue
@@ -469,6 +469,115 @@
+
+
+
+
+
+ 在此灵活调控全平台陪玩订单抽成比例、二级分销裂变返润比例以及陪伴师/玩家佣金提现限制门槛,小程序端推广说明与提现流程将动态同步。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 在此设定同城活动组局的招募席位上下限、AA制默认分摊基准,以及房主请客/悬赏发单时展示的快捷赏金与总预算梯队。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -709,6 +818,59 @@ const saveExpertRulesConfig = () => {
saveConfig('expert_rules', expertRulesForm);
};
+// P1 财务抽成与分销规则
+const financeRulesForm = reactive({
+ name: '平台抽成与推广分销规则',
+ description: '平台佣金抽成比例、二级分销返润与佣金提现门槛限制',
+ status: 1,
+ valueJson: {
+ platformRatePercent: 10.0,
+ firstReferralPercent: 5.0,
+ secondReferralPercent: 2.0,
+ minWithdrawAmount: 50,
+ maxWithdrawAmount: 20000,
+ withdrawFeePercent: 0.0,
+ withdrawDesc: '提现将自动发起微信企业付款打款至微信零钱,工作日预计2小时内到账。'
+ }
+});
+
+const saveFinanceRulesConfig = () => {
+ saveConfig('finance_rules', financeRulesForm);
+};
+
+// P3 组局发单与预算规则
+const activityRulesForm = reactive({
+ name: '同城组局发单与预算预设规范',
+ description: '招募席位上下限、快捷单人赏金预设与包场预算梯队',
+ status: 1,
+ valueJson: {
+ minParticipants: 2,
+ maxParticipants: 10,
+ defaultParticipants: 4,
+ presetPerPersonFees: [200, 300, 500, 800, 1000],
+ presetTotalFees: [500, 1000, 2000, 3000, 5000],
+ defaultAaFee: 50
+ }
+});
+
+const presetPerPersonFeesText = ref('200, 300, 500, 800, 1000');
+const presetTotalFeesText = ref('500, 1000, 2000, 3000, 5000');
+
+const saveActivityRulesConfig = () => {
+ const pFees = presetPerPersonFeesText.value
+ .split(/[,,\s]+/)
+ .map(Number)
+ .filter(n => !isNaN(n) && n > 0);
+ const tFees = presetTotalFeesText.value
+ .split(/[,,\s]+/)
+ .map(Number)
+ .filter(n => !isNaN(n) && n > 0);
+
+ if (pFees.length > 0) activityRulesForm.valueJson.presetPerPersonFees = pFees;
+ if (tFees.length > 0) activityRulesForm.valueJson.presetTotalFees = tFees;
+ saveConfig('activity_rules', activityRulesForm);
+};
+
const fetchConfigs = async () => {
loading.value = true;
try {
@@ -761,6 +923,22 @@ const fetchConfigs = async () => {
if (Array.isArray(expertRulesForm.valueJson.candidateTags)) {
expertCandidateTags.value = expertRulesForm.valueJson.candidateTags;
}
+ } else if (item.configKey === 'finance_rules') {
+ financeRulesForm.name = item.name;
+ financeRulesForm.description = item.description;
+ financeRulesForm.status = item.status;
+ financeRulesForm.valueJson = { ...financeRulesForm.valueJson, ...(item.valueJson || {}) };
+ } else if (item.configKey === 'activity_rules') {
+ activityRulesForm.name = item.name;
+ activityRulesForm.description = item.description;
+ activityRulesForm.status = item.status;
+ activityRulesForm.valueJson = { ...activityRulesForm.valueJson, ...(item.valueJson || {}) };
+ if (Array.isArray(activityRulesForm.valueJson.presetPerPersonFees)) {
+ presetPerPersonFeesText.value = activityRulesForm.valueJson.presetPerPersonFees.join(', ');
+ }
+ if (Array.isArray(activityRulesForm.valueJson.presetTotalFees)) {
+ presetTotalFeesText.value = activityRulesForm.valueJson.presetTotalFees.join(', ');
+ }
}
});
}
diff --git a/pwuniapp/common/api.js b/pwuniapp/common/api.js
index 50c5334..8975a6d 100644
--- a/pwuniapp/common/api.js
+++ b/pwuniapp/common/api.js
@@ -763,3 +763,16 @@ export async function getSafetyPlaces() {
}
}
+/**
+ * 提交大神实名资质入驻认证申请
+ */
+export async function applyExpertCert(data) {
+ try {
+ const res = await http.post('/companions/apply', data);
+ return res;
+ } catch (err) {
+ throw err;
+ }
+}
+
+
diff --git a/pwuniapp/pages/activity/create.vue b/pwuniapp/pages/activity/create.vue
index 51201fd..27de4fe 100644
--- a/pwuniapp/pages/activity/create.vue
+++ b/pwuniapp/pages/activity/create.vue
@@ -80,12 +80,12 @@
招募席位 (人)
- 包含房主在内的组局总人数
+ 包含房主在内的组局总人数 ({{ minParticipants }}-{{ maxParticipants }}人)
-
+
{{ form.maxParticipants }} 人
-
+
@@ -138,7 +138,7 @@
快捷赏金:
快捷预算:
{
try {
- const [placesList, catList] = await Promise.allSettled([
+ const [placesList, catList, configsRes] = await Promise.allSettled([
getSafetyPlaces(),
- getNavCategories()
+ getNavCategories(),
+ getPlatformConfigs()
]);
if (placesList.status === 'fulfilled' && Array.isArray(placesList.value) && placesList.value.length > 0) {
@@ -368,6 +374,21 @@ const loadLivePlaces = async () => {
.map((c) => c.name);
if (activeCats.length > 0) {
games.value = activeCats;
+ if (!games.value.includes(form.value.game)) {
+ form.value.game = games.value[0];
+ }
+ }
+ }
+
+ if (configsRes.status === 'fulfilled' && configsRes.value?.activity_rules) {
+ const actRules = configsRes.value.activity_rules;
+ if (actRules.minParticipants) minParticipants.value = Number(actRules.minParticipants);
+ if (actRules.maxParticipants) maxParticipants.value = Number(actRules.maxParticipants);
+ if (Array.isArray(actRules.presetPerPersonFees) && actRules.presetPerPersonFees.length > 0) {
+ presetPerPersonFees.value = actRules.presetPerPersonFees;
+ }
+ if (Array.isArray(actRules.presetTotalFees) && actRules.presetTotalFees.length > 0) {
+ presetTotalFees.value = actRules.presetTotalFees;
}
}
} catch (e) {
@@ -386,7 +407,10 @@ const applyRecommendZone = (zone) => {
};
const changeParticipants = (delta) => {
- form.value.maxParticipants = Math.max(2, Math.min(10, form.value.maxParticipants + delta));
+ form.value.maxParticipants = Math.max(
+ minParticipants.value,
+ Math.min(maxParticipants.value, form.value.maxParticipants + delta)
+ );
};
const handleSubmit = async () => {
diff --git a/pwuniapp/pages/user/index.vue b/pwuniapp/pages/user/index.vue
index 1cf5dd8..3c8ba51 100644
--- a/pwuniapp/pages/user/index.vue
+++ b/pwuniapp/pages/user/index.vue
@@ -206,6 +206,74 @@
+
+
+
+
+
+
+
+ 提交真实有效资料,平台将在 24 小时内由运营团队人工核验后开通接单工作台。
+
+
+
+ 真实姓名
+
+
+
+
+ 身份证号码
+
+
+
+
+ 主营陪伴品类
+
+
+ {{ trk }}
+
+
+
+
+
+ 期望服务单价 (元/小时,平台限价: ¥{{ expertPriceMin }} - ¥{{ expertPriceMax }})
+
+
+
+
+ 技能专长标签 (最多选6项)
+
+
+ {{ tg }}
+
+
+
+
+
+
+
+
+
@@ -546,13 +614,22 @@ const handleRecharge = async () => {
const handleWithdraw = () => {
const comm = Number(user.value.commission || 0);
+ const finRules = platformConfigs.value.finance_rules || {};
+ const minWithdraw = Number(finRules.minWithdrawAmount) || 50;
+ const withdrawDesc = finRules.withdrawDesc || '提现将自动发起微信企业付款打款到微信零钱。';
+
if (comm <= 0) {
uni.showToast({ title: '暂无可用提现佣金', icon: 'none' });
return;
}
+ if (comm < minWithdraw) {
+ uni.showToast({ title: `提现金额须满 ¥${minWithdraw} 元起提`, icon: 'none' });
+ return;
+ }
+
uni.showModal({
title: '佣金提现申请',
- content: `当前可提现佣金为 ¥${user.value.commission},提现将自动发起微信企业付款打款到微信零钱。`,
+ content: `当前可提现佣金为 ¥${user.value.commission} (最低满¥${minWithdraw}起提)。\n${withdrawDesc}`,
confirmText: '确认提现',
confirmColor: '#10B981',
success: async (res) => {
@@ -579,14 +656,99 @@ const handleWithdraw = () => {
};
const showInvitePoster = () => {
+ const finRules = platformConfigs.value.finance_rules || {};
+ const firstRate = finRules.firstReferralPercent != null ? finRules.firstReferralPercent : 5.0;
+ const secondRate = finRules.secondReferralPercent != null ? finRules.secondReferralPercent : 2.0;
+ const totalRate = (Number(firstRate) + Number(secondRate)).toFixed(1);
+
uni.showModal({
title: '二级分销推广',
- content: `您的专属邀请码为:PW${user.value.id}\n已成功邀请 ${user.value.invitedCount} 位好友!\n好友下单您将实时享受最高 15% 自动分润提成。`,
+ content: `您的专属邀请码为:PW${user.value.id}\n已成功邀请 ${user.value.invitedCount} 位好友!\n好友下单享受直推 ${firstRate}% + 间推 ${secondRate}%,全网自动分润最高达 ${totalRate}%!`,
showCancel: false,
- confirmText: '保存太阳码'
+ confirmText: '保存推广码'
});
};
+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 = () => {
const certStatus = getExpertCertStatus();
@@ -606,26 +768,15 @@ const applyExpert = () => {
});
return;
}
- uni.showModal({
- title: '申请成为陪玩大神',
- content: '需开通实名认证以承接线上游戏与同城生活陪伴订单。是否确认提交当前用户实名认证?',
- confirmText: '提交入驻',
- cancelText: '取消',
- confirmColor: '#10B981',
- success: (res) => {
- if (res.confirm) {
- setExpertCertStatus(CERT_STATUS.APPROVED);
- uni.showToast({
- title: '实名认证已通过!接单权限已开通',
- icon: 'success',
- duration: 1500
- });
- uni.navigateTo({
- url: '/pages/expert/workspace'
- });
- }
- }
- });
+
+ expertCertForm.value = {
+ realName: '',
+ idCard: '',
+ track: '同城陪吃',
+ hourlyRate: expertDefaultRate.value,
+ tags: []
+ };
+ showExpertCertModal.value = true;
};
const contactService = () => {
@@ -1475,6 +1626,73 @@ 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);
+ }
+ }
+}
+
@keyframes glowSpin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
diff --git a/server/src/db/mysql.ts b/server/src/db/mysql.ts
index f9223ce..d6daafb 100644
--- a/server/src/db/mysql.ts
+++ b/server/src/db/mysql.ts
@@ -721,6 +721,25 @@ async function seedInitialDataIfEmpty(conn: any): Promise {
]
});
+ const financeRulesVal = JSON.stringify({
+ platformRatePercent: 10.0,
+ firstReferralPercent: 5.0,
+ secondReferralPercent: 2.0,
+ minWithdrawAmount: 50,
+ maxWithdrawAmount: 20000,
+ withdrawFeePercent: 0.0,
+ withdrawDesc: '提现将自动发起微信企业付款打款至微信零钱,工作日预计2小时内到账。'
+ });
+
+ const activityRulesVal = JSON.stringify({
+ minParticipants: 2,
+ maxParticipants: 10,
+ defaultParticipants: 4,
+ presetPerPersonFees: [200, 300, 500, 800, 1000],
+ presetTotalFees: [500, 1000, 2000, 3000, 5000],
+ defaultAaFee: 50
+ });
+
const [cfgItemRows]: any = await conn.query('SELECT COUNT(*) as count FROM platform_configs');
if (cfgItemRows[0].count === 0) {
const customerServiceVal = JSON.stringify({
@@ -758,8 +777,15 @@ async function seedInitialDataIfEmpty(conn: any): Promise {
('faq_list', '帮助中心与常见问题FAQ', ?, '小程序端帮助中心常见问答与指引', 1),
('security_words', '私下引流与违规自定义敏感词库', ?, '全平台发帖、评论及订单防私下交易关键词实时过滤', 1),
('home_popup', '小程序首页运营全局弹窗', ?, '节日活动推广、新用户专享首单优惠全局浮层弹窗', 1),
- ('expert_rules', '大神技能标签库与定价区间规范', ?, '陪玩大神入驻审核、定价上下限限制与官方推荐技能标签库', 1);
- `, [customerServiceVal, safetyNoticeVal, protocolVal, communityTopicsVal, communityNoticeVal, faqListVal, securityWordsVal, homePopupVal, expertRulesVal]);
+ ('expert_rules', '大神技能标签库与定价区间规范', ?, '陪玩大神入驻审核、定价上下限限制与官方推荐技能标签库', 1),
+ ('finance_rules', '平台抽成与推广分销规则', ?, '平台佣金抽成比例、二级分销返润与佣金提现门槛限制', 1),
+ ('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1);
+ `, [
+ customerServiceVal, safetyNoticeVal, protocolVal,
+ communityTopicsVal, communityNoticeVal, faqListVal,
+ securityWordsVal, homePopupVal, expertRulesVal,
+ financeRulesVal, activityRulesVal
+ ]);
} else {
// 增量保证各核心配置项完整就绪
const [existingRows]: any = await conn.query('SELECT config_key FROM platform_configs');
@@ -800,5 +826,17 @@ async function seedInitialDataIfEmpty(conn: any): Promise {
VALUES ('expert_rules', '大神技能标签库与定价区间规范', ?, '陪玩大神入驻审核、定价上下限限制与官方推荐技能标签库', 1)
`, [expertRulesVal]);
}
+ if (!existingKeys.has('finance_rules')) {
+ await conn.query(`
+ INSERT INTO platform_configs (config_key, name, value_json, description, status)
+ VALUES ('finance_rules', '平台抽成与推广分销规则', ?, '平台佣金抽成比例、二级分销返润与佣金提现门槛限制', 1)
+ `, [financeRulesVal]);
+ }
+ if (!existingKeys.has('activity_rules')) {
+ await conn.query(`
+ INSERT INTO platform_configs (config_key, name, value_json, description, status)
+ VALUES ('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1)
+ `, [activityRulesVal]);
+ }
}
}
diff --git a/server/src/db/repository.ts b/server/src/db/repository.ts
index fc70708..eee7103 100644
--- a/server/src/db/repository.ts
+++ b/server/src/db/repository.ts
@@ -549,6 +549,44 @@ export class MysqlRepository {
return this.getCompanionById(id);
}
+ async applyCompanion(data: {
+ userId: number;
+ name: string;
+ idCard: string;
+ track: string;
+ hourlyRate?: number;
+ tags?: string[];
+ avatar?: string;
+ gender?: string;
+ }): Promise {
+ const name = data.name.trim();
+ const track = data.track || '同城陪吃';
+ const hourlyRate = Number(data.hourlyRate) || 88;
+ const tagsJson = JSON.stringify(data.tags || []);
+ const avatar = data.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300';
+ const gender = data.gender || '女';
+ const maskedIdCard = data.idCard.length >= 8
+ ? data.idCard.substring(0, 6) + '********' + data.idCard.substring(data.idCard.length - 4)
+ : '110101********0000';
+
+ const [existing]: any = await pool.query('SELECT * FROM companions WHERE name = ?', [name]);
+ if (existing && existing.length > 0) {
+ await pool.query(`
+ UPDATE companions
+ SET track = ?, hourly_rate = ?, tags = ?, avatar = ?, gender = ?, audit_status = '待审核', status = '待审核', id_card_masked = ?
+ WHERE id = ?
+ `, [track, hourlyRate, tagsJson, avatar, gender, maskedIdCard, existing[0].id]);
+ return this.getCompanionById(existing[0].id);
+ }
+
+ const [res]: any = await pool.query(`
+ INSERT INTO companions (name, avatar, gender, age, track, rank_name, hourly_rate, voice_duration, voice_title, rating, order_count, status, audit_status, id_card_masked, tags, apply_time)
+ VALUES (?, ?, ?, 23, ?, '申请中陪伴师', ?, 15, '热情语音介绍', 5.0, 0, '待审核', '待审核', ?, ?, NOW())
+ `, [name, avatar, gender, track, hourlyRate, maskedIdCard, tagsJson]);
+
+ return this.getCompanionById(res.insertId);
+ }
+
async auditCompanion(id: number, action: 'approve' | 'reject', reason?: string): Promise {
const auditStatus = action === 'approve' ? '已通过' : '已驳回';
const status = action === 'approve' ? '接单中' : '已驳回';
@@ -1824,6 +1862,20 @@ export class MysqlRepository {
if (isNaN(amount) || amount <= 0) {
throw new Error('提现金额必须大于0');
}
+
+ // 动态读取财务提现配置限制门槛
+ let minWithdraw = 50;
+ try {
+ const fConfig = await this.getPlatformConfigByKey('finance_rules');
+ if (fConfig?.valueJson?.minWithdrawAmount) {
+ minWithdraw = Number(fConfig.valueJson.minWithdrawAmount);
+ }
+ } catch (_) {}
+
+ if (amount < minWithdraw) {
+ throw new Error(`提现金额不能低于平台最低门槛 ¥${minWithdraw}`);
+ }
+
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
diff --git a/server/src/index.ts b/server/src/index.ts
index ec28a98..a3c036e 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -550,6 +550,57 @@ app.get('/api/companions/:id', async (req: Request, res: Response) => {
}
});
+// 大神资质与实名入驻真实申请接口
+app.post('/api/companions/apply', async (req: Request, res: Response) => {
+ try {
+ const { userId, name, idCard, track, hourlyRate, tags, avatar, gender } = req.body;
+ if (!name || !name.trim()) {
+ return res.status(400).json({ code: 40000, message: '真实姓名不能为空' });
+ }
+ if (!idCard || !idCard.trim()) {
+ return res.status(400).json({ code: 40000, message: '身份证号不能为空' });
+ }
+
+ // 微信内容安全检测
+ const sec = contentSecurityService.checkText(`${name} ${track || ''} ${(tags || []).join(' ')}`);
+ if (!sec.pass) {
+ return res.status(400).json({ code: sec.code, message: sec.message });
+ }
+
+ // 校验价格与标签
+ const expertRulesConfig = await repo.getPlatformConfigByKey('expert_rules');
+ const rules = expertRulesConfig?.valueJson || { minHourlyRate: 30, maxHourlyRate: 500, maxTagsCount: 6 };
+ if (hourlyRate) {
+ const rateNum = Number(hourlyRate);
+ if (rateNum < (rules.minHourlyRate || 30) || rateNum > (rules.maxHourlyRate || 500)) {
+ return res.status(400).json({
+ code: 40000,
+ message: `服务单价需在 ¥${rules.minHourlyRate || 30} - ¥${rules.maxHourlyRate || 500} 之间`
+ });
+ }
+ }
+
+ const companion = await repo.applyCompanion({
+ userId: Number(userId) || 1001,
+ name: name.trim(),
+ idCard: idCard.trim(),
+ track: track || '同城陪吃',
+ hourlyRate: Number(hourlyRate) || rules.defaultHourlyRate || 88,
+ tags: Array.isArray(tags) ? tags.slice(0, rules.maxTagsCount || 6) : [],
+ avatar,
+ gender
+ });
+
+ res.json({
+ code: 20000,
+ data: companion,
+ message: '大神实名认证申请已提交,等待运营人工审核'
+ });
+ } catch (err: any) {
+ res.status(400).json({ code: 40000, message: err.message });
+ }
+});
+
app.post('/api/companions/:id/audit', async (req: Request, res: Response) => {
try {
const companionId = Number(req.params.id);
diff --git a/server/tests/content-security.spec.ts b/server/tests/content-security.spec.ts
index 8370139..f446c7a 100644
--- a/server/tests/content-security.spec.ts
+++ b/server/tests/content-security.spec.ts
@@ -221,5 +221,47 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
expect(emojiRegex.test(content)).toBe(false);
});
});
+
+ describe('7. 财务分销规则、组局预算规范与大神入驻申请闭环验证 (finance_rules & activity_rules)', () => {
+ it('MySQL 数据库与种子数据必须注册 finance_rules 与 activity_rules 配置项', () => {
+ const mysqlPath = path.join(rootDir, 'server', 'src', 'db', 'mysql.ts');
+ const content = fs.readFileSync(mysqlPath, 'utf-8');
+ expect(content).toContain('finance_rules');
+ expect(content).toContain('platformRatePercent');
+ expect(content).toContain('firstReferralPercent');
+ expect(content).toContain('activity_rules');
+ expect(content).toContain('minParticipants');
+ expect(content).toContain('presetPerPersonFees');
+ });
+
+ it('管理后台 platform-config 必须包含 Tab 9 财务规则与 Tab 10 组局预算面板', () => {
+ const adminConfigPath = path.join(rootDir, 'admin', 'arco-design-pro-vite', 'src', 'views', 'safety', 'platform-config', 'index.vue');
+ const content = fs.readFileSync(adminConfigPath, 'utf-8');
+ expect(content).toContain('finance_rules_config');
+ expect(content).toContain('财务抽成与分销规则');
+ expect(content).toContain('activity_rules_config');
+ expect(content).toContain('组局发单与预算规范');
+ expect(content).toContain('financeRulesForm');
+ expect(content).toContain('activityRulesForm');
+ });
+
+ it('服务端路由必须提供 POST /api/companions/apply 真实申请接口且校验姓名/身份证/安全', () => {
+ const serverIndexPath = path.join(rootDir, 'server', 'src', 'index.ts');
+ const content = fs.readFileSync(serverIndexPath, 'utf-8');
+ expect(content).toContain("app.post('/api/companions/apply'");
+ expect(content).toContain('真实姓名不能为空');
+ expect(content).toContain('身份证号不能为空');
+ });
+
+ it('小程序个人中心与发起组局页面必须动态绑定配置并实现门槛保护', () => {
+ const userIndexContent = fs.readFileSync(path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue'), 'utf-8');
+ const createActContent = fs.readFileSync(path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'create.vue'), 'utf-8');
+ expect(userIndexContent).toContain('applyExpertCert');
+ expect(userIndexContent).toContain('showExpertCertModal');
+ expect(userIndexContent).toContain('finance_rules');
+ expect(createActContent).toContain('presetPerPersonFees');
+ expect(createActContent).toContain('activity_rules');
+ });
+ });
});