<script setup>
|
import { onMounted, reactive, ref } from 'vue'
|
import { useRouter } from 'vue-router'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ArrowLeft, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import {
|
getGuideAutoDisableConfig,
|
getGuideUsers,
|
updateGuideAutoDisableConfig,
|
updateGuideWhitelist,
|
} from '@/api/users'
|
import { normalizeAutoDisableSettings } from '@/utils/guideAutoDisable'
|
|
const router = useRouter()
|
const settings = reactive({ orderThreshold: '', ratingThreshold: '' })
|
const whitelist = ref([])
|
const whitelistLoading = ref(false)
|
const whitelistTotal = ref(0)
|
const dateRange = ref([])
|
const query = reactive({ keyword: '', teamType: null, rating: null })
|
const dialogVisible = ref(false)
|
const candidateLoading = ref(false)
|
const candidates = ref([])
|
const candidateKeyword = ref('')
|
|
const loadSettings = async () => Object.assign(settings, await getGuideAutoDisableConfig())
|
|
const loadWhitelist = async () => {
|
whitelistLoading.value = true
|
try {
|
const [startDate, endDate] = dateRange.value || []
|
const result = await getGuideUsers({
|
page: 1,
|
size: 100,
|
keyword: query.keyword.trim() || undefined,
|
startDate,
|
endDate,
|
teamType: query.teamType || undefined,
|
ratingLevel: query.rating || undefined,
|
whitelisted: true,
|
})
|
whitelist.value = result.records
|
whitelistTotal.value = result.total
|
} finally {
|
whitelistLoading.value = false
|
}
|
}
|
|
const formatDateTime = (value) => value ? value.replace('T', ' ').slice(0, 19) : '-'
|
|
const saveSettings = async () => {
|
const { orderThreshold, ratingThreshold } = normalizeAutoDisableSettings(settings)
|
if (!Number.isInteger(orderThreshold) || orderThreshold <= 0) {
|
ElMessage.warning('订单数阈值请输入大于0的正整数')
|
return
|
}
|
if (ratingThreshold < 1 || ratingThreshold > 5) {
|
ElMessage.warning('评分阈值请输入1到5之间的数字')
|
return
|
}
|
Object.assign(settings, await updateGuideAutoDisableConfig({ orderThreshold, ratingThreshold }))
|
ElMessage.success('自动禁用设置已保存')
|
}
|
|
const resetQuery = () => {
|
Object.assign(query, { keyword: '', teamType: null, rating: null })
|
dateRange.value = []
|
loadWhitelist()
|
}
|
|
const removeGuide = async (guide) => {
|
try {
|
await ElMessageBox.confirm(`确认将“${guide.realName || guide.username}”移出白名单吗?`, '移除白名单', { type: 'warning' })
|
await updateGuideWhitelist(guide.id, false)
|
await loadWhitelist()
|
ElMessage.success('已移出白名单')
|
} catch (error) {
|
if (error !== 'cancel' && error !== 'close') throw error
|
}
|
}
|
|
const loadCandidates = async () => {
|
candidateLoading.value = true
|
try {
|
const result = await getGuideUsers({
|
page: 1,
|
size: 100,
|
keyword: candidateKeyword.value.trim() || undefined,
|
whitelisted: false,
|
})
|
candidates.value = result.records
|
} finally {
|
candidateLoading.value = false
|
}
|
}
|
|
const resetCandidateSearch = () => {
|
candidateKeyword.value = ''
|
loadCandidates()
|
}
|
|
const openAddDialog = () => {
|
dialogVisible.value = true
|
candidateKeyword.value = ''
|
loadCandidates()
|
}
|
|
const addGuide = async (guide) => {
|
await updateGuideWhitelist(guide.id, true)
|
candidates.value = candidates.value.filter((item) => item.id !== guide.id)
|
await loadWhitelist()
|
ElMessage.success('已加入白名单')
|
}
|
|
onMounted(() => Promise.all([loadSettings(), loadWhitelist()]))
|
</script>
|
|
<template>
|
<section class="auto-disable-page">
|
<el-button class="back-button" :icon="ArrowLeft" @click="router.push('/guides')">返回导游列表</el-button>
|
|
<el-card class="settings-card" shadow="never">
|
<h2>自动禁用设置</h2>
|
<p class="rule-description">非白名单导游同时满足以下禁用条件时,系统将自动禁用;尚无有效评分的导游不参与判断。</p>
|
<div class="condition-row">
|
<strong>条件1</strong><span>团单数大于等于</span>
|
<el-input v-model="settings.orderThreshold" placeholder="请输入" />
|
<small>可输入大于0的正整数</small>
|
</div>
|
<div class="condition-row">
|
<strong class="condition-label-with-operator"><span>且</span>条件2</strong><span>平均评分小于或等于</span>
|
<el-input v-model="settings.ratingThreshold" placeholder="请输入" />
|
<small>可输入数字1-5</small>
|
</div>
|
<el-button class="save-button" type="primary" @click="saveSettings">保存</el-button>
|
</el-card>
|
|
<el-card class="whitelist-card" shadow="never">
|
<h2>自动禁用白名单</h2>
|
<el-form class="whitelist-query" label-position="left" label-width="72px">
|
<el-form-item label="导游搜索">
|
<el-input v-model="query.keyword" clearable placeholder="请输入导游手机号/姓名/编号" />
|
</el-form-item>
|
<el-form-item label="创建时间">
|
<el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" />
|
</el-form-item>
|
<el-form-item label="讲解队/个人">
|
<el-select v-model="query.teamType" clearable placeholder="全部">
|
<el-option label="个人" value="personal" /><el-option label="讲解队" value="team" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="评分">
|
<el-select v-model="query.rating" clearable placeholder="全部">
|
<el-option label="4分及以上" value="high" /><el-option label="3-4分" value="medium" /><el-option label="3分以下" value="low" />
|
</el-select>
|
</el-form-item>
|
<div class="query-actions">
|
<el-button type="primary" :icon="Search" @click="loadWhitelist">查询</el-button>
|
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
|
</div>
|
</el-form>
|
<div class="table-toolbar"><el-button type="primary" :icon="Plus" @click="openAddDialog">新增</el-button></div>
|
<el-table v-loading="whitelistLoading" :data="whitelist" border row-key="id">
|
<el-table-column prop="id" label="导游编号" width="105" align="center" />
|
<el-table-column prop="realName" label="导游姓名" min-width="110" align="center" />
|
<el-table-column prop="mobile" label="手机号" min-width="135" align="center" />
|
<el-table-column label="注册时间" min-width="165" align="center">
|
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
</el-table-column>
|
<el-table-column label="所属讲解队" width="125" align="center">
|
<template #default="{ row }">{{ row.teamName || row.teamType }}</template>
|
</el-table-column>
|
<el-table-column prop="acceptedOrderCount" label="接单总数" width="105" align="center" />
|
<el-table-column prop="currentMonthOrderCount" label="本月接单" width="105" align="center" />
|
<el-table-column label="评分" width="90" align="center">
|
<template #default="{ row }">{{ Number(row.rating || 0).toFixed(1) }}</template>
|
</el-table-column>
|
<el-table-column label="操作" width="130" fixed="right" align="center">
|
<template #default="{ row }"><el-button link type="danger" @click="removeGuide(row)">移除白名单</el-button></template>
|
</el-table-column>
|
<template #empty><el-empty description="白名单暂无导游" /></template>
|
</el-table>
|
<div class="total-row">共 {{ whitelistTotal }} 条</div>
|
</el-card>
|
|
<el-dialog v-model="dialogVisible" title="新增白名单导游" width="720px">
|
<div class="candidate-search">
|
<el-input v-model="candidateKeyword" clearable placeholder="请输入导游手机号/姓名/编号" @keyup.enter="loadCandidates" />
|
<el-button type="primary" :icon="Search" @click="loadCandidates">查询</el-button>
|
<el-button :icon="Refresh" @click="resetCandidateSearch">重置</el-button>
|
</div>
|
<el-table v-loading="candidateLoading" :data="candidates" border max-height="420">
|
<el-table-column prop="id" label="编号" width="90" />
|
<el-table-column prop="realName" label="姓名" />
|
<el-table-column prop="mobile" label="手机号" />
|
<el-table-column label="状态" width="90">
|
<template #default="{ row }">{{ row.status === 1 ? '启用' : '禁用' }}</template>
|
</el-table-column>
|
<el-table-column label="操作" width="90">
|
<template #default="{ row }"><el-button link type="primary" @click="addGuide(row)">添加</el-button></template>
|
</el-table-column>
|
</el-table>
|
</el-dialog>
|
</section>
|
</template>
|
|
<style scoped>
|
.auto-disable-page { display: flex; flex-direction: column; gap: 14px; }
|
.back-button { align-self: flex-start; }
|
.settings-card, .whitelist-card { border: 0; border-radius: 4px; }
|
.settings-card h2, .whitelist-card h2 { margin: 0 0 20px; font-size: 18px; }
|
.rule-description { margin: -8px 0 18px; color: #64748b; font-size: 13px; }
|
.condition-row { display: grid; grid-template-columns: 56px 150px 165px 1fr; align-items: center; gap: 12px; margin: 10px 0; }
|
.condition-row strong { font-size: 13px; line-height: 1.8; }
|
.condition-label-with-operator { position: relative; }
|
.condition-label-with-operator > span { position: absolute; bottom: 100%; left: 0; }
|
.condition-row span { color: #334155; font-size: 13px; }
|
.condition-row small { color: #7c8796; }
|
.save-button { margin: 18px 0 0 12px; width: 74px; }
|
.whitelist-query { display: grid; grid-template-columns: 1fr 1.1fr 1fr auto; column-gap: 28px; }
|
.whitelist-query :deep(.el-form-item) { margin-right: 0; }
|
.whitelist-query :deep(.el-select), .whitelist-query :deep(.el-date-editor) { width: 100%; }
|
.query-actions { display: flex; flex-direction: column; gap: 10px; grid-column: 4; grid-row: 1 / span 2; }
|
.table-toolbar { display: flex; justify-content: flex-end; padding: 12px 0 10px; border-top: 1px solid #e2e8f0; }
|
.total-row { margin-top: 22px; color: #64748b; text-align: right; font-size: 13px; }
|
.candidate-search { display: flex; gap: 12px; margin-bottom: 16px; }
|
</style>
|