<script setup>
|
import { computed, onMounted, reactive, ref } from 'vue'
|
import { useRoute, useRouter } from 'vue-router'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ArrowLeft, Edit, Plus } from '@element-plus/icons-vue'
|
import {
|
createGuideTeam,
|
getGuideTeamCollectionGuideOptions,
|
getGuideTeamDetail,
|
updateGuideTeam,
|
} from '@/api/guideTeams'
|
import { getScenicSpotDetail, getScenicSpots } from '@/api/scenicSpots'
|
|
const route = useRoute()
|
const router = useRouter()
|
const loading = ref(false)
|
const saving = ref(false)
|
const collectionGuideLoading = ref(false)
|
const collectionGuideOptions = ref([])
|
const formRef = ref()
|
const scenicFormRef = ref()
|
const scenicDialogVisible = ref(false)
|
const scenicEditingIndex = ref(-1)
|
const scenicOptionsLoading = ref(false)
|
const scenicDetailLoading = ref(false)
|
const scenicOptions = ref([])
|
const meetingPointOptions = ref([])
|
const isCreate = computed(() => route.name === 'guide-team-create')
|
const isEdit = computed(() => route.name === 'guide-team-edit')
|
const readonly = computed(() => route.name === 'guide-team-detail')
|
const pageTitle = computed(() => isCreate.value ? '新增讲解队' : isEdit.value ? '编辑讲解队' : '讲解队详情')
|
const form = reactive({
|
id: null, teamName: '', contactPhone: '', createdAt: '', companyName: '',
|
unifiedSocialCreditCode: '', commissionRatio: null, wechatOpenId: '', merchantId: '',
|
balance: 0, collectionGuideId: null, scenicConfigs: [],
|
})
|
const scenicForm = reactive({ scenicSpotId: null, scenicName: '', cityName: '', meetingPointNames: [] })
|
const rules = {
|
teamName: [
|
{ required: true, message: '请输入讲解队名称', trigger: 'blur' },
|
{ max: 100, message: '讲解队名称不能超过100个字符', trigger: 'blur' },
|
],
|
contactPhone: [
|
{ required: true, message: '请输入联系人电话', trigger: 'blur' },
|
{ pattern: /^[0-9+\-() ]{6,20}$/, message: '联系人电话格式不正确', trigger: 'blur' },
|
],
|
commissionRatio: [
|
{ required: true, message: '请输入平台抽成比例', trigger: 'change' },
|
],
|
}
|
const scenicRules = {
|
scenicSpotId: [{ required: true, message: '请选择景区', trigger: 'change' }],
|
meetingPointNames: [{ required: true, type: 'array', min: 1, message: '请选择汇合点', trigger: 'change' }],
|
}
|
|
const formatDateTime = (value) => value ? value.replace('T', ' ').slice(0, 19) : '-'
|
const formatMoney = (value) => `¥${Number(value || 0).toFixed(2)}`
|
const assignDetail = (detail) => Object.assign(form, {
|
id: detail.id,
|
teamName: detail.teamName || '',
|
contactPhone: detail.contactPhone || '',
|
createdAt: detail.createdAt || '',
|
companyName: detail.companyName || '',
|
unifiedSocialCreditCode: detail.unifiedSocialCreditCode || '',
|
commissionRatio: detail.commissionRatio ?? 0,
|
wechatOpenId: detail.wechatOpenId || '',
|
merchantId: detail.merchantId || '',
|
balance: detail.balance ?? 0,
|
collectionGuideId: detail.collectionGuideId ?? null,
|
scenicConfigs: (detail.scenicConfigs || []).map((item) => ({ ...item })),
|
})
|
const loadCollectionGuideOptions = async () => {
|
if (isCreate.value) return
|
collectionGuideLoading.value = true
|
try { collectionGuideOptions.value = await getGuideTeamCollectionGuideOptions(route.params.id) }
|
finally { collectionGuideLoading.value = false }
|
}
|
const loadDetail = async () => {
|
if (isCreate.value) { form.commissionRatio = 0; return }
|
loading.value = true
|
try {
|
assignDetail(await getGuideTeamDetail(route.params.id))
|
await loadCollectionGuideOptions()
|
}
|
finally { loading.value = false }
|
}
|
const loadScenicOptions = async () => {
|
scenicOptionsLoading.value = true
|
try {
|
const result = await getScenicSpots({ page: 1, size: 100, status: 1 })
|
scenicOptions.value = result.records || []
|
} finally { scenicOptionsLoading.value = false }
|
}
|
const selectScenic = async (scenicSpotId, preserveMeetingPoints = false) => {
|
const selected = scenicOptions.value.find((item) => item.id === scenicSpotId)
|
scenicForm.scenicName = selected?.scenicName || ''
|
scenicForm.cityName = selected?.cityName || ''
|
if (!preserveMeetingPoints) scenicForm.meetingPointNames = []
|
meetingPointOptions.value = []
|
if (!scenicSpotId) return
|
scenicDetailLoading.value = true
|
try {
|
const detail = await getScenicSpotDetail(scenicSpotId)
|
meetingPointOptions.value = (detail.meetingPoints || []).filter((point) => point.status === 1)
|
} finally { scenicDetailLoading.value = false }
|
}
|
const findScenicOption = (row) => scenicOptions.value.find((item) => (
|
item.scenicName === row.scenicName && item.cityName === row.cityName
|
))
|
const splitMeetingPoints = (value) => String(value || '').split(/[,,]/).map((item) => item.trim()).filter(Boolean)
|
const openScenicCreate = () => {
|
scenicEditingIndex.value = -1
|
Object.assign(scenicForm, { scenicSpotId: null, scenicName: '', cityName: '', meetingPointNames: [] })
|
meetingPointOptions.value = []
|
scenicDialogVisible.value = true
|
}
|
const openScenicEdit = async (row, index) => {
|
scenicEditingIndex.value = index
|
const selected = findScenicOption(row)
|
Object.assign(scenicForm, {
|
scenicSpotId: selected?.id || null,
|
scenicName: row.scenicName,
|
cityName: row.cityName,
|
meetingPointNames: splitMeetingPoints(row.meetingPoints),
|
})
|
meetingPointOptions.value = []
|
scenicDialogVisible.value = true
|
if (selected) await selectScenic(selected.id, true)
|
}
|
const saveScenic = async () => {
|
await scenicFormRef.value.validate()
|
const value = {
|
scenicName: scenicForm.scenicName,
|
cityName: scenicForm.cityName,
|
meetingPoints: scenicForm.meetingPointNames.join(','),
|
}
|
const duplicated = form.scenicConfigs.some((item, index) => (
|
index !== scenicEditingIndex.value && item.scenicName.trim() === value.scenicName
|
))
|
if (duplicated) {
|
ElMessage.warning('该景区已配置')
|
return
|
}
|
if (scenicEditingIndex.value >= 0) form.scenicConfigs.splice(scenicEditingIndex.value, 1, value)
|
else form.scenicConfigs.push(value)
|
scenicDialogVisible.value = false
|
}
|
const removeScenic = async (index) => {
|
try {
|
await ElMessageBox.confirm('确认删除该景区配置吗?', '删除景区', { type: 'warning' })
|
form.scenicConfigs.splice(index, 1)
|
} catch (error) {
|
if (error !== 'cancel' && error !== 'close') throw error
|
}
|
}
|
const save = async () => {
|
await formRef.value.validate()
|
if (!form.scenicConfigs.length) {
|
ElMessage.warning('请至少配置一个景区')
|
return
|
}
|
if (!form.wechatOpenId.trim() && !form.merchantId.trim()) {
|
ElMessage.warning('openID和商户ID至少配置一个')
|
return
|
}
|
saving.value = true
|
try {
|
const payload = {
|
teamName: form.teamName.trim(),
|
contactPhone: form.contactPhone.trim(),
|
companyName: form.companyName.trim() || null,
|
unifiedSocialCreditCode: form.unifiedSocialCreditCode.trim() || null,
|
commissionRatio: form.commissionRatio,
|
wechatOpenId: form.wechatOpenId.trim() || null,
|
merchantId: form.merchantId.trim() || null,
|
collectionGuideId: form.collectionGuideId,
|
scenicConfigs: form.scenicConfigs,
|
}
|
const result = isCreate.value ? await createGuideTeam(payload) : await updateGuideTeam(route.params.id, payload)
|
ElMessage.success(isCreate.value ? '新建成功' : '保存成功')
|
assignDetail(result)
|
router.replace(`/guide-teams/${result.id}`)
|
} finally { saving.value = false }
|
}
|
|
onMounted(() => Promise.all([loadDetail(), loadScenicOptions()]))
|
</script>
|
|
<template>
|
<section class="team-detail-page">
|
<div class="detail-toolbar">
|
<el-button :icon="ArrowLeft" @click="router.push('/guide-teams')">返回讲解队列表</el-button>
|
<el-button v-if="readonly" type="primary" :icon="Edit" @click="router.push(`/guide-teams/${route.params.id}/edit`)">编辑</el-button>
|
</div>
|
|
<el-card v-loading="loading" class="detail-card" shadow="never">
|
<div class="page-heading">{{ pageTitle }}</div>
|
<el-form ref="formRef" :model="form" :rules="rules" label-position="left">
|
<section class="detail-section">
|
<h3>基本信息</h3>
|
<div class="basic-grid">
|
<el-form-item label="讲解队" prop="teamName">
|
<el-input v-model="form.teamName" :disabled="readonly" maxlength="100" />
|
</el-form-item>
|
<el-form-item label="讲解队编号">
|
<el-input :model-value="form.id || '保存后生成'" disabled />
|
</el-form-item>
|
<el-form-item label="联系人电话" prop="contactPhone">
|
<el-input v-model="form.contactPhone" :disabled="readonly" maxlength="20" />
|
</el-form-item>
|
<el-form-item label="注册时间">
|
<el-input :model-value="formatDateTime(form.createdAt)" disabled />
|
</el-form-item>
|
<el-form-item label="讲解队余额">
|
<el-input :model-value="formatMoney(form.balance)" disabled />
|
</el-form-item>
|
<el-form-item label="企业名称">
|
<el-input v-model="form.companyName" :disabled="readonly" maxlength="200" />
|
</el-form-item>
|
<el-form-item label="统一社会信用代码">
|
<el-input v-model="form.unifiedSocialCreditCode" :disabled="readonly" maxlength="32" />
|
</el-form-item>
|
</div>
|
</section>
|
|
<section class="detail-section business-section">
|
<h3>业务配置</h3>
|
<div class="config-label">景区配置</div>
|
<el-table :data="form.scenicConfigs" border class="scenic-table">
|
<el-table-column prop="scenicName" label="景区名称" min-width="150" align="center" />
|
<el-table-column prop="cityName" label="城市" min-width="130" align="center" />
|
<el-table-column prop="meetingPoints" label="汇合点" min-width="280" align="center" show-overflow-tooltip />
|
<el-table-column v-if="!readonly" label="操作" width="140" align="center">
|
<template #default="{ row, $index }">
|
<el-button link type="danger" @click="removeScenic($index)">删除</el-button>
|
<el-button link type="primary" @click="openScenicEdit(row, $index)">编辑</el-button>
|
</template>
|
</el-table-column>
|
<template #empty><el-empty description="暂无景区配置" /></template>
|
</el-table>
|
<el-button v-if="!readonly" class="add-button" type="primary" :icon="Plus" @click="openScenicCreate">添加</el-button>
|
|
<el-form-item class="commission-row" label="平台抽成比例" prop="commissionRatio">
|
<el-input-number v-model="form.commissionRatio" :disabled="readonly" :min="0" :max="100" :precision="2" :controls="false" />
|
<span class="percent">%</span>
|
</el-form-item>
|
</section>
|
|
<section class="detail-section collection-section">
|
<div class="section-heading">
|
<h3>收款信息</h3>
|
<span>openID和商户ID至少配置一个</span>
|
</div>
|
<div class="collection-grid">
|
<el-form-item label="收款导游">
|
<el-select
|
v-model="form.collectionGuideId"
|
:disabled="readonly || isCreate"
|
:loading="collectionGuideLoading"
|
clearable
|
placeholder="请选择收款导游"
|
>
|
<el-option
|
v-for="guide in collectionGuideOptions"
|
:key="guide.id"
|
:label="`${guide.realName || '-'} ${guide.mobile || ''}${guide.unavailableReason ? `(${guide.unavailableReason})` : ''}`"
|
:value="guide.id"
|
:disabled="!guide.configurable && guide.id !== form.collectionGuideId"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="openID">
|
<el-input v-model="form.wechatOpenId" :disabled="readonly" maxlength="128" />
|
</el-form-item>
|
<el-form-item label="商户ID">
|
<el-input v-model="form.merchantId" :disabled="readonly" maxlength="64" />
|
</el-form-item>
|
</div>
|
</section>
|
|
<div v-if="!readonly" class="save-row">
|
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
|
</div>
|
</el-form>
|
</el-card>
|
|
<el-dialog v-model="scenicDialogVisible" :title="scenicEditingIndex >= 0 ? '编辑景区配置' : '添加景区配置'" width="560px" destroy-on-close>
|
<el-form ref="scenicFormRef" :model="scenicForm" :rules="scenicRules" label-width="86px">
|
<el-form-item label="景区名称" prop="scenicSpotId">
|
<el-select
|
v-model="scenicForm.scenicSpotId"
|
:loading="scenicOptionsLoading"
|
filterable
|
placeholder="请选择景区"
|
@change="selectScenic"
|
>
|
<el-option
|
v-for="scenic in scenicOptions"
|
:key="scenic.id"
|
:label="`${scenic.scenicName}(${scenic.cityName})`"
|
:value="scenic.id"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="城市">
|
<el-input :model-value="scenicForm.cityName" disabled placeholder="选择景区后自动带出" />
|
</el-form-item>
|
<el-form-item label="汇合点" prop="meetingPointNames">
|
<el-select
|
v-model="scenicForm.meetingPointNames"
|
:disabled="!scenicForm.scenicSpotId"
|
:loading="scenicDetailLoading"
|
multiple
|
collapse-tags
|
collapse-tags-tooltip
|
placeholder="请选择汇合点"
|
>
|
<el-option
|
v-for="point in meetingPointOptions"
|
:key="point.id"
|
:label="point.pointName"
|
:value="point.pointName"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<el-button @click="scenicDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="saveScenic">确定</el-button>
|
</template>
|
</el-dialog>
|
</section>
|
</template>
|
|
<style scoped>
|
.team-detail-page { min-height: 720px; }
|
.detail-toolbar { display: flex; justify-content: space-between; margin-bottom: 16px; }
|
.detail-card { border: 0; border-radius: 4px; }
|
.detail-card :deep(.el-card__body) { padding: 0; }
|
.page-heading { padding: 18px 34px; color: #29476b; font-size: 16px; font-weight: 700; border-bottom: 1px solid #edf0f3; }
|
.detail-section { padding: 24px 36px 30px; }
|
.detail-section + .detail-section { border-top: 10px solid #f4f7fb; }
|
.detail-section h3 { margin: 0 0 26px; color: #29476b; font-size: 14px; }
|
.basic-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 42px; }
|
.basic-grid :deep(.el-form-item) { display: grid; grid-template-columns: 112px minmax(0, 1fr); margin: 0; }
|
.basic-grid :deep(.el-form-item__label) { width: auto !important; color: #53657d; font-weight: 600; }
|
.basic-grid :deep(.el-input__wrapper.is-disabled) { background: #eef0f3; box-shadow: none; }
|
.business-section { min-height: 340px; }
|
.config-label { margin: 0 0 12px 12px; color: #53657d; font-size: 13px; font-weight: 600; }
|
.scenic-table { width: min(760px, calc(100% - 190px)); margin-left: 12px; }
|
.add-button { display: block; margin: 14px 190px 0 auto; }
|
.commission-row { width: 350px; margin: 44px 0 0 12px; }
|
.commission-row :deep(.el-form-item__label) { width: 112px; color: #53657d; font-weight: 600; }
|
.commission-row :deep(.el-input-number) { width: 160px; }
|
.percent { margin-left: 10px; color: #53657d; }
|
.section-heading { display: flex; align-items: baseline; gap: 24px; }
|
.section-heading span { color: #f05b61; font-size: 13px; }
|
.collection-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 42px; max-width: 780px; }
|
.collection-grid :deep(.el-form-item) { display: grid; grid-template-columns: 76px minmax(0, 1fr); margin: 0; }
|
.collection-grid :deep(.el-form-item__label) { width: auto !important; color: #53657d; font-weight: 600; }
|
.save-row { padding: 28px 36px; border-top: 10px solid #f4f7fb; }
|
.save-row :deep(.el-button) { width: 138px; }
|
.team-detail-page :deep(.el-dialog .el-select) { width: 100%; }
|
@media (max-width: 1400px) {
|
.basic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
}
|
</style>
|