liujie
2026-07-30 280ef5100bece9b40ac51736aef037704a52a4ca
frontend/src/views/GuideUserDetailView.vue
@@ -3,13 +3,25 @@
import { useRoute, useRouter } from 'vue-router'
import { ArrowLeft, Check, Plus, Upload } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getGuideUserDetail, updateGuideUserDetail, updateGuideWhitelist } from '@/api/users'
import {
  addGuideScenicConfig,
  deleteGuideScenicConfig,
  getGuideUserDetail,
  updateGuideUserDetail,
  updateGuideWhitelist,
} from '@/api/users'
import { getScenicSpots } from '@/api/scenicSpots'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const saving = ref(false)
const user = ref(null)
const scenicOptions = ref([])
const scenicPopoverVisible = ref(false)
const selectedScenicSpotId = ref(null)
const scenicSaving = ref(false)
const deletingScenicConfigId = ref(null)
const documentImages = computed(() => [
  { key: 'idCardFrontUrl', label: '身份证(正)*' },
@@ -21,6 +33,10 @@
const formatDateTime = (value) => value ? value.replace('T', ' ').slice(0, 19) : '-'
const displayValue = (value) => value === null || value === undefined || value === '' ? '-' : value
const guideStatusLabel = (status) => ({ 0: '禁用', 1: '启用', 2: '待审核', 3: '已注销' }[status] || '未知')
const availableScenicOptions = computed(() => {
  const configuredNames = new Set((user.value?.scenicConfigs || []).map((item) => item.scenicName))
  return scenicOptions.value.filter((item) => !configuredNames.has(item.scenicName))
})
const loadDetail = async () => {
  loading.value = true
@@ -31,16 +47,55 @@
  }
}
const loadScenicOptions = async () => {
  const result = await getScenicSpots({ page: 1, size: 100, status: 1 })
  scenicOptions.value = result.records || []
}
const updateWhitelist = async (enabled) => {
  await updateGuideWhitelist(user.value.id, enabled)
  ElMessage.success(enabled ? '已加入自动禁用白名单' : '已移出自动禁用白名单')
}
const addScenic = async () => {
const openScenicSelector = () => {
  selectedScenicSpotId.value = null
  if (!availableScenicOptions.value.length) {
    ElMessage.info('暂无可添加的启用景区')
    return
  }
  scenicPopoverVisible.value = true
}
const saveSelectedScenic = async (scenicSpotId) => {
  if (!scenicSpotId || scenicSaving.value) return
  scenicSaving.value = true
  try {
    const { value: scenicName } = await ElMessageBox.prompt('请输入景区名称', '添加景区配置', { inputValidator: (value) => !!value?.trim() || '景区名称不能为空' })
    const { value: cityName } = await ElMessageBox.prompt('请输入城市名称', '添加景区配置')
    user.value.scenicConfigs.push({ scenicName: scenicName.trim(), cityName: cityName?.trim() || '' })
    user.value.scenicConfigs = await addGuideScenicConfig(user.value.id, scenicSpotId)
    scenicPopoverVisible.value = false
    selectedScenicSpotId.value = null
    ElMessage.success('景区配置已保存')
  } catch (error) {
    selectedScenicSpotId.value = null
    throw error
  } finally {
    scenicSaving.value = false
  }
}
const removeScenic = async (config) => {
  try {
    await ElMessageBox.confirm(`确认删除“${config.scenicName}”配置吗?`, '删除景区配置', {
      type: 'warning',
      confirmButtonText: '删除',
      cancelButtonText: '取消',
    })
    deletingScenicConfigId.value = config.id
    try {
      user.value.scenicConfigs = await deleteGuideScenicConfig(user.value.id, config.id)
      ElMessage.success('景区配置已删除')
    } finally {
      deletingScenicConfigId.value = null
    }
  } catch (error) {
    if (error !== 'cancel' && error !== 'close') throw error
  }
@@ -64,7 +119,6 @@
      qualificationCertificateUrl: user.value.qualificationCertificateUrl,
      teamId: user.value.teamId,
      wechatOpenId: user.value.wechatOpenId,
      scenicConfigs: user.value.scenicConfigs,
    })
    ElMessage.success('导游详情保存成功')
  } finally {
@@ -82,7 +136,7 @@
  query: { guideId: user.value.id, guideName: user.value.realName },
})
onMounted(loadDetail)
onMounted(() => Promise.all([loadDetail(), loadScenicOptions()]))
</script>
<template>
@@ -158,11 +212,37 @@
            <el-table-column prop="scenicName" label="景区名称" align="center" />
            <el-table-column prop="cityName" label="城市" align="center" />
            <el-table-column label="操作" align="center">
              <template #default="{ $index }"><el-button link type="danger" @click="user.scenicConfigs.splice($index, 1)">删除</el-button></template>
              <template #default="{ row }">
                <el-button
                  link
                  type="danger"
                  :loading="deletingScenicConfigId === row.id"
                  @click="removeScenic(row)"
                >删除</el-button>
              </template>
            </el-table-column>
            <template #empty><span class="empty-text">暂无景区配置</span></template>
          </el-table>
          <el-button class="add-scenic" type="primary" :icon="Plus" @click="addScenic">添加</el-button>
          <el-popover v-model:visible="scenicPopoverVisible" placement="bottom-end" :width="320" trigger="manual">
            <el-select
              v-model="selectedScenicSpotId"
              class="scenic-selector"
              filterable
              placeholder="请选择景区,选择后自动保存"
              :loading="scenicSaving"
              @change="saveSelectedScenic"
            >
              <el-option
                v-for="item in availableScenicOptions"
                :key="item.id"
                :label="`${item.scenicName}(${item.cityName || '未配置城市'})`"
                :value="item.id"
              />
            </el-select>
            <template #reference>
              <el-button class="add-scenic" type="primary" :icon="Plus" @click="openScenicSelector">添加</el-button>
            </template>
          </el-popover>
        </div>
        <div class="whitelist-row">
@@ -216,6 +296,7 @@
.scenic-table { margin-left: 86px; }
.empty-text { color: #9aa5b1; }
.add-scenic { display: block; margin: 12px 0 0 auto; }
.scenic-selector { width: 100%; }
.whitelist-row { display: flex; align-items: center; gap: 18px; margin-top: 22px; }
.collection-section { min-height: 150px; }
.section-heading { display: flex; align-items: baseline; gap: 24px; }