feat(safety): add expert skill tags and price range constraints configuration

这个提交包含在:
xcbyy
2026-10-10 13:56:38 +08:00
父节点 2e726e125d
当前提交 d7f5c31762
共有 5 个文件被更改,包括 351 次插入 和 5 次删除

查看文件

@@ -378,6 +378,97 @@
</a-card>
</div>
</a-tab-pane>
<!-- Tab 8: 大神资质与定价规则 (P4) -->
<a-tab-pane key="expert_rules_config" title="大神资质与定价规则">
<div class="tab-pane-content">
<a-alert type="success" style="margin-bottom: 20px">
在此设定陪伴师/陪玩大神的入驻审核规范、服务单价保护区间(防止恶意低价或超常标价),以及全平台官方认证候选技能标签库。
</a-alert>
<a-card title="大神资质与价格控制规则" :bordered="false" style="background: var(--color-fill-2)">
<a-form :model="expertRulesForm.valueJson" layout="vertical">
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="最低服务单价 (元/小时)">
<a-input-number v-model="expertRulesForm.valueJson.minHourlyRate" :min="1" :max="1000" placeholder="如 30" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="最高服务单价 (元/小时)">
<a-input-number v-model="expertRulesForm.valueJson.maxHourlyRate" :min="10" :max="5000" placeholder="如 500" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="推荐默认初始单价 (元/小时)">
<a-input-number v-model="expertRulesForm.valueJson.defaultHourlyRate" :min="10" :max="1000" placeholder="如 88" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="每位大神最多可选标签数">
<a-input-number v-model="expertRulesForm.valueJson.maxTagsCount" :min="1" :max="10" placeholder="如 6" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="实名认证强校验门禁">
<a-switch v-model="expertRulesForm.valueJson.requireRealName" checked-text="必须实名" unchecked-text="免实名" />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="允许大神提交个性标签">
<a-switch v-model="expertRulesForm.valueJson.allowCustomTags" checked-text="允许" unchecked-text="仅限候选库" />
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div style="margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; font-size: 14px;">官方认证技能候选标签库 ({{ expertCandidateTags.length }})</span>
<a-button type="outline" size="small" @click="addCandidateTag">
<template #icon><icon-plus /></template>
新增候选标签
</a-button>
</div>
<div style="margin-bottom: 16px;">
<a-space wrap size="medium">
<a-tag
v-for="(tag, index) in expertCandidateTags"
:key="index"
color="arcoblue"
closable
size="large"
@close="removeCandidateTag(index)"
>
{{ tag }}
</a-tag>
</a-space>
</div>
<a-form-item label="快捷批量录入技能标签(多个标签用换行或逗号隔开)">
<a-textarea
v-model="batchTagsInput"
placeholder="如:\n国服王者\n声优达人\n幽默风趣\n暖心倾听"
:auto-size="{ minRows: 3, maxRows: 6 }"
/>
</a-form-item>
<a-button type="secondary" size="small" @click="handleBatchAddTags">
解析并导入标签库
</a-button>
<div style="margin-top: 24px">
<a-button type="primary" :loading="saving" @click="saveExpertRulesConfig">
保存大神资质与定价规则
</a-button>
</div>
</a-form>
</a-card>
</div>
</a-tab-pane>
</a-tabs>
</a-card>
</div>
@@ -551,6 +642,73 @@ const handlePopupUploadError = () => {
Message.error('图片上传失败,请检查文件大小及网络');
};
// P4 大神资质与定价规则
const expertRulesForm = reactive({
name: '大神技能标签库与定价区间规范',
description: '陪玩大神入驻审核、定价上下限限制与官方推荐技能标签库',
status: 1,
valueJson: {
minHourlyRate: 30,
maxHourlyRate: 500,
defaultHourlyRate: 88,
requireRealName: true,
allowCustomTags: true,
maxTagsCount: 6,
candidateTags: [
'国服王者', '声优达人', '幽默风趣', '随叫随到', '暖心倾听',
'百星野王', '金牌探店', '量贩麦霸', '微醺搭子', '绅士礼貌',
'活力开朗', '耐心教学', '高胜率车头', '拍照好看'
]
}
});
const expertCandidateTags = ref<string[]>([
'国服王者', '声优达人', '幽默风趣', '随叫随到', '暖心倾听',
'百星野王', '金牌探店', '量贩麦霸', '微醺搭子', '绅士礼貌',
'活力开朗', '耐心教学', '高胜率车头', '拍照好看'
]);
const batchTagsInput = ref('');
const addCandidateTag = () => {
const newTag = window.prompt('请输入新的技能候选标签(如:国服第一中单)');
if (newTag && newTag.trim()) {
const t = newTag.trim();
if (!expertCandidateTags.value.includes(t)) {
expertCandidateTags.value.push(t);
}
}
};
const removeCandidateTag = (index: number) => {
expertCandidateTags.value.splice(index, 1);
};
const handleBatchAddTags = () => {
if (!batchTagsInput.value.trim()) {
Message.warning('请输入需要批量导入的技能标签文本');
return;
}
const splitTags = batchTagsInput.value
.split(/[\n,,\s]+/)
.map(t => t.trim())
.filter(Boolean);
let addedCount = 0;
splitTags.forEach(t => {
if (!expertCandidateTags.value.includes(t)) {
expertCandidateTags.value.push(t);
addedCount++;
}
});
batchTagsInput.value = '';
Message.success(`成功解析并新增了 ${addedCount} 个技能标签`);
};
const saveExpertRulesConfig = () => {
expertRulesForm.valueJson.candidateTags = expertCandidateTags.value;
saveConfig('expert_rules', expertRulesForm);
};
const fetchConfigs = async () => {
loading.value = true;
try {
@@ -595,6 +753,14 @@ const fetchConfigs = async () => {
homePopupForm.description = item.description;
homePopupForm.status = item.status;
homePopupForm.valueJson = { ...homePopupForm.valueJson, ...(item.valueJson || {}) };
} else if (item.configKey === 'expert_rules') {
expertRulesForm.name = item.name;
expertRulesForm.description = item.description;
expertRulesForm.status = item.status;
expertRulesForm.valueJson = { ...expertRulesForm.valueJson, ...(item.valueJson || {}) };
if (Array.isArray(expertRulesForm.valueJson.candidateTags)) {
expertCandidateTags.value = expertRulesForm.valueJson.candidateTags;
}
}
});
}