zjk
2026-08-06 25ed74b6d0be3646f6acda98d93fbc1dad472775
feat(merchant-admin): 更新商户分类、完善类型定义并新增表单样式

新增并调整商户可选分类列表,补充多种新的商户类型
重构商户权益配置的类型定义为结构化数组,新增小程序跳转相关字段
添加商户编辑页面的配套CSS样式,优化表单布局与展示效果
4个文件已修改
1444 ■■■■■ 已修改文件
talent-card-admin/src/style.css 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/types/admin.ts 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/MerchantCreateView.vue 1253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/MerchantView.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/style.css
@@ -677,7 +677,7 @@
}
.merchant-rights-panel {
  min-height: 1260px;
  /* min-height: 1260px; */
  padding: 24px 0 10px;
  background: #ffffff;
}
@@ -734,6 +734,12 @@
  padding: 14px 0 0;
}
.merchant-rights-detail-content {
  grid-column: 1 / 3;
  display: flex;
  flex-direction: column;
}
.merchant-rights-detail-row label {
  grid-column: 1 / 3;
  color: #334155;
@@ -747,6 +753,35 @@
.merchant-rights-detail-row .el-textarea {
  grid-column: 1 / 3;
  width: 520px;
}
.carwash-miniprogram-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 24px;
}
.carwash-jump-radio {
  display: flex;
  gap: 24px;
}
.carwash-jump-input-row {
  display: flex;
  align-items: center;
  gap: 40px;
  margin-bottom: 8px;
}
.carwash-jump-input-row span {
  font-size: 14px;
  color: #111827;
  font-weight: bold;
}
.carwash-jump-input-row .el-input {
  width: 240px;
}
.merchant-rights-detail-row .el-textarea__inner {
@@ -964,6 +999,51 @@
.merchant-consume-row .el-input {
  width: 104px;
}
.hotel-voucher-row {
  grid-template-columns: 76px 42px 104px 24px 64px 104px 96px 42px;
}
.hotel-consume-row {
  display: flex;
  align-items: flex-start;
  padding-left: 186px;
  padding-top: 20px;
  gap: 12px;
  color: #111827;
  font-size: 13px;
}
.hotel-consume-row > strong {
  width: 76px;
  flex-shrink: 0;
  margin-top: 6px;
}
.hotel-consume-row > .el-switch {
  margin-top: 6px;
}
.hotel-consume-options {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.hotel-consume-option {
  display: flex;
  align-items: center;
  gap: 12px;
}
.hotel-consume-option .el-radio {
  margin-right: 0;
  width: 50px;
}
.hotel-margin-left {
  margin-left: 16px;
}
.consume-desc-row {
@@ -1225,10 +1305,24 @@
}
.product-form-grid {
  display: grid;
  grid-template-columns: 280px 270px;
  column-gap: 26px;
  display: flex;
  gap: 26px;
  align-items: start;
}
.product-info-left {
  width: 360px;
  flex-shrink: 0;
}
.product-info-middle {
  flex-shrink: 0;
  width: 270px;
}
.product-info-right {
  flex-shrink: 0;
  width: 270px;
}
.merchant-product-form-panel .el-input__wrapper,
@@ -1251,6 +1345,16 @@
  grid-template-columns: 54px 134px 24px;
}
.product-form-row.amount.hotel-amount {
  display: flex;
  align-items: center;
  gap: 10px;
}
.product-form-row.amount.hotel-amount .el-input-number {
  width: 76px;
}
.product-form-row.amount .el-input-number {
  width: 134px;
}
talent-card-admin/src/types/admin.ts
@@ -180,7 +180,21 @@
  groupBuyingEnabled?: number
  usageInstructions?: string
  rightsText?: string
  merchantRightsConfig?: Array<Record<string, unknown>> | string
  miniProgramJumpType?: string
  miniProgramJumpUrl?: string
  merchantRightsConfig?: Array<{
    level: string
    enabled: boolean
    tag: string
    detail: string
    storeConsume: boolean
    discount: string
    governmentSubsidy: boolean
    consumeDesc: string
    consumeEnabled: boolean
    parkingType?: string // "free" | "paid"
    parkingScope?: string // "all" | "selected"
  }> | string
  serviceLabel?: string
  businessHours?: Array<{ startTime: string; endTime: string }>
  isRecommend?: number
@@ -209,7 +223,21 @@
  groupBuyingEnabled?: number
  usageInstructions?: string
  rightsText?: string
  merchantRightsConfig?: Array<Record<string, unknown>>
  miniProgramJumpType?: string
  miniProgramJumpUrl?: string
  merchantRightsConfig?: Array<{
    level: string
    enabled: boolean
    tag: string
    detail: string
    storeConsume: boolean
    discount: string
    governmentSubsidy: boolean
    consumeDesc: string
    consumeEnabled: boolean
    parkingType?: string // "free" | "paid"
    parkingScope?: string // "all" | "selected"
  }>
  serviceLabel?: string
  businessHours?: Array<{ startTime: string; endTime: string }>
  recommend?: number
talent-card-admin/src/views/MerchantCreateView.vue
@@ -40,6 +40,17 @@
  governmentSubsidy: boolean
  consumeDesc: string
  consumeEnabled: boolean
  freeVoucherEnabled?: boolean
  freeVoucherTimes?: string
  freeVoucherCycle?: string
  freeVoucherSubsidy?: boolean
  consumeType?: string
  consumeDiscount?: string
  consumeDiscountSubsidy?: boolean
  consumeCoupon?: string
  consumeCouponSubsidy?: boolean
  parkingType?: 'free' | 'paid'
  parkingScope?: 'all' | 'selected'
}
const route = useRoute()
@@ -50,7 +61,7 @@
const accountTaken = ref(false)
const imageInputRef = ref<HTMLInputElement>()
const imageUploading = ref(false)
const imageUploadTarget = ref<'merchant' | 'productCover' | 'productImage'>('merchant')
const imageUploadTarget = ref<'merchant' | 'productCover' | 'productImage' | 'approvalWindow'>('merchant')
const merchantCoverPreviewFailed = ref(false)
const productCoverPreviewFailed = ref(false)
const routeTab = typeof route.query.tab === 'string' ? route.query.tab : 'settings'
@@ -59,6 +70,16 @@
const isEditMode = computed(() => route.query.mode === 'edit')
const normalizedMerchantType = computed(() => String(form.merchantType || '').trim())
const isFoodMerchant = computed(() => normalizedMerchantType.value === '餐饮服务')
const isApartmentMerchant = computed(() => normalizedMerchantType.value === '公寓租赁')
const isHotelMerchant = computed(() => normalizedMerchantType.value === '酒店服务')
const isCarWashMerchant = computed(() => normalizedMerchantType.value === '自助洗车')
const isHealthCheckMerchant = computed(() => normalizedMerchantType.value === '免费体检')
const isParkingMerchant = computed(() => normalizedMerchantType.value === '停车服务')
const isChildcareMerchant = computed(() => normalizedMerchantType.value === '子女托管')
const isFinancialMerchant = computed(() => normalizedMerchantType.value === '金融服务')
const isMovieMerchant = computed(() => normalizedMerchantType.value === '观影服务')
const isSpouseEmploymentMerchant = computed(() => normalizedMerchantType.value === '配偶推荐就业')
const isApprovalMerchant = computed(() => normalizedMerchantType.value === '行政审批')
const isServiceMerchant = computed(() => isFoodMerchant.value || normalizedMerchantType.value === '景区服务')
const requiresMerchantOperator = computed(() => !isEditMode.value && (
  normalizedMerchantType.value === '景区服务'
@@ -75,12 +96,40 @@
  return merchantType === '餐饮服务' || merchantType === '景区服务' ? 1 : 0
}
const tabs = [
  { label: '商家设置', value: 'settings' },
  { label: '商家权益', value: 'rights' },
  { label: '团单', value: 'products' },
  { label: '优惠券', value: 'coupons' },
]
const tabs = computed(() => {
  if (isApartmentMerchant.value) {
    return [
      { label: '商家设置', value: 'settings' },
      { label: '商家权益', value: 'rights' },
    ]
  }
  if (isHealthCheckMerchant.value || isParkingMerchant.value || isChildcareMerchant.value || isFinancialMerchant.value || isMovieMerchant.value || isSpouseEmploymentMerchant.value || isApprovalMerchant.value) {
    return [
      { label: '商家设置', value: 'settings' },
      { label: '商家权益', value: 'rights' },
    ]
  }
  if (isCarWashMerchant.value) {
    if (form.miniProgramJumpType === 'normal') {
      return [
        { label: '商家设置', value: 'settings' },
        { label: '商家权益', value: 'rights' },
        { label: '团单', value: 'products' },
        { label: '优惠券', value: 'coupons' },
      ]
    }
    return [
      { label: '商家设置', value: 'settings' },
      { label: '商家权益', value: 'rights' },
    ]
  }
  return [
    { label: '商家设置', value: 'settings' },
    { label: '商家权益', value: 'rights' },
    { label: '团单', value: 'products' },
    { label: '优惠券', value: 'coupons' },
  ]
})
const form = reactive<MerchantPayload & { createdAt: string }>({
  merchantName: '',
@@ -96,6 +145,8 @@
  groupBuyingEnabled: 0,
  usageInstructions: '',
  rightsText: '',
  miniProgramJumpType: 'miniprogram',
  miniProgramJumpUrl: '',
  serviceLabel: '',
  recommend: defaultMerchantRecommend(),
  sortNo: undefined,
@@ -148,7 +199,7 @@
  total: 0,
})
const productForm = reactive<ProductPayload>({
const productForm = reactive<ProductPayload & { createdAt?: string, updatedAt?: string }>({
  merchantId: queryString('merchantId'),
  productType: 'GROUP',
  productName: '',
@@ -161,6 +212,21 @@
  sortNo: 0,
})
const productImages = ref<string[]>([])
interface HealthCheckItem {
  name: string
  meaning: string
  gender: string[]
}
const healthCheckItems = ref<HealthCheckItem[]>([])
function addHealthCheckItem() {
  healthCheckItems.value.push({ name: '', meaning: '', gender: ['男'] })
}
function removeHealthCheckItem(index: number) {
  healthCheckItems.value.splice(index, 1)
}
const productNoticeForm = reactive({
  validPeriod: '',
@@ -284,6 +350,387 @@
const businessHours = reactive<BusinessHour[]>([{ id: 1, value: [] }])
function defaultMerchantRights(): MerchantRightConfig[] {
  const currentType = queryString('merchantType', '') || normalizedMerchantType.value
  const isHotel = currentType === '酒店服务'
  const isCarWash = currentType === '自助洗车'
  const isHealthCheck = currentType === '免费体检'
  const isParking = currentType === '停车服务'
  const isChildcare = currentType === '子女托管'
  const isFinancial = currentType === '金融服务'
  const isMovie = currentType === '观影服务'
  const isSpouseEmployment = currentType === '配偶推荐就业'
  const isApproval = currentType === '行政审批'
  if (isHealthCheck) {
    return [
      {
        level: '卓越人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '菁英人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
    ]
  }
  if (isFinancial) {
    return [
      {
        level: '卓越人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '菁英人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
    ]
  }
  if (isMovie) {
    return [
      {
        level: '卓越人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '菁英人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
    ]
  }
  if (isSpouseEmployment) {
    return [
      {
        level: '卓越人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '菁英人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
    ]
  }
  if (isApproval) {
    return []
  }
  if (isCarWash && form.miniProgramJumpType === 'miniprogram') {
    return [
      {
        level: '卓越人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: false,
        discount: '',
        governmentSubsidy: false,
        consumeDesc: '',
        consumeEnabled: false,
      },
      {
          level: '菁英人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: false,
          discount: '',
          governmentSubsidy: false,
          consumeDesc: '',
          consumeEnabled: false,
        },
      ]
    }
    if (isChildcare) {
      return [
        {
          level: '卓越人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
        },
        {
          level: '杰出人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
        },
        {
          level: '菁英人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
        },
        {
          level: '青年人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
        },
      ]
    }
    if (isParking) {
      return [
        {
          level: '卓越人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
          parkingType: 'free',
          parkingScope: 'all',
        },
        {
          level: '杰出人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
          parkingType: 'paid',
        },
        {
          level: '菁英人才卡',
          enabled: true,
          tag: '',
          detail: '',
          storeConsume: true,
          discount: '',
          governmentSubsidy: true,
          consumeDesc: '',
          consumeEnabled: true,
          parkingType: 'paid',
        },
      ]
    }
    if (isHotel) {
    return [
      {
        level: '卓越人才卡(外地)',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: true,
        discount: '',
        governmentSubsidy: true,
        consumeDesc: '',
        consumeEnabled: true,
        freeVoucherEnabled: true,
        freeVoucherTimes: '',
        freeVoucherCycle: '每天',
        freeVoucherSubsidy: true,
        consumeType: '折扣',
        consumeDiscount: '',
        consumeDiscountSubsidy: true,
        consumeCoupon: '',
        consumeCouponSubsidy: true,
      },
      {
        level: '卓越人才卡(本地)',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: true,
        discount: '',
        governmentSubsidy: true,
        consumeDesc: '',
        consumeEnabled: true,
        freeVoucherEnabled: true,
        freeVoucherTimes: '',
        freeVoucherCycle: '每天',
        freeVoucherSubsidy: true,
        consumeType: '折扣',
        consumeDiscount: '',
        consumeDiscountSubsidy: true,
        consumeCoupon: '',
        consumeCouponSubsidy: true,
      },
      {
        level: '杰出人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: true,
        discount: '',
        governmentSubsidy: true,
        consumeDesc: '',
        consumeEnabled: true,
        freeVoucherEnabled: true,
        freeVoucherTimes: '',
        freeVoucherCycle: '每天',
        freeVoucherSubsidy: true,
        consumeType: '折扣',
        consumeDiscount: '',
        consumeDiscountSubsidy: true,
        consumeCoupon: '',
        consumeCouponSubsidy: true,
      },
      {
        level: '菁英人才卡',
        enabled: true,
        tag: '',
        detail: '',
        storeConsume: true,
        discount: '',
        governmentSubsidy: true,
        consumeDesc: '',
        consumeEnabled: true,
        freeVoucherEnabled: true,
        freeVoucherTimes: '',
        freeVoucherCycle: '每天',
        freeVoucherSubsidy: true,
        consumeType: '折扣',
        consumeDiscount: '',
        consumeDiscountSubsidy: true,
        consumeCoupon: '',
        consumeCouponSubsidy: true,
      },
    ]
  }
  return [
  {
    level: '卓越人才卡',
@@ -352,6 +799,136 @@
let locationMap: LeafletMap | undefined
let locationMarker: Marker | undefined
const mapMarkerIcon = L.divIcon({ className: 'merchant-map-marker', html: '<span></span>', iconSize: [26, 26], iconAnchor: [13, 26] })
interface ParkingAddress {
  name: string
  address: string
  longitude?: number
  latitude?: number
}
interface SchoolAddress {
  name: string
  address: string
  longitude?: number
  latitude?: number
  detail?: string
}
interface ApprovalItem {
  name: string
  content: string
  address: string
  phone: string
  talentLevels: string[]
  windowInfoUrl?: string
  isNew?: boolean
  isEditing?: boolean
}
const parkingAddresses = ref<ParkingAddress[]>([])
const activeParkingAddress = ref<ParkingAddress>()
const schoolAddresses = ref<SchoolAddress[]>([])
const activeSchoolAddress = ref<SchoolAddress>()
const schoolDialogVisible = ref(false)
const editingSchoolIndex = ref(-1)
const editingSchool = reactive<SchoolAddress>({ name: '', address: '', detail: '' })
const approvalItems = ref<ApprovalItem[]>([])
const approvalUploadIndex = ref(-1)
function addParkingAddress() {
  parkingAddresses.value.push({ name: '', address: '' })
}
function removeParkingAddress(index: number) {
  parkingAddresses.value.splice(index, 1)
}
async function markParkingLocation(row: ParkingAddress) {
  activeSchoolAddress.value = undefined
  activeParkingAddress.value = row
  pendingLongitude.value = String(row.longitude || '')
  pendingLatitude.value = String(row.latitude || '')
  locationDialogVisible.value = true
  locationSearch.value = row.address || ''
  await initializeLocationMap()
}
function openSchoolDialog(index: number) {
  editingSchoolIndex.value = index
  if (index >= 0) {
    Object.assign(editingSchool, schoolAddresses.value[index])
  } else {
    Object.assign(editingSchool, { name: '', address: '', detail: '', longitude: undefined, latitude: undefined })
  }
  schoolDialogVisible.value = true
}
function saveSchool() {
  if (!editingSchool.name?.trim()) { ElMessage.warning('请输入学校名称'); return }
  if (!editingSchool.address?.trim()) { ElMessage.warning('请输入地址'); return }
//   if (!editingSchool.longitude || !editingSchool.latitude) { ElMessage.warning('请完成定位标点'); return }
  if (editingSchoolIndex.value >= 0) {
    Object.assign(schoolAddresses.value[editingSchoolIndex.value], editingSchool)
  } else {
    schoolAddresses.value.push({ ...editingSchool })
  }
  schoolDialogVisible.value = false
}
function removeSchool(index: number) {
  schoolAddresses.value.splice(index, 1)
}
async function markSchoolLocation() {
  activeParkingAddress.value = undefined
  activeSchoolAddress.value = editingSchool
  pendingLongitude.value = String(editingSchool.longitude || '')
  pendingLatitude.value = String(editingSchool.latitude || '')
  locationDialogVisible.value = true
  locationSearch.value = editingSchool.address || ''
  await initializeLocationMap()
}
function addApprovalRow() {
  approvalItems.value.push({
    name: '',
    content: '',
    address: '',
    phone: '',
    talentLevels: [],
    windowInfoUrl: '',
    isNew: true,
    isEditing: true,
  })
}
function saveApprovalRow(index: number) {
  const item = approvalItems.value[index]
  if (!item) return
  if (!item.name.trim()) { ElMessage.warning('请输入名称'); return }
  if (!item.content.trim()) { ElMessage.warning('请输入权益内容'); return }
  if (!item.address.trim()) { ElMessage.warning('请输入地址'); return }
  if (!item.phone.trim()) { ElMessage.warning('请输入电话'); return }
  if (!item.talentLevels.length) { ElMessage.warning('请至少选择一个适用人才'); return }
  item.isNew = false
  item.isEditing = false
  ElMessage.success('当前行已保存')
}
function editApprovalRow(index: number) {
  const item = approvalItems.value[index]
  if (!item) return
  item.isEditing = true
}
function removeApproval(index: number) {
  approvalItems.value.splice(index, 1)
}
function uploadApprovalWindow(index: number) {
  approvalUploadIndex.value = index
  changeCover('approvalWindow')
}
function addBusinessHour() {
  businessHours.push({ id: Date.now(), value: [] })
@@ -435,6 +1012,8 @@
}
async function markLocation() {
  activeParkingAddress.value = undefined
  activeSchoolAddress.value = undefined
  pendingLongitude.value = String(form.longitude || '')
  pendingLatitude.value = String(form.latitude || '')
  locationDialogVisible.value = true
@@ -449,14 +1028,22 @@
    ElMessage.warning('请输入有效的经纬度')
    return
  }
  form.longitude = longitude
  form.latitude = latitude
  if (activeParkingAddress.value) {
    activeParkingAddress.value.longitude = longitude
    activeParkingAddress.value.latitude = latitude
  } else if (activeSchoolAddress.value) {
    activeSchoolAddress.value.longitude = longitude
    activeSchoolAddress.value.latitude = latitude
  } else {
    form.longitude = longitude
    form.latitude = latitude
  }
  locationDialogVisible.value = false
  closeLocationMap()
  ElMessage.success('地图位置已标记')
}
function changeCover(target: 'merchant' | 'productCover' | 'productImage') {
function changeCover(target: 'merchant' | 'productCover' | 'productImage' | 'approvalWindow') {
  imageUploadTarget.value = target
  imageInputRef.value?.click()
}
@@ -495,6 +1082,10 @@
    if (imageUploadTarget.value === 'merchant') {
      form.coverUrl = result.url
      resetMerchantCoverPreview()
    } else if (imageUploadTarget.value === 'approvalWindow') {
      if (approvalUploadIndex.value >= 0) {
        approvalItems.value[approvalUploadIndex.value].windowInfoUrl = result.url
      }
    } else if (imageUploadTarget.value === 'productCover') {
      productForm.coverUrl = result.url
      resetProductCoverPreview()
@@ -505,6 +1096,7 @@
  } catch {
    // The shared HTTP interceptor displays the upload error.
  } finally {
    approvalUploadIndex.value = -1
    imageUploading.value = false
  }
}
@@ -513,7 +1105,11 @@
  const savedRights = parseJsonValue<MerchantRightConfig[]>(config, [])
  rightsConfigs.splice(0, rightsConfigs.length, ...defaultMerchantRights().map((item) => {
    const saved = Array.isArray(savedRights) ? savedRights.find((entry) => entry?.level === item.level) : undefined
    return { ...item, ...(saved || {}) }
    return {
      ...item,
      ...(saved || {}),
      consumeEnabled: item.consumeEnabled
    }
  }))
}
@@ -522,6 +1118,64 @@
  if (!merchantId) {
    ElMessage.warning('请先保存商家设置')
    return
  }
  if (isParkingMerchant.value) {
    if (parkingAddresses.value.length === 0) {
      ElMessage.warning('请至少添加一个停车场')
      return
    }
    const names = parkingAddresses.value.map(p => p.name?.trim()).filter(Boolean)
    if (names.length !== parkingAddresses.value.length) {
      ElMessage.warning('停车场名称不能为空')
      return
    }
    if (new Set(names).size !== names.length) {
      ElMessage.warning('停车场名称不能重复')
      return
    }
    const addresses = parkingAddresses.value.map(p => p.address?.trim()).filter(Boolean)
    if (addresses.length !== parkingAddresses.value.length) {
      ElMessage.warning('停车场地址不能为空')
      return
    }
    const unlocated = parkingAddresses.value.filter(p => !p.latitude || !p.longitude)
    if (unlocated.length > 0) {
      ElMessage.warning('请完成所有停车场的标点')
      return
    }
  }
  if (isChildcareMerchant.value && schoolAddresses.value.length > 0) {
    const names = schoolAddresses.value.map(s => s.name?.trim()).filter(Boolean)
    if (names.length !== schoolAddresses.value.length) {
      ElMessage.warning('学校名称不能为空')
      return
    }
    const addresses = schoolAddresses.value.map(s => s.address?.trim()).filter(Boolean)
    if (addresses.length !== schoolAddresses.value.length) {
      ElMessage.warning('学校地址不能为空')
      return
    }
    const unlocated = schoolAddresses.value.filter(s => !s.latitude || !s.longitude)
    if (unlocated.length > 0) {
      ElMessage.warning('请完成所有学校地址的标点')
      return
    }
  }
  if (isApprovalMerchant.value && approvalItems.value.length > 0) {
    const invalidItem = approvalItems.value.find(item => (
      !item.name?.trim()
      || !item.content?.trim()
      || !item.address?.trim()
      || !item.phone?.trim()
      || !item.talentLevels?.length
    ))
    if (invalidItem) {
      ElMessage.warning('请完善行政审批权益表格信息')
      return
    }
  }
  rightsSaving.value = true
@@ -538,7 +1192,13 @@
      latitude: Number(form.latitude || 0) || undefined,
      canLoginMerchantApp: Number(form.canLoginMerchantApp ?? 1),
      groupBuyingEnabled: form.groupBuyingEnabled,
      usageInstructions: form.usageInstructions,
      usageInstructions: isParkingMerchant.value
        ? JSON.stringify(parkingAddresses.value)
        : isChildcareMerchant.value
          ? JSON.stringify(schoolAddresses.value)
          : isApprovalMerchant.value
            ? JSON.stringify(approvalItems.value)
            : form.usageInstructions,
      rightsText: form.rightsText,
      merchantRightsConfig: rightsConfigs.map((item) => ({ ...item })),
      serviceLabel: form.serviceLabel,
@@ -572,6 +1232,13 @@
}
function productAmountText(row: Product) {
  if (isHotelMerchant.value) {
    const min = row.salePrice
    const max = row.originalPrice
    if (min !== undefined && max !== undefined) {
      return `${Number(min).toFixed(2).replace(/\.00$/, '')} - ${Number(max).toFixed(2).replace(/\.00$/, '')}`
    }
  }
  const amount = row.salePrice ?? row.originalPrice
  if (amount === undefined || amount === null) return ''
  return Number(amount).toFixed(2).replace(/\.00$/, '')
@@ -621,12 +1288,18 @@
  productForm.benefitDesc = row?.benefitDesc || ''
  productForm.status = row?.status ?? 1
  productForm.sortNo = row?.sortNo ?? 0
  productForm.createdAt = row?.createdAt || ''
  productForm.updatedAt = row?.submitTime || row?.createdAt || ''
  productNoticeForm.validPeriod = notice.validPeriod || ''
  productNoticeForm.useMethod = notice.useMethod || ''
  productNoticeForm.availableTime = notice.availableTime || ''
  productNoticeForm.otherRule = notice.otherRule || ''
  productValidPeriodRange.value = rangeValues(productNoticeForm.validPeriod, /\d{4}-\d{2}-\d{2}/g)
  productAvailableTimeRange.value = rangeValues(productNoticeForm.availableTime, /\d{2}:\d{2}/g)
  if (isHealthCheckMerchant.value) {
    const parsed = parseJsonValue<HealthCheckItem[]>(row?.detailText, [])
    healthCheckItems.value = parsed.length ? parsed : [{ name: '', meaning: '', gender: ['男'] }]
  }
  resetProductRights(row?.rightsConfig)
}
@@ -674,10 +1347,10 @@
}
function openProductCreate() {
  if (!currentMerchantId()) {
    ElMessage.warning('请先保存商家信息')
    return
  }
//   if (!currentMerchantId()) {
//     ElMessage.warning('请先保存商家信息')
//     return
//   }
  resetProductForm()
  productMode.value = 'form'
}
@@ -705,35 +1378,53 @@
    ElMessage.warning('请输入商品名称')
    return
  }
  if (productForm.salePrice === undefined || productForm.salePrice === null || !Number.isFinite(Number(productForm.salePrice)) || Number(productForm.salePrice) <= 0) {
    ElMessage.warning('请输入大于 0 的商品金额')
    return
  }
  if (!productForm.coverUrl?.trim()) {
    ElMessage.warning('请上传商品封面图')
    return
  }
  if (productImages.value.length === 0) {
    ElMessage.warning('请至少添加一张商品图片')
    return
  }
  if (!productForm.detailText?.trim()) {
    ElMessage.warning('请输入商品详情')
    return
  }
  if (productRights.some((item) => item.free && !validFreeLimitCount(item.limitCount))) {
    ElMessage.warning('免费次数请输入 1 至 999 的整数,999 表示不限次数')
    return
  }
  if (productRights.some((item) => item.free && !validExceedDiscount(item.exceedDiscount))) {
    ElMessage.warning('超过次数折扣请输入大于 0 且不超过 10 的一位小数')
    return
  if (!isHealthCheckMerchant.value) {
    if (productForm.salePrice === undefined || productForm.salePrice === null || !Number.isFinite(Number(productForm.salePrice)) || Number(productForm.salePrice) <= 0) {
      ElMessage.warning('请输入大于 0 的商品金额')
      return
    }
    if (productImages.value.length === 0) {
      ElMessage.warning('请至少添加一张商品图片')
      return
    }
    if (!productForm.detailText?.trim()) {
      ElMessage.warning('请输入商品详情')
      return
    }
    if (productRights.some((item) => item.free && !validFreeLimitCount(item.limitCount))) {
      ElMessage.warning('免费次数请输入 1 至 999 的整数,999 表示不限次数')
      return
    }
    if (productRights.some((item) => item.free && !validExceedDiscount(item.exceedDiscount))) {
      ElMessage.warning('超过次数折扣请输入大于 0 且不超过 10 的一位小数')
      return
    }
  } else {
    if (!productValidPeriodRange.value || productValidPeriodRange.value.length !== 2) {
      ElMessage.warning('请选择可预约时间')
      return
    }
    if (healthCheckItems.value.length === 0) {
      ElMessage.warning('请至少添加一项检查项目')
      return
    }
    for (const item of healthCheckItems.value) {
      if (!item.name?.trim() || !item.meaning?.trim() || item.gender.length === 0) {
        ElMessage.warning('请完善检查项目信息(名称、意义、适用性别均不能为空)')
        return
      }
    }
  }
  productSaving.value = true
  try {
    const payload = {
      ...productForm,
      merchantId: currentMerchantId(),
      detailText: isHealthCheckMerchant.value ? JSON.stringify(healthCheckItems.value) : productForm.detailText,
      salePrice: productForm.salePrice === undefined ? undefined : Number(productForm.salePrice),
      originalPrice: productForm.originalPrice === undefined ? undefined : Number(productForm.originalPrice),
      stock: productForm.stock === undefined ? undefined : Number(productForm.stock),
@@ -1031,7 +1722,7 @@
        ElMessage.warning('请上传商家封面图')
        return
    }
    if (!hasValidBusinessHours()) {
        if (!isApprovalMerchant.value && !hasValidBusinessHours()) {
        ElMessage.warning('请选择有效的营业时间')
        return
    }
@@ -1078,6 +1769,60 @@
    return
  }
  if (isParkingMerchant.value && parkingAddresses.value.length > 0) {
    const names = parkingAddresses.value.map(p => p.name?.trim()).filter(Boolean)
    if (names.length !== parkingAddresses.value.length) {
      ElMessage.warning('停车场名称不能为空')
      return
    }
    if (new Set(names).size !== names.length) {
      ElMessage.warning('停车场名称不能重复')
      return
    }
    const addresses = parkingAddresses.value.map(p => p.address?.trim()).filter(Boolean)
    if (addresses.length !== parkingAddresses.value.length) {
      ElMessage.warning('停车场地址不能为空')
      return
    }
    const unlocated = parkingAddresses.value.filter(p => !p.latitude || !p.longitude)
    if (unlocated.length > 0) {
      ElMessage.warning('请完成所有停车场的标点')
      return
    }
  }
  if (isChildcareMerchant.value && schoolAddresses.value.length > 0) {
    const names = schoolAddresses.value.map(s => s.name?.trim()).filter(Boolean)
    if (names.length !== schoolAddresses.value.length) {
      ElMessage.warning('学校名称不能为空')
      return
    }
    const addresses = schoolAddresses.value.map(s => s.address?.trim()).filter(Boolean)
    if (addresses.length !== schoolAddresses.value.length) {
      ElMessage.warning('学校地址不能为空')
      return
    }
    const unlocated = schoolAddresses.value.filter(s => !s.latitude || !s.longitude)
    if (unlocated.length > 0) {
      ElMessage.warning('请完成所有学校地址的标点')
      return
    }
  }
  if (isApprovalMerchant.value && approvalItems.value.length > 0) {
    const invalidItem = approvalItems.value.find(item => (
      !item.name?.trim()
      || !item.content?.trim()
      || !item.address?.trim()
      || !item.phone?.trim()
      || !item.talentLevels?.length
    ))
    if (invalidItem) {
      ElMessage.warning('请完善行政审批权益表格信息')
      return
    }
  }
  saving.value = true
  try {
    const payload = {
@@ -1094,7 +1839,13 @@
        loginAccount: !isEditMode.value ? accountForm.loginAccount : undefined,
        loginPassword: !isEditMode.value || updatingMerchantPassword ? accountForm.loginPassword : undefined,
        groupBuyingEnabled: form.groupBuyingEnabled,
        usageInstructions: form.usageInstructions,
        usageInstructions: isParkingMerchant.value
            ? JSON.stringify(parkingAddresses.value)
            : isChildcareMerchant.value
                ? JSON.stringify(schoolAddresses.value)
                : isApprovalMerchant.value
                    ? JSON.stringify(approvalItems.value)
                    : form.usageInstructions,
        rightsText: form.rightsText,
        merchantRightsConfig: rightsConfigs.map((item) => ({ ...item })),
        serviceLabel: form.serviceLabel,
@@ -1136,6 +1887,12 @@
  productNoticeForm.availableTime = rangeText(value)
})
watch(() => form.miniProgramJumpType, () => {
  if (isCarWashMerchant.value && !isEditMode.value) {
    resetMerchantRights(rightsConfigs.map((item) => ({ ...item })))
  }
})
onMounted(() => {
    if (isEditMode.value && queryString('merchantId')) {
        getAdminMerchantDetail(queryString('merchantId')).then((merchant) => {
@@ -1147,6 +1904,13 @@
            form.canLoginMerchantApp = Number(merchant.canLoginMerchantApp ?? 1)
            form.recommend = Number(merchant.isRecommend ?? 0)
            accountForm.loginAccount = merchant.loginAccount || ''
      if (isParkingMerchant.value) {
        parkingAddresses.value = parseJsonValue<ParkingAddress[]>(merchant.usageInstructions, [])
      } else if (isChildcareMerchant.value) {
        schoolAddresses.value = parseJsonValue<SchoolAddress[]>(merchant.usageInstructions, [])
      } else if (isApprovalMerchant.value) {
        approvalItems.value = parseJsonValue<ApprovalItem[]>(merchant.usageInstructions, [])
      }
            resetBusinessHours(merchant.businessHours)
            resetMerchantRights(merchant.merchantRightsConfig)
        })
@@ -1179,15 +1943,15 @@
      <section class="merchant-create-panel">
        <h3>商家信息</h3>
        <div class="merchant-create-content" :class="{ 'service-merchant-content': isServiceMerchant }">
          <div class="merchant-create-left">
        <div class="merchant-create-content" :class="{ 'service-merchant-content': isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant }">
          <div class="merchant-create-left" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant ? 'flex: 1' : ''">
            <div class="merchant-form-row three-columns">
              <label>商家名称</label>
              <el-input v-model="form.merchantName" />
            </div>
            <div class="merchant-form-row address-row">
              <label>地址</label>
            <div v-if="!isApartmentMerchant" class="merchant-form-row address-row">
              <label>{{ isCarWashMerchant || isHealthCheckMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant ? '服务点' : '地址' }}</label>
              <el-input v-model="form.address" />
              <el-button type="primary" @click="markLocation">地图标点</el-button>
            </div>
@@ -1207,33 +1971,33 @@
            </div>
          </div>
          <div v-if="!isServiceMerchant" class="merchant-create-middle">
          <div v-if="!isServiceMerchant && !isApartmentMerchant && !isHotelMerchant && !isCarWashMerchant && !isHealthCheckMerchant && !isParkingMerchant && !isChildcareMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && !isApprovalMerchant" class="merchant-create-middle">
            <div class="merchant-form-row">
              <label>联系电话</label>
              <el-input v-model="form.contactMobile" />
            </div>
            <template>
                <div class="merchant-form-row recommend-row">
                    <label>开启团购</label>
                    <el-switch v-model="form.groupBuyingEnabled" :active-value="1" :inactive-value="0" />
                </div>
            <template v-if="!isApartmentMerchant">
              <div class="merchant-form-row recommend-row">
                <label>开启团购</label>
                <el-switch v-model="form.groupBuyingEnabled" :active-value="1" :inactive-value="0" />
              </div>
                <div class="merchant-form-row">
                    <label>服务标签</label>
                    <el-input v-model="form.serviceLabel" placeholder="如:到店消费" />
                </div>
              <div class="merchant-form-row">
                <label>服务标签</label>
                <el-input v-model="form.serviceLabel" placeholder="如:到店消费" />
              </div>
                <div class="merchant-form-row">
                    <label>经纬度</label>
                    <el-input v-model="form.longitude" placeholder="经度" />
                    <el-input v-model="form.latitude" placeholder="纬度" />
                </div>
            </template>
              <div class="merchant-form-row">
                <label>经纬度</label>
                <el-input v-model="form.longitude" placeholder="经度" />
                <el-input v-model="form.latitude" placeholder="纬度" />
              </div>
            </template>
          </div>
          <div class="merchant-create-right">
            <div v-if="isServiceMerchant" class="merchant-form-row service-contact-row">
          <div class="merchant-create-right" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant ? 'flex: 1' : ''">
            <div v-if="isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant" class="merchant-form-row service-contact-row">
              <label>联系电话</label>
              <el-input v-model="form.contactMobile" placeholder="请输入联系电话" />
            </div>
@@ -1243,7 +2007,7 @@
              <el-input v-model="form.createdAt" readonly />
            </div>
            <div class="merchant-hours-block">
            <div v-if="!isApprovalMerchant" class="merchant-hours-block">
              <label>营业时间</label>
              <div class="merchant-hours-list">
                <div v-for="(item, index) in businessHours" :key="item.id" class="merchant-hour-row">
@@ -1284,17 +2048,8 @@
          </div>
        </div>
    </section>
    <el-dialog v-model="locationDialogVisible" title="选择地址" width="820px" destroy-on-close @closed="closeLocationMap">
      <div class="merchant-location-picker">
        <div class="merchant-map-search"><el-input v-model="locationSearch" placeholder="搜索商家地址或地点" clearable @keyup.enter="searchMapLocation" /><el-button type="primary" :loading="locationSearching" @click="searchMapLocation">搜索</el-button></div>
        <div v-if="locationSearchResults.length" class="merchant-map-results"><button v-for="item in locationSearchResults" :key="`${item.longitude}-${item.latitude}`" type="button" @click="selectSearchLocation(item)">{{ item.name }}</button></div>
        <div ref="mapElement" class="merchant-location-map" />
        <div class="merchant-location-coordinates">已选坐标:{{ pendingLongitude || '-' }}, {{ pendingLatitude || '-' }}<span>可搜索地点或直接点击地图放置标点</span></div>
      </div>
      <template #footer><el-button @click="locationDialogVisible = false">取消</el-button><el-button type="primary" @click="saveLocation">确认</el-button></template>
    </el-dialog>
        <section v-if="!isServiceMerchant" class="merchant-create-panel">
                <section v-if="!isServiceMerchant && !isApartmentMerchant && !isHotelMerchant && !isCarWashMerchant && !isHealthCheckMerchant && !isParkingMerchant && !isChildcareMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && !isApprovalMerchant" class="merchant-create-panel">
            <h3>小程序详情配置</h3>
            <div class="merchant-form-row">
                <label>使用说明</label>
@@ -1309,7 +2064,7 @@
      <section class="merchant-account-panel">
        <h3>账户设置</h3>
        <el-form class="merchant-account-form" :model="accountForm" label-width="72px" label-position="left">
          <el-form-item v-if="isServiceMerchant" label="登录商户端" label-width="100px">
          <el-form-item v-if="isServiceMerchant || isApartmentMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant" label="登录商户端" label-width="100px">
            <el-switch v-model="form.canLoginMerchantApp" :active-value="1" :inactive-value="0" />
          </el-form-item>
          <el-form-item label="登录账号" :error="accountTaken ? '登录账号已存在,请更换' : ''">
@@ -1329,12 +2084,23 @@
    <template v-else-if="activeTab === 'rights'">
      <section class="merchant-rights-panel">
        <div class="merchant-rights-top-row">
        <div v-if="!isApartmentMerchant && !isCarWashMerchant && !isHealthCheckMerchant && !isParkingMerchant && !isChildcareMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && !isApprovalMerchant" class="merchant-rights-top-row">
          <strong>团单服务</strong>
          <el-switch v-model="form.groupBuyingEnabled" :active-value="1" :inactive-value="0" />
        </div>
        <div v-for="item in rightsConfigs" :key="item.level" class="merchant-rights-card">
        <div v-if="isCarWashMerchant" class="merchant-rights-top-row carwash-miniprogram-row">
          <el-radio-group v-model="form.miniProgramJumpType" class="carwash-jump-radio">
            <el-radio label="miniprogram">小程序跳转</el-radio>
            <el-radio label="normal">普通</el-radio>
          </el-radio-group>
          <div v-if="form.miniProgramJumpType === 'miniprogram'" class="carwash-jump-input-row">
            <span>小程序跳转链接</span>
            <el-input v-model="form.miniProgramJumpUrl" placeholder="请输入" />
          </div>
        </div>
        <div v-if="!isApprovalMerchant" v-for="item in rightsConfigs" :key="item.level" class="merchant-rights-card">
          <div class="merchant-rights-level-row">
            <strong>{{ item.level }}</strong>
            <el-switch v-model="item.enabled" />
@@ -1344,8 +2110,22 @@
          <div class="merchant-rights-detail-row">
            <label>权益详情描述</label>
            <div class="merchant-rights-detail-content">
              <el-input v-model="item.detail" type="textarea" :rows="4" resize="none" placeholder="请输入" />
              <div class="merchant-rights-example">
              <div v-if="isParkingMerchant" class="merchant-rights-parking-radio" style="margin-top: 16px;">
                <el-radio-group v-model="item.parkingType">
                  <el-radio label="free">免费</el-radio>
                  <el-radio label="paid">付费</el-radio>
                </el-radio-group>
              </div>
              <div v-if="isParkingMerchant && item.parkingType === 'free'" style="margin-top: 16px;">
                <el-radio-group v-model="item.parkingScope" style="display: flex; flex-direction: column; align-items: flex-start; gap: 12px;">
                  <el-radio label="all" style="margin-right: 0;">停车场全部可选</el-radio>
                  <el-radio label="selected" style="margin-right: 0;">停车场只选其一</el-radio>
                </el-radio-group>
              </div>
            </div>
            <div class="merchant-rights-example" v-if="isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant">
                <span>示例:</span>
                <div class="merchant-rights-phone-preview">
                  <div class="phone-preview-hero">
@@ -1355,7 +2135,25 @@
                  </div>
                  <div class="phone-preview-body">
                    <div class="phone-preview-title-row">
                      <strong>百杏厨房(中环天地店)</strong>
                      <strong>{{ item.level }}</strong>
                    </div>
                    <div class="phone-preview-detail">
                      <p>{{ item.detail || '请输入权益详情描述' }}</p>
                    </div>
                  </div>
                </div>
              </div>
            <div class="merchant-rights-example" v-else-if="!isParkingMerchant && (!isCarWashMerchant || form.miniProgramJumpType === 'normal')">
                <span>示例:</span>
                <div class="merchant-rights-phone-preview">
                  <div class="phone-preview-hero">
                    <img :src="rightExampleImage" alt="权益示例" />
                    <span class="phone-preview-back">‹</span>
                    <span class="phone-preview-more">•••</span>
                  </div>
                  <div class="phone-preview-body">
                    <div class="phone-preview-title-row">
                      <strong>{{ form.merchantName || '百杏厨房(中环天地店)' }}</strong>
                      <div class="phone-preview-coupon">
                        <span>券</span>
                        <b>8.8折</b>
@@ -1364,7 +2162,7 @@
                    <div class="phone-preview-tag">到店消费 | 卓越卡可用</div>
                    <div class="phone-preview-address">
                      <span>⌖</span>
                      <p>四川省攀枝花市东区炳三区新福路20号中环天地负二层</p>
                      <p>{{ form.address || '四川省攀枝花市东区炳三区新福路20号中环天地负二层' }}</p>
                      <i>➤</i>
                      <i>☎</i>
                    </div>
@@ -1380,18 +2178,59 @@
              </div>
          </div>
          <template v-if="item.consumeEnabled">
            <div class="merchant-consume-row">
              <strong>到店消费</strong>
              <el-switch v-model="item.storeConsume" />
              <span>折扣</span>
              <el-input v-model="item.discount" placeholder="请输入" />
              <span>折</span>
              <strong>是否政府补贴</strong>
              <el-switch v-model="item.governmentSubsidy" />
            </div>
          <template v-if="item.consumeEnabled && !isApartmentMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && (!isCarWashMerchant || form.miniProgramJumpType === 'normal') && (!isParkingMerchant || item.parkingType === 'paid')">
            <template v-if="isHotelMerchant">
              <div class="merchant-consume-row hotel-voucher-row">
                <strong>免费券</strong>
                <el-switch v-model="item.freeVoucherEnabled" />
                <el-input v-model="item.freeVoucherTimes" placeholder="请输入" class="hotel-times-input" />
                <span>次</span>
                <span class="hotel-margin-left">重置周期</span>
                <el-select v-model="item.freeVoucherCycle" class="hotel-cycle-select">
                  <el-option label="每天" value="每天" />
                  <el-option label="每周" value="每周" />
                  <el-option label="每月" value="每月" />
                  <el-option label="每季" value="每季" />
                  <el-option label="每年" value="每年" />
                </el-select>
                <strong class="hotel-margin-left">是否政府补贴</strong>
                <el-switch v-model="item.freeVoucherSubsidy" />
              </div>
            <div class="merchant-rights-detail-row consume-desc-row">
              <div class="merchant-consume-row hotel-consume-row">
                <strong>到店消费</strong>
                <el-switch v-model="item.storeConsume" />
                <div class="hotel-consume-options">
                  <div class="hotel-consume-option">
                    <el-radio v-model="item.consumeType" label="折扣">折扣</el-radio>
                    <el-input v-model="item.consumeDiscount" placeholder="请输入" />
                    <span>折</span>
                    <strong class="hotel-margin-left">是否政府补贴</strong>
                    <el-switch v-model="item.consumeDiscountSubsidy" />
                  </div>
                  <div class="hotel-consume-option">
                    <el-radio v-model="item.consumeType" label="优惠">优惠</el-radio>
                    <el-input v-model="item.consumeCoupon" placeholder="请输入" />
                    <span>元</span>
                    <strong class="hotel-margin-left">是否政府补贴</strong>
                    <el-switch v-model="item.consumeCouponSubsidy" />
                  </div>
                </div>
              </div>
            </template>
            <template v-else>
              <div class="merchant-consume-row">
                <strong>到店消费</strong>
                <el-switch v-model="item.storeConsume" />
                <span>折扣</span>
                <el-input v-model="item.discount" placeholder="请输入" />
                <span>折</span>
                <strong>是否政府补贴</strong>
                <el-switch v-model="item.governmentSubsidy" />
              </div>
            </template>
            <div v-if="!isParkingMerchant" class="merchant-rights-detail-row consume-desc-row">
              <label>到店消费使用描述</label>
              <el-input v-model="item.consumeDesc" type="textarea" :rows="4" resize="none" placeholder="请输入" />
              <div class="merchant-rights-example">
@@ -1428,7 +2267,109 @@
                </div>
              </div>
            </div>
            <div v-if="isParkingMerchant || isChildcareMerchant" class="merchant-rights-detail-row consume-desc-row">
              <label>到店消费使用描述</label>
              <el-input v-model="item.consumeDesc" type="textarea" :rows="4" resize="none" placeholder="请输入" />
            </div>
          </template>
        </div>
        <div v-if="isParkingMerchant" class="merchant-rights-parking-address-panel" style="padding: 0 100px;">
          <div class="parking-address-header">停车场地址</div>
          <div class="parking-address-table-wrapper">
            <el-table :data="parkingAddresses" border>
              <el-table-column label="名称">
                <template #default="{ row }"><el-input v-model="row.name" placeholder="请输入" /></template>
              </el-table-column>
              <el-table-column label="地址">
                <template #default="{ row }"><el-input v-model="row.address" placeholder="请输入" /></template>
              </el-table-column>
              <el-table-column label="定位标点" width="120" align="center">
                <template #default="{ row }">
                  <el-button type="primary" size="small" @click="markParkingLocation(row)">{{ row.latitude ? '已标点' : '未标点' }}</el-button>
                </template>
              </el-table-column>
              <el-table-column label="操作" width="100" align="center">
                <template #default="{ $index }">
                  <el-button link type="danger" @click="removeParkingAddress($index)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <div style="text-align: center; margin-top: 16px;">
              <el-button plain @click="addParkingAddress">新增</el-button>
            </div>
          </div>
        </div>
        <div v-if="isChildcareMerchant" class="merchant-rights-parking-address-panel" style="padding: 0 100px;">
          <div class="parking-address-header">学校地址</div>
          <div class="parking-address-table-wrapper">
            <el-table :data="schoolAddresses" border>
              <el-table-column label="名称" prop="name" />
              <el-table-column label="地址" prop="address" />
              <el-table-column label="操作" width="120" align="center">
                <template #default="{ $index }">
                  <el-button link type="danger" @click="removeSchool($index)">删除</el-button>
                  <el-button link type="primary" @click="openSchoolDialog($index)">详情</el-button>
                </template>
              </el-table-column>
            </el-table>
            <div style="text-align: center; margin-top: 16px;">
              <el-button plain @click="openSchoolDialog(-1)">新增</el-button>
            </div>
          </div>
        </div>
        <div v-if="isApprovalMerchant" class="merchant-rights-parking-address-panel" style="padding: 0 32px;">
          <div class="parking-address-header">商家权益</div>
          <div class="parking-address-table-wrapper">
            <el-table :data="approvalItems" border>
              <el-table-column label="名称" min-width="120">
                <template #default="{ row }">
                  <el-input v-if="row.isNew || row.isEditing" v-model="row.name" placeholder="请输入" />
                  <span v-else>{{ row.name }}</span>
                </template>
              </el-table-column>
              <el-table-column label="权益内容" min-width="220">
                <template #default="{ row }">
                  <el-input v-if="row.isNew || row.isEditing" v-model="row.content" type="textarea" :rows="3" resize="none" placeholder="请输入" />
                  <span v-else>{{ row.content }}</span>
                </template>
              </el-table-column>
              <el-table-column label="地址" min-width="140">
                <template #default="{ row }">
                  <el-input v-if="row.isNew || row.isEditing" v-model="row.address" placeholder="请输入" />
                  <span v-else>{{ row.address }}</span>
                </template>
              </el-table-column>
              <el-table-column label="电话" min-width="100">
                <template #default="{ row }">
                  <el-input v-if="row.isNew || row.isEditing" v-model="row.phone" placeholder="请输入" />
                  <span v-else>{{ row.phone }}</span>
                </template>
              </el-table-column>
              <el-table-column label="适用人才" min-width="180">
                <template #default="{ row }">
                  <el-checkbox-group v-model="row.talentLevels" :disabled="!row.isNew && !row.isEditing">
                    <el-checkbox label="卓越人才卡" />
                    <el-checkbox label="杰出人才卡" />
                    <el-checkbox label="菁英人才卡" />
                  </el-checkbox-group>
                </template>
              </el-table-column>
              <el-table-column label="操作" min-width="160" align="center">
                <template #default="{ $index, row }">
                  <el-button v-if="row.isNew || row.isEditing" link type="primary" @click="saveApprovalRow($index)">保存</el-button>
                  <el-button v-else link type="primary" @click="editApprovalRow($index)">编辑</el-button>
                  <el-button link type="danger" @click="removeApproval($index)">删除</el-button>
                  <el-button link type="primary" @click="uploadApprovalWindow($index)">{{ row.windowInfoUrl ? '重新上传窗口信息' : '上传窗口信息' }}</el-button>
                </template>
              </el-table-column>
            </el-table>
            <div style="text-align: right; margin-top: 16px;">
              <el-button plain @click="addApprovalRow">新增</el-button>
            </div>
          </div>
        </div>
      </section>
@@ -1490,7 +2431,7 @@
            <el-table-column label="商品金额" min-width="90">
              <template #default="{ row }">{{ productAmountText(row) }}</template>
            </el-table-column>
            <el-table-column prop="salesCount" label="销量" min-width="90">
            <el-table-column v-if="!isHotelMerchant && !isHealthCheckMerchant" prop="salesCount" label="销量" min-width="90">
              <template #default="{ row }">{{ row.salesCount || '' }}</template>
            </el-table-column>
            <el-table-column label="修改时间" min-width="90">
@@ -1559,7 +2500,7 @@
                <el-button type="primary" :loading="imageUploading" @click="changeCover('productCover')">更换封面</el-button>
              </div>
              <div class="product-image-row compact">
              <div v-if="!isHealthCheckMerchant" class="product-image-row compact">
                <label>商品图片</label>
                <div class="product-image-list">
                  <div v-for="(image, index) in productImages" :key="image" class="product-thumb-preview">
@@ -1572,32 +2513,107 @@
                </div>
                <el-button type="primary" :loading="imageUploading" @click="changeCover('productImage')">添加图片</el-button>
              </div>
              <div v-if="isHealthCheckMerchant" class="product-form-row health-check-time-row" style="margin-top: 24px;">
                <label>可预约时间</label>
                <el-date-picker
                  v-model="productValidPeriodRange"
                  type="daterange"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  range-separator="—"
                  format="YYYY/MM/DD"
                  value-format="YYYY-MM-DD"
                />
              </div>
            </div>
            <div class="product-info-middle">
              <div class="product-form-row amount">
            <div v-if="!isHealthCheckMerchant" class="product-info-middle">
              <div class="product-form-row amount" :class="{ 'hotel-amount': isHotelMerchant }">
                <label>商品金额</label>
                <el-input-number
                  v-model="productForm.salePrice"
                  :min="0.01"
                  :step="0.01"
                  :precision="2"
                  :controls="false"
                  placeholder="请输入"
                />
                <template v-if="isHotelMerchant">
                  <el-input-number
                    v-model="productForm.salePrice"
                    :min="0.01"
                    :step="0.01"
                    :precision="2"
                    :controls="false"
                    placeholder=""
                  />
                  <span class="amount-separator">-</span>
                  <el-input-number
                    v-model="productForm.originalPrice"
                    :min="0.01"
                    :step="0.01"
                    :precision="2"
                    :controls="false"
                    placeholder=""
                  />
                </template>
                <template v-else>
                  <el-input-number
                    v-model="productForm.salePrice"
                    :min="0.01"
                    :step="0.01"
                    :precision="2"
                    :controls="false"
                    placeholder="请输入"
                  />
                </template>
                <span>元</span>
              </div>
            </div>
            <div class="product-info-right" v-if="isEditMode && (isHotelMerchant || isHealthCheckMerchant)">
              <div class="product-form-row">
                <label>创建时间</label>
                <span>{{ formatDisplayTime(productForm.createdAt) }}</span>
              </div>
              <div class="product-form-row">
                <label>修改时间</label>
                <span>{{ formatDisplayTime(productForm.updatedAt) }}</span>
              </div>
            </div>
          </div>
          <div class="product-detail-row">
          <div v-if="!isHealthCheckMerchant" class="product-detail-row">
            <label>商品详情</label>
            <el-input v-model="productForm.detailText" type="textarea" :rows="8" resize="none" placeholder="请输入" />
          </div>
          <div v-if="isHealthCheckMerchant" class="product-detail-row health-check-table-row">
            <label>商品详情</label>
            <div style="flex: 1; display: flex; flex-direction: column; gap: 12px;">
              <el-table :data="healthCheckItems" border>
                <el-table-column label="检查项目">
                  <template #default="{ row }"><el-input v-model="row.name" placeholder="请输入" /></template>
                </el-table-column>
                <el-table-column label="项目意义">
                  <template #default="{ row }"><el-input v-model="row.meaning" placeholder="请输入" /></template>
                </el-table-column>
                <el-table-column label="男/女" width="160">
                  <template #default="{ row }">
                    <el-checkbox-group v-model="row.gender">
                      <el-checkbox label="男">男</el-checkbox>
                      <el-checkbox label="女">女</el-checkbox>
                    </el-checkbox-group>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="80" align="center">
                  <template #default="{ $index }">
                    <el-button link type="primary" @click="removeHealthCheckItem($index)">删除</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <div style="text-align: right;">
                <el-button type="primary" @click="addHealthCheckItem">新增</el-button>
              </div>
            </div>
          </div>
        </section>
        <section class="merchant-product-form-panel notice">
        <section v-if="!isHealthCheckMerchant" class="merchant-product-form-panel notice">
          <h3>购买须知设置(非必填)</h3>
          <div class="product-notice-grid">
            <div class="product-notice-field">
@@ -1634,7 +2650,7 @@
          </div>
        </section>
        <section class="merchant-product-form-panel rights">
        <section v-if="!isHotelMerchant && !isHealthCheckMerchant" class="merchant-product-form-panel rights">
          <h3>人才权益</h3>
          <div class="product-rights-list">
            <div v-for="item in productRights" :key="item.level" class="product-right-row">
@@ -1903,5 +2919,36 @@
    </template>
    <section v-else class="merchant-create-panel merchant-create-empty-panel" />
    <el-dialog v-model="schoolDialogVisible" :title="editingSchoolIndex >= 0 ? '编辑学校' : '新增学校'" width="820px" destroy-on-close>
      <el-form label-width="100px" style="padding: 20px 20px 0;">
        <el-form-item label="学校名称">
          <el-input v-model="editingSchool.name" placeholder="请输入" style="width: 320px" />
        </el-form-item>
        <el-form-item label="地址">
          <el-input v-model="editingSchool.address" placeholder="请输入" style="width: 320px" />
        </el-form-item>
        <el-form-item label="定位点">
          <el-button type="primary" size="small" @click="markSchoolLocation">{{ editingSchool.latitude ? '已标点' : '未标点' }}</el-button>
        </el-form-item>
        <el-form-item label="学校详情">
          <el-input v-model="editingSchool.detail" type="textarea" :rows="12" placeholder="请输入学校详情..." />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="schoolDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveSchool">保存</el-button>
      </template>
    </el-dialog>
    <el-dialog v-model="locationDialogVisible" title="选择地址" width="820px" destroy-on-close @closed="closeLocationMap">
      <div class="merchant-location-picker">
        <div class="merchant-map-search"><el-input v-model="locationSearch" placeholder="搜索商家地址或地点" clearable @keyup.enter="searchMapLocation" /><el-button type="primary" :loading="locationSearching" @click="searchMapLocation">搜索</el-button></div>
        <div v-if="locationSearchResults.length" class="merchant-map-results"><button v-for="item in locationSearchResults" :key="`${item.longitude}-${item.latitude}`" type="button" @click="selectSearchLocation(item)">{{ item.name }}</button></div>
        <div ref="mapElement" class="merchant-location-map" />
        <div class="merchant-location-coordinates">已选坐标:{{ pendingLongitude || '-' }}, {{ pendingLatitude || '-' }}<span>可搜索地点或直接点击地图放置标点</span></div>
      </div>
      <template #footer><el-button @click="locationDialogVisible = false">取消</el-button><el-button type="primary" @click="saveLocation">确认</el-button></template>
    </el-dialog>
  </div>
</template>
talent-card-admin/src/views/MerchantView.vue
@@ -15,23 +15,48 @@
const merchants = ref<Merchant[]>([])
const selectedCreateType = ref('餐饮服务')
const createMerchantTypes = ['餐饮服务', '电影院', '大剧院', '酒店服务', '景区服务']
const merchantTypes = [
  '餐饮服务',
  '电影院',
  '大剧院',
  '酒店服务',
const createMerchantTypes = [
  '免费体检',
  '就医绿色通道',
  '配偶推荐就业',
  '金融服务',
  '学生托管',
  '公寓租赁服务',
  '行政审批',
  '子女托管',
  '餐饮服务',
  '酒店服务',
  '公寓租赁',
  '加油服务',
  '停车服务',
  '洗车服务',
  '自助洗车',
  '景区服务',
  '购房优惠',
  '购车优惠',
  '购车服务',
  '家电服务',
  '数码服务',
  '文化服务',
  '电信服务',
  '口腔治疗保健',
  '观影服务',
]
const merchantTypes = [
  '免费体检',
  '配偶推荐就业',
  '金融服务',
  '行政审批',
  '子女托管',
  '餐饮服务',
  '酒店服务',
  '公寓租赁',
  '加油服务',
  '停车服务',
  '自助洗车',
  '景区服务',
  '购房优惠',
  '购车服务',
  '家电服务',
  '数码服务',
  '文化服务',
  '电信服务',
  '口腔治疗保健',
  '观影服务',
]