feat(safety): add expert skill tags and price range constraints configuration
这个提交包含在:
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户