| | |
| | | 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() |
| | |
| | | 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' |
| | |
| | | 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 === '景区服务' |
| | |
| | | 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: '', |
| | |
| | | groupBuyingEnabled: 0, |
| | | usageInstructions: '', |
| | | rightsText: '', |
| | | miniProgramJumpType: 'miniprogram', |
| | | miniProgramJumpUrl: '', |
| | | serviceLabel: '', |
| | | recommend: defaultMerchantRecommend(), |
| | | sortNo: undefined, |
| | |
| | | total: 0, |
| | | }) |
| | | |
| | | const productForm = reactive<ProductPayload>({ |
| | | const productForm = reactive<ProductPayload & { createdAt?: string, updatedAt?: string }>({ |
| | | merchantId: queryString('merchantId'), |
| | | productType: 'GROUP', |
| | | productName: '', |
| | |
| | | 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: '', |
| | |
| | | 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: '卓越人才卡', |
| | |
| | | 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: [] }) |
| | |
| | | } |
| | | |
| | | async function markLocation() { |
| | | activeParkingAddress.value = undefined |
| | | activeSchoolAddress.value = undefined |
| | | pendingLongitude.value = String(form.longitude || '') |
| | | pendingLatitude.value = String(form.latitude || '') |
| | | locationDialogVisible.value = true |
| | |
| | | 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() |
| | | } |
| | |
| | | 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() |
| | |
| | | } catch { |
| | | // The shared HTTP interceptor displays the upload error. |
| | | } finally { |
| | | approvalUploadIndex.value = -1 |
| | | imageUploading.value = false |
| | | } |
| | | } |
| | |
| | | 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 |
| | | } |
| | | })) |
| | | } |
| | | |
| | |
| | | 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 |
| | |
| | | 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, |
| | |
| | | } |
| | | |
| | | 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$/, '') |
| | |
| | | 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) |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function openProductCreate() { |
| | | if (!currentMerchantId()) { |
| | | ElMessage.warning('请先保存商家信息') |
| | | return |
| | | } |
| | | // if (!currentMerchantId()) { |
| | | // ElMessage.warning('请先保存商家信息') |
| | | // return |
| | | // } |
| | | resetProductForm() |
| | | productMode.value = 'form' |
| | | } |
| | |
| | | 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), |
| | |
| | | ElMessage.warning('请上传商家封面图') |
| | | return |
| | | } |
| | | if (!hasValidBusinessHours()) { |
| | | if (!isApprovalMerchant.value && !hasValidBusinessHours()) { |
| | | ElMessage.warning('请选择有效的营业时间') |
| | | return |
| | | } |
| | |
| | | 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 = { |
| | |
| | | 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, |
| | |
| | | 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) => { |
| | |
| | | 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) |
| | | }) |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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 ? '登录账号已存在,请更换' : ''"> |
| | |
| | | |
| | | <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" /> |
| | |
| | | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | </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"> |
| | |
| | | </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> |
| | | |
| | |
| | | <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"> |
| | |
| | | <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"> |
| | |
| | | </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"> |
| | |
| | | </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"> |
| | |
| | | </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> |