From 0a1dcb3977f3b4f0c19a4b09e16e1b6c2fda292e Mon Sep 17 00:00:00 2001 From: ZhuRui Date: Tue, 4 Aug 2026 17:01:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=84=E7=90=86=20banner=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/banner/types.ts | 6 +- src/config/mock/bannerList.ts | 66 +- .../banner/components/BannerFormModal.tsx | 785 +++++++++++------- src/pages/events/banner/model/services.ts | 10 +- .../events/banner/model/useBannerModel.ts | 33 +- src/pages/events/orders/index.tsx | 2 +- src/pages/events/users/index.tsx | 28 +- src/pages/events/users/model/services.ts | 57 +- src/pages/events/users/model/useUserModel.ts | 45 +- src/pages/finance/wallet/model/services.ts | 4 +- src/pages/finance/withdraw/model/services.ts | 6 +- src/utils/areaData.ts | 70 ++ 12 files changed, 699 insertions(+), 413 deletions(-) diff --git a/src/api/banner/types.ts b/src/api/banner/types.ts index b9946de..d95d867 100644 --- a/src/api/banner/types.ts +++ b/src/api/banner/types.ts @@ -8,12 +8,12 @@ export interface TournamentAdminBannerListVO { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; status: string; - createTime: string; + createDate: string; } /** Banner 列表查询参数 */ @@ -28,7 +28,7 @@ export interface BannerSaveParams { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; diff --git a/src/config/mock/bannerList.ts b/src/config/mock/bannerList.ts index ea7a532..cca6e42 100644 --- a/src/config/mock/bannerList.ts +++ b/src/config/mock/bannerList.ts @@ -13,12 +13,12 @@ export interface MockBannerVO { img: string; title: string; type: string; - linkUrl: string; + params: string; area: string; showTimeBegin: string; showTimeEnd: string; status: string; - createTime: string; + createDate: string; } export const MOCK_BANNER_LIST: MockBannerVO[] = [ @@ -27,143 +27,143 @@ export const MOCK_BANNER_LIST: MockBannerVO[] = [ img: 'https://picsum.photos/seed/banner1/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', type: '1', - linkUrl: '', - area: '广东省深圳市', + params: 'EV20260701001', + area: '深圳市', showTimeBegin: '2026-08-01 00:00', showTimeEnd: '2026-08-31 23:59', status: '1', - createTime: '2026-07-20 08:00:00', + createDate: '2026-07-20 08:00:00', }, { id: '2', img: 'https://picsum.photos/seed/banner2/80/44', title: '马拉松城市联赛限时优惠', type: '2', - linkUrl: 'https://example.com/marathon', - area: '浙江省杭州市', + params: 'https://example.com/marathon', + area: '杭州市', showTimeBegin: '2026-08-15 00:00', showTimeEnd: '2026-09-15 23:59', status: '1', - createTime: '2026-07-19 11:30:00', + createDate: '2026-07-19 11:30:00', }, { id: '3', img: 'https://picsum.photos/seed/banner3/80/44', title: '健身运动会专属福利', type: '1', - linkUrl: '', + params: 'EV20260701003', area: '北京市', showTimeBegin: '2026-07-01 00:00', showTimeEnd: '2026-07-30 23:59', status: '1', - createTime: '2026-07-18 14:20:00', + createDate: '2026-07-18 14:20:00', }, { id: '4', img: 'https://picsum.photos/seed/banner4/80/44', title: '平台品牌宣传活动', type: '2', - linkUrl: 'https://example.com/promo', + params: 'https://example.com/promo', area: '上海市', showTimeBegin: '2026-06-01 00:00', showTimeEnd: '2026-06-30 23:59', status: '0', - createTime: '2026-07-17 18:05:00', + createDate: '2026-07-17 18:05:00', }, { id: '5', img: 'https://picsum.photos/seed/banner5/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', type: '1', - linkUrl: '', - area: '广东省深圳市', + params: 'EV20260701005', + area: '深圳市', showTimeBegin: '2026-09-01 00:00', showTimeEnd: '2026-09-30 23:59', status: '1', - createTime: '2026-07-16 21:15:00', + createDate: '2026-07-16 21:15:00', }, { id: '6', img: 'https://picsum.photos/seed/banner6/80/44', title: '马拉松城市联赛限时优惠', type: '3', - linkUrl: '/pages/activity/detail', - area: '浙江省杭州市', + params: '/pages/activity/detail', + area: '杭州市', showTimeBegin: '2026-10-01 00:00', showTimeEnd: '2026-10-31 23:59', status: '1', - createTime: '2026-07-15 00:30:00', + createDate: '2026-07-15 00:30:00', }, { id: '7', img: 'https://picsum.photos/seed/banner7/80/44', title: '全民健身日特别活动', type: '1', - linkUrl: '', + params: 'EV20260701007', area: '北京市', showTimeBegin: '2026-08-08 00:00', showTimeEnd: '2026-08-08 23:59', status: '1', - createTime: '2026-07-14 03:50:00', + createDate: '2026-07-14 03:50:00', }, { id: '8', img: 'https://picsum.photos/seed/banner8/80/44', title: '新春运动季大促销', type: '2', - linkUrl: 'https://example.com/spring', + params: 'https://example.com/spring', area: '上海市', showTimeBegin: '2026-01-15 00:00', showTimeEnd: '2026-02-15 23:59', status: '0', - createTime: '2026-07-13 07:05:00', + createDate: '2026-07-13 07:05:00', }, { id: '9', img: 'https://picsum.photos/seed/banner9/80/44', title: '2026全国青少年羽毛球锦标赛火热报名中', - type: '1', - linkUrl: '', - area: '广东省深圳市', + type: '0', + params: '', + area: '全国', showTimeBegin: '2026-08-01 00:00', showTimeEnd: '2026-08-31 23:59', status: '1', - createTime: '2026-07-12 10:25:00', + createDate: '2026-07-12 10:25:00', }, { id: '10', img: 'https://picsum.photos/seed/banner10/80/44', title: '平台品牌宣传活动', type: '2', - linkUrl: 'https://example.com/brand', - area: '浙江省杭州市', + params: 'https://example.com/brand', + area: '杭州市', showTimeBegin: '2026-11-01 00:00', showTimeEnd: '2026-11-30 23:59', status: '1', - createTime: '2026-07-11 13:40:00', + createDate: '2026-07-11 13:40:00', }, { id: '11', img: 'https://picsum.photos/seed/banner11/80/44', title: '秋季健身挑战赛', type: '1', - linkUrl: '', + params: 'EV20260701011', area: '北京市', showTimeBegin: '2026-09-01 00:00', showTimeEnd: '2026-09-30 23:59', status: '1', - createTime: '2026-07-10 17:00:00', + createDate: '2026-07-10 17:00:00', }, { id: '12', img: 'https://picsum.photos/seed/banner12/80/44', title: '双十一运动装备大促', type: '2', - linkUrl: 'https://example.com/double11', + params: 'https://example.com/double11', area: '上海市', showTimeBegin: '2026-11-01 00:00', showTimeEnd: '2026-11-11 23:59', status: '1', - createTime: '2026-07-09 20:15:00', + createDate: '2026-07-09 20:15:00', }, ]; diff --git a/src/pages/events/banner/components/BannerFormModal.tsx b/src/pages/events/banner/components/BannerFormModal.tsx index 720971a..dc329fd 100644 --- a/src/pages/events/banner/components/BannerFormModal.tsx +++ b/src/pages/events/banner/components/BannerFormModal.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref, reactive } from 'vue'; +import { defineComponent, ref, reactive, computed } from 'vue'; import { useEffect, useState } from '@/hooks'; import { Modal, @@ -12,30 +12,30 @@ import { message, Spin, } from 'ant-design-vue'; -import { JUMP_TYPE_OPTIONS, EVENT_OPTIONS } from '../model/useBannerModel'; -import { - getProvinceCityCascaderOptions, - parseAreaToCascaderPath, - formatCascaderArea, -} from '@/utils/areaData'; +import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel'; +import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData'; import { ImageCropper } from '@yp-component/root'; import { FileImageOutlined } from '@ant-design/icons-vue'; import { uploadFile, OssUploadType } from '@/utils/oss'; +import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services'; import dayjs from 'dayjs'; import styles from './BannerFormModal.module.less'; -/** 省市两级 Cascader 选项 */ -const cascaderAreaOptions = getProvinceCityCascaderOptions(); +/** 所属区域 Cascader 选项:全国 + 省市两级 */ +const areaCascaderOptions: any[] = [ + { value: '全国', label: '全国' }, + ...getProvinceCityCascaderOptions(), +]; /** 默认表单数据 */ const getDefaultForm = () => ({ title: '', imageUrl: '', imageStatus: 'empty' as 'empty' | 'uploading' | 'success' | 'error', - type: '1' as '1' | '2' | '3', + type: '1' as '1' | '2' | '3' | '0', linkUrl: '', relatedEventId: undefined as string | undefined, - area: '', + area: '全国', displayTimeRange: null as [string, string] | null, sort: 1, }); @@ -47,8 +47,6 @@ const URL_MAX = 500; /** 图片上传限制:最大 10MB */ const IMAGE_MAX_SIZE = 10 * 1024 * 1024; -/** 裁剪比例:1053:351 = 3:1 */ -const CROP_ASPECT_RATIO = 1053 / 351; interface BannerFormModalProps { visible: boolean; @@ -72,6 +70,37 @@ function base64ToFile(dataUrl: string, fileName: string): File { return new File([u8arr], fileName, { type: mime }); } +// ============================================================ +// 工具函数 +// ============================================================ + +/** 从 Cascader 路径中提取城市名(最后一级),用于区域筛选 */ +function extractCityName(path: string[]): string { + if (!path || path.length === 0 || path[0] === '全国') return ''; + return path[path.length - 1]; +} + +/** 从 Cascader 路径中提取存储用的区域字符串 */ +function extractAreaValue(path: string[]): string { + if (!path || path.length === 0) return '全国'; + if (path[0] === '全国') return '全国'; + // 直辖市路径如 ["北京市"],普通城市如 ["广东省", "深圳市"] + return path[path.length - 1]; +} + +/** 根据跳转类型计算 parms 字段的值 */ +function buildParams(type: string, relatedEventId: string | undefined, linkUrl: string): string { + if (type === '1') return relatedEventId ?? ''; + if (type === '2') return linkUrl || ''; + if (type === '3') return linkUrl || ''; + // type === '0' 无链接 + return ''; +} + +// ============================================================ +// 组件 +// ============================================================ + /** * Banner 新增/编辑表单弹窗 */ @@ -88,36 +117,165 @@ export default defineComponent({ const formRef = ref(); const fileInputRef = ref(null); const formData = reactive(getDefaultForm()); - /** Cascader 区域的路径数组(与 formData.area 字符串双向同步) */ - const areaPath = ref([]); - /** 是否由用户主动切换了跳转类型(跳过初始加载的 type 联动校验) */ + + /** Cascader 区域的路径数组:如 ["全国"]、["广东省", "深圳市"] */ + const areaPath = ref(['全国']); + + /** 是否由用户主动切换了跳转类型 */ const typeChanged = ref(false); - /** 裁剪弹窗状态 */ + // ---- 裁剪弹窗状态 ---- const [cropperVisible, setCropperVisible] = useState(false); - /** 待裁剪图片的本地预览 URL */ const [uploadImageUrl, setUploadImageUrl] = useState(''); - /** 根据 record 初始化表单 */ + // ---- 赛事搜索状态 ---- + const eventKeyword = ref(''); + const [eventOptions, setEventOptions] = useState<{ value: string; label: string }[]>([]); + const [eventSearchLoading, setEventSearchLoading] = useState(false); + /** 编辑时加载赛事详情的独立 loading */ + const [editLoading, setEditLoading] = useState(false); + /** 手动防抖计时器 */ + let searchTimer: ReturnType | null = null; + + /** 赛事下拉选项:第一项固定为"不关联" */ + const eventSelectOptions = computed(() => { + return [{ value: '', label: '不关联' }, ...eventOptions.value]; + }); + + // ---- 赛事搜索 API 调用 ---- + const doSearch = async (keyword: string) => { + if (!keyword.trim()) { + setEventOptions([]); + return; + } + setEventSearchLoading(true); + try { + const cityName = extractCityName(areaPath.value); + const params: Record = { + page: '1', + limit: '999', + name: keyword.trim(), + }; + if (cityName) { + params.areaName = cityName; + } + const res = await getEventList(params as any); + if (res.code == 200) { + const list: TournamentAdminVO[] = res.data?.list || []; + setEventOptions( + list.map((item) => ({ + value: item.id, + label: item.name, + })), + ); + } else { + setEventOptions([]); + } + } catch (e) { + console.error('[Banner] 搜索赛事失败:', e); + setEventOptions([]); + } finally { + setEventSearchLoading(false); + } + }; + + /** 赛事搜索输入(带 350ms 手动防抖) */ + const onEventSearch = (value: string) => { + eventKeyword.value = value; + if (searchTimer) clearTimeout(searchTimer); + if (!value.trim()) { + setEventOptions([]); + return; + } + searchTimer = setTimeout(() => { + doSearch(value); + }, 350); + }; + + /** 所属区域 Cascader 变化 */ + const onAreaChange = (val: any) => { + const path: string[] = val || []; + const newArea = extractAreaValue(path); + areaPath.value = path; + formData.area = newArea; + + // 切换到非"全国"区域时清空已关联赛事 + if (newArea !== '全国') { + formData.relatedEventId = undefined; + formRef.value?.clearValidate('relatedEventId'); + } + + // 区域变化时重新搜索(如果当前有关键词) + if (eventKeyword.value.trim()) { + if (searchTimer) clearTimeout(searchTimer); + searchTimer = setTimeout(() => { + doSearch(eventKeyword.value); + }, 0); + } + }; + + // ---- 根据 record 初始化表单 ---- const initFormFromRecord = (record: any) => { const fresh = getDefaultForm(); if (!record) { Object.assign(formData, fresh); - areaPath.value = []; + areaPath.value = ['全国']; + eventKeyword.value = ''; + setEventOptions([]); return; } - const area = record.area || record.region || ''; - areaPath.value = parseAreaToCascaderPath(area); + // 区域:兼容旧格式 "广东省-深圳市"、新格式 "深圳市"、"全国" + const rawArea = record.area || record.region || ''; + let cityName: string; + if (rawArea.includes('-')) { + cityName = rawArea.split('-').pop() || '全国'; + } else { + cityName = rawArea || '全国'; + } + areaPath.value = cityNameToCascaderPath(cityName); + + // 跳转类型 + const type = String(record.type || record.jumpType || '1'); + + // 关联赛事ID(编辑时从 params/linkUrl 读取) + let relatedEventId: string | undefined; + if (type === '1') { + const eventId = record.params || record.linkUrl || record.jumpUrl || ''; + if (eventId) { + relatedEventId = eventId; + // 异步获取赛事名称用于回显 + setEditLoading(true); + getEventDetail(eventId) + .then((detailRes) => { + if (detailRes.code == 200) { + const eventName = detailRes.data?.name || eventId; + setEventOptions([{ value: eventId, label: eventName }]); + } + }) + .catch(() => { + setEventOptions([{ value: eventId, label: eventId }]); + }) + .finally(() => { + setEditLoading(false); + }); + } + } + + eventKeyword.value = ''; + if (type !== '1' || !relatedEventId) { + setEventOptions([]); + } + Object.assign(formData, { ...fresh, title: record.title || '', imageUrl: record.img || record.imageUrl || '', imageStatus: record.img || record.imageUrl ? ('success' as const) : ('empty' as const), - type: String(record.type || record.jumpType || '1'), - linkUrl: record.linkUrl || record.jumpUrl || '', - relatedEventId: record.relatedEventId || undefined, - area, + type, + linkUrl: record.params || record.linkUrl || record.jumpUrl || '', + relatedEventId, + area: cityName, displayTimeRange: (record.showTimeBegin || record.displayStartTime) && (record.showTimeEnd || record.displayEndTime) @@ -130,10 +288,9 @@ export default defineComponent({ }); }; - /** 监听 visible 变化重置表单 */ + // ---- 监听 visible 变化重置表单 ---- useEffect(() => { if (props.visible) { - // 标记首次渲染,跳过 type 联动校验 typeChanged.value = false; initFormFromRecord(props.record); setCropperVisible(false); @@ -142,9 +299,16 @@ export default defineComponent({ } }, [() => props.visible]); - /** 监听 type 变化时清理联动字段值和校验 */ + // ---- 清理防抖计时器 ---- + useEffect(() => { + if (!props.visible && searchTimer) { + clearTimeout(searchTimer); + searchTimer = null; + } + }, [() => props.visible]); + + // ---- 监听 type 变化,清理联动字段 ---- useEffect(() => { - // 首次加载跳过校验 if (!typeChanged.value) { if (formData.type !== '1') formData.relatedEventId = undefined; if (formData.type !== '2' && formData.type !== '3') formData.linkUrl = ''; @@ -162,25 +326,28 @@ export default defineComponent({ const activeNames: string[] = []; if (formData.type === '1') activeNames.push('relatedEventId'); if (formData.type === '2' || formData.type === '3') activeNames.push('linkUrl'); - // 清除所有联动字段的校验状态 formRef.value?.clearValidate(['relatedEventId', 'linkUrl']); - // 如果有已填值,重新校验当前字段 if (activeNames.length) { setTimeout(() => formRef.value?.validateFields(activeNames).catch(() => {}), 0); } }, [() => formData.type]); - /** 触发隐藏的文件选择器 */ + /** 重新校验 Banner 图片字段,上传完成后清除或更新错误提示 */ + const revalidateImageUrl = () => { + setTimeout(() => { + formRef.value?.validateFields(['imageUrl']).catch(() => {}); + }, 0); + }; + + // ---- 图片上传相关 ---- const triggerFilePicker = () => { if (formData.imageStatus === 'uploading') return; fileInputRef.value?.click(); }; - /** 处理文件选择 → 打开裁剪弹窗 */ const handleFileChange = (e: Event) => { const target = e.target as HTMLInputElement; const file = target.files?.[0]; - // 重置 input 以便同一文件可再次选择 target.value = ''; if (!file) return; @@ -193,21 +360,17 @@ export default defineComponent({ return; } - // 生成本地预览 URL,打开裁剪弹窗 setUploadImageUrl(URL.createObjectURL(file)); setCropperVisible(true); }; - /** 裁剪确认回调:将裁剪后的图片上传到 OSS */ const handleCropConfirm = async (dataUrl: string) => { setCropperVisible(false); - // 释放旧的本地预览 if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { URL.revokeObjectURL(formData.imageUrl); } - // 先用 base64 做本地预览 const localUrl = dataUrl; formData.imageUrl = localUrl; formData.imageStatus = 'uploading'; @@ -216,27 +379,26 @@ export default defineComponent({ const file = base64ToFile(dataUrl, `banner_${Date.now()}.jpg`); const result = await uploadFile(file, OssUploadType.Banner as any); - // 释放本地预览 URL.revokeObjectURL(localUrl); formData.imageUrl = result.url; formData.imageStatus = 'success'; message.success('图片上传成功'); + revalidateImageUrl(); } catch (err: any) { console.error('[Banner] 上传失败:', err); URL.revokeObjectURL(localUrl); formData.imageUrl = ''; formData.imageStatus = 'error'; message.error(err?.message || '图片上传失败'); + revalidateImageUrl(); } - // 清理裁剪资源 if (uploadImageUrl.value) { URL.revokeObjectURL(uploadImageUrl.value); } setUploadImageUrl(''); }; - /** 裁剪弹窗取消 */ const onCropperCancel = () => { setCropperVisible(false); if (uploadImageUrl.value) { @@ -245,16 +407,16 @@ export default defineComponent({ setUploadImageUrl(''); }; - /** 删除已上传图片 */ const handleRemoveImage = () => { if (formData.imageUrl && formData.imageUrl.startsWith('blob:')) { URL.revokeObjectURL(formData.imageUrl); } formData.imageUrl = ''; formData.imageStatus = 'empty'; + revalidateImageUrl(); }; - /** 提交表单 */ + // ---- 提交表单 ---- const handleSubmit = async () => { try { await formRef.value?.validate(); @@ -271,15 +433,8 @@ export default defineComponent({ img: formData.imageUrl || '', title: formData.title || '', type: formData.type, - linkUrl: - formData.type === '1' - ? formData.relatedEventId || '' - : formData.type === '2' - ? formData.linkUrl || '' - : formData.type === '3' - ? formData.linkUrl || '' - : '', - area: formData.area || formatCascaderArea(areaPath.value), + params: buildParams(formData.type, formData.relatedEventId, formData.linkUrl), + area: formData.area, showTimeBegin: formData.displayTimeRange?.[0] || '', showTimeEnd: formData.displayTimeRange?.[1] || '', sort: String(formData.sort ?? 1), @@ -299,246 +454,295 @@ export default defineComponent({ centered wrapClassName={styles.bannerFormModalMain} > -
- {/* ===== Banner 标题 ===== */} - - - - - {/* ===== 上传图片(必填,带裁剪) ===== */} - { - if (!formData.imageUrl || formData.imageUrl.startsWith('blob:')) { - return Promise.reject(new Error('请上传 Banner 图片')); - } - return Promise.resolve(); - }, - }, - ]} - > -
- - {formData.imageStatus === 'empty' || formData.imageStatus === 'error' ? ( -
- - - - 点击上传 - - 建议尺寸 1053*351 像素 -
- 支持 JPG/PNG,最大 10MB -
-
- ) : ( -
- - banner preview - -
- 重新上传 -
-
- )} -
-
- - {/* ===== 跳转类型 ===== */} - - - - )} - - {formData.type === '2' && ( - - - - )} - - {formData.type === '3' && ( - - - - )} - - {/* ===== 所属区域 ===== */} - - { - formData.area = formatCascaderArea(val || []); - }} - /> - - - {/* ===== 展示时间(开始/结束) ===== */} -
- - { - formData.displayTimeRange = [ - v ? v.format('YYYY-MM-DD') : '', - formData.displayTimeRange?.[1] || '', - ]; - }} - style={{ width: '100%' }} - /> - - - { - const [s, e] = formData.displayTimeRange || []; - if (s && e && new Date(e) < new Date(s)) { - return Promise.reject(new Error('结束时间不能早于开始时间')); - } - return Promise.resolve(); - }, - }, - ]} - > - { - formData.displayTimeRange = [ - formData.displayTimeRange?.[0] || '', - v ? v.format('YYYY-MM-DD') : '', - ]; - }} - style={{ width: '100%' }} - /> - + {editLoading.value ? ( +
+
+ ) : ( + <> + + {/* ===== Banner 标题 ===== */} + + + - {/* ===== 排序 ===== */} - - - - + {/* ===== 上传图片(必填,带裁剪) ===== */} + { + if (formData.imageStatus === 'uploading') { + return Promise.reject(new Error('图片仍在上传中,请稍候')); + } + if ( + formData.imageUrl?.startsWith('blob:') || + formData.imageUrl?.startsWith('data:') + ) { + return Promise.reject(new Error('请等待图片上传完成')); + } + return Promise.resolve(); + }, + }, + ]} + > +
+ + {formData.imageStatus === 'empty' || formData.imageStatus === 'error' ? ( +
+ + + + 点击上传 + + 建议尺寸 1053*351 像素 +
+ 支持 JPG/PNG,最大 10MB +
+
+ ) : ( +
+ + banner preview + +
+ 重新上传 +
+
+ )} +
+
- {/* ===== 底部按钮 ===== */} -
- - -
+ {/* ===== 跳转类型 ===== */} + + { + if (formData.relatedEventId && eventOptions.value.length === 0) { + doSearch(formData.relatedEventId); + } + }} + /> + + )} + + {/* ===== 联动字段:自定义链接 ===== */} + {formData.type === '2' && ( + + + + )} + + {/* ===== 联动字段:小程序页面 ===== */} + {formData.type === '3' && ( + + + + )} + + {/* ===== 所属区域(省市树种选择 + 全国) ===== */} + + + + + {/* ===== 展示时间(开始/结束) ===== */} +
+ { + if (!formData.displayTimeRange?.[0]) { + return Promise.reject(new Error('请选择开始时间')); + } + return Promise.resolve(); + }, + }, + ]} + > + { + const newStart = v ? v.format('YYYY-MM-DD HH:mm:ss') : ''; + const currentEnd = formData.displayTimeRange?.[1] || ''; + if (newStart && currentEnd && dayjs(newStart).isAfter(dayjs(currentEnd))) { + formData.displayTimeRange = [currentEnd, newStart]; + } else { + formData.displayTimeRange = [newStart, currentEnd]; + } + formRef.value?.clearValidate(['displayStartTime', 'displayEndTime']); + }} + style={{ width: '100%' }} + /> + + + { + if (!formData.displayTimeRange?.[1]) { + return Promise.reject(new Error('请选择结束时间')); + } + return Promise.resolve(); + }, + }, + ]} + > + { + const newEnd = v ? v.format('YYYY-MM-DD HH:mm:ss') : ''; + const currentStart = formData.displayTimeRange?.[0] || ''; + if (newEnd && currentStart && dayjs(newEnd).isBefore(dayjs(currentStart))) { + formData.displayTimeRange = [newEnd, currentStart]; + } else { + formData.displayTimeRange = [currentStart, newEnd]; + } + formRef.value?.clearValidate(['displayStartTime', 'displayEndTime']); + }} + style={{ width: '100%' }} + /> + +
+ + {/* ===== 排序 ===== */} + + + + + + {/* ===== 底部按钮 ===== */} +
+ + +
+ + )} {/* ===== 图片裁剪弹窗 ===== */} @@ -555,7 +759,6 @@ export default defineComponent({
new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/pages/events/banner/model/useBannerModel.ts b/src/pages/events/banner/model/useBannerModel.ts index 85aa6ee..229d2c5 100644 --- a/src/pages/events/banner/model/useBannerModel.ts +++ b/src/pages/events/banner/model/useBannerModel.ts @@ -11,7 +11,6 @@ import { type TournamentAdminBannerListVO, type BannerListQueryParams, } from './services'; -import { getProvinceCityOptions } from '@/utils/areaData'; // ============================================================ // 常量 @@ -24,15 +23,17 @@ export const BANNER_STATUS_OPTIONS = [ { value: '0', label: '禁用' }, ] as const; -/** 跳转类型选项(value 对应 API type: 1=赛事详情,2=自定义连接,3=小程序页面) */ +/** 跳转类型选项(value 对应 API type: 0=无链接,1=赛事详情,2=自定义连接,3=小程序页面) */ export const JUMP_TYPE_OPTIONS = [ { value: '1', label: '赛事详情页' }, { value: '2', label: '自定义链接' }, { value: '3', label: '小程序页面' }, + { value: '0', label: '无链接' }, ] as const; /** 跳转类型映射 */ const JUMP_TYPE_MAP: Record = { + '0': { label: '无链接', tone: 'default' }, '1': { label: '赛事详情页', tone: 'primary' }, '2': { label: '自定义链接', tone: 'purple' }, '3': { label: '小程序页面', tone: 'cyan' }, @@ -44,18 +45,6 @@ const STATUS_MAP: Record