diff --git a/src/views/activities/ActivityList.vue b/src/views/activities/ActivityList.vue index 2f49d4f..2485a02 100644 --- a/src/views/activities/ActivityList.vue +++ b/src/views/activities/ActivityList.vue @@ -140,8 +140,9 @@ type BookingDayRow = { /** 可选;空=不限制开始,仅需在截止前 */ booking_opens_at: string booking_deadline_at: string - day_quota: number - /** 有值时 H5 场次「人数限制」展示此处文案,否则展示名额数字 */ + /** 未填写时表格为空,提交前须填 */ + day_quota: number | undefined + /** 有值时 H5 场次「人数限制」展示此处文案,否则展示可预约人数数字 */ quota_note?: string booked_count?: number } @@ -222,104 +223,6 @@ function formatYmdInShanghai(iso: string | undefined | null): string { }).format(d) } -type TimeSlotForm = { start: string; end: string } - -function parseSpecificTimeToSlots(raw?: string | null): TimeSlotForm[] { - if (!raw?.trim()) return [] - const s = raw.trim() - if (s.startsWith('[')) { - try { - const arr = JSON.parse(s) as unknown - if (Array.isArray(arr)) { - return arr - .map((x): TimeSlotForm | null => { - if (typeof x !== 'string') return null - const p = String(x).split('-').map((t) => t.trim()) - if (p.length >= 2 && p[0] && p[1]) { - return { start: p[0].slice(0, 5), end: p[1].slice(0, 5) } - } - return null - }) - .filter((x): x is TimeSlotForm => x != null) - } - } catch { - /* 非 JSON */ - } - } - const p = s.split('-').map((t) => t.trim()) - if (p.length >= 2 && p[0] && p[1]) { - return [{ start: p[0].replace(/\s.*/, '').slice(0, 5), end: p[1].replace(/\s.*/, '').slice(0, 5) }] - } - return [] -} - -function slotsToSpecificTimePayload(slots: TimeSlotForm[]): string | null { - const parts = slots - .map((r) => { - const a = String(r.start || '').trim() - const b = String(r.end || '').trim() - if (!a || !b) return '' - return `${a}-${b}` - }) - .filter(Boolean) - if (!parts.length) return null - return JSON.stringify(parts) -} - -function activitySpecificTimeDisplay(raw?: string | null): string { - const slots = parseSpecificTimeToSlots(raw) - if (!slots.length) return String(raw || '').trim() - return slots.map((s) => `${s.start}-${s.end}`).join(',') -} - -/** 活动起止日(YYYY-MM-DD)内的每一个日历日,东八区 */ -function listActivityDateYmdsInclusive(startYmd: string, endYmd: string): string[] { - const s = String(startYmd || '').trim().slice(0, 10) - const e = String(endYmd || '').trim().slice(0, 10) - if (!/^\d{4}-\d{2}-\d{2}$/.test(s) || !/^\d{4}-\d{2}-\d{2}$/.test(e) || s > e) return [] - const fmt = new Intl.DateTimeFormat('en-CA', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - }) - const bump = (cur: string): string => { - const [y, mo, d] = cur.split('-').map(Number) - const ms = - new Date( - `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}T12:00:00+08:00`, - ).getTime() + 86400000 - return fmt.format(new Date(ms)) - } - const out: string[] = [] - let cur = s - while (cur <= e) { - out.push(cur) - if (cur === e) break - cur = bump(cur) - } - return out -} - -/** 校验 HH:mm 且结束早于同一场次日下的开始则无意义 */ -function isValidHmPair(startHm: string, endHm: string): boolean { - const p = /^(\d{1,2}):(\d{2})$/ - const a = p.exec(String(startHm || '').trim()) - const b = p.exec(String(endHm || '').trim()) - if (!a || !b) return false - const asm = (+a[1]) * 60 + (+a[2]) - const bsm = (+b[1]) * 60 + (+b[2]) - return bsm > asm && asm >= 0 && asm < 1440 && bsm <= 1439 -} - -function combineYmdAndHmToDatetime(ymd: string, hm: string): string { - const p = /^(\d{1,2}):(\d{2})$/.exec(String(hm || '').trim()) - if (!p) return `${ymd} 09:00:00` - const h = Math.min(23, Math.max(0, +p[1])) - const mi = Math.min(59, Math.max(0, +p[2])) - return `${ymd} ${String(h).padStart(2, '0')}:${String(mi).padStart(2, '0')}:00` -} - function parseShanghaiLocalToMs(datetimeYmdHmss: string): number { let s = String(datetimeYmdHmss || '').trim().replace(' ', 'T') if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s)) s += ':00' @@ -327,120 +230,6 @@ function parseShanghaiLocalToMs(datetimeYmdHmss: string): number { return new Date(`${s}+08:00`).getTime() } -function formatDatetimeShanghai(ms: number): string { - if (!Number.isFinite(ms)) return '' - const d = new Date(ms) - const p = new Intl.DateTimeFormat('en-CA', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, - }) - .formatToParts(d) - .reduce>((acc, x) => { - if (x.type !== 'literal') acc[x.type] = x.value - return acc - }, {}) - const ymd = `${p.year}-${p.month}-${p.day}` - const t = `${(p.hour || '').padStart(2, '0')}:${(p.minute || '').padStart(2, '0')}:${(p.second || '00').padStart(2, '0')}` - return `${ymd} ${t}` -} - -function buildSessionDatetimePairsFromActivityForm(): Array<{ session_start_at: string; session_end_at: string }> { - const dates = listActivityDateYmdsInclusive(form.start_at, form.end_at) - const slots = form.specific_time_slots.filter((r) => - isValidHmPair(String(r.start || '').trim(), String(r.end || '').trim()), - ) - const pairs: Array<{ session_start_at: string; session_end_at: string }> = [] - for (const ymd of dates) { - for (const sl of slots) { - pairs.push({ - session_start_at: combineYmdAndHmToDatetime(ymd, sl.start), - session_end_at: combineYmdAndHmToDatetime(ymd, sl.end), - }) - } - } - return pairs -} - -/** 场次开始调整后:预约截止不再自动填充;仅当已填写截止时与预约开始一起做规则校正 */ -function ensureOnlineBookingTimesConsistent(row: BookingDayRow): void { - if (normalizeReservationKind(form.reservation_type) !== 'online') return - const sessStartMs = parseShanghaiLocalToMs(row.session_start_at) - if (!Number.isFinite(sessStartMs)) return - const ymd = row.session_start_at.trim().slice(0, 10) - const dlRaw = String(row.booking_deadline_at || '').trim() - if (!dlRaw) { - row.booking_opens_at = `${ymd} 00:00:00` - return - } - let deadlineMs = parseShanghaiLocalToMs(dlRaw) - if (!Number.isFinite(deadlineMs)) return - deadlineMs = Math.min(deadlineMs, sessStartMs - 60 * 1000) - row.booking_deadline_at = formatDatetimeShanghai(deadlineMs) - let opensMs = parseShanghaiLocalToMs(`${ymd} 00:00:00`) - if (!Number.isFinite(opensMs) || opensMs > deadlineMs) { - opensMs = deadlineMs - 60 * 1000 - } - row.booking_opens_at = formatDatetimeShanghai(opensMs) -} - -const suppressSessionScheduleSync = ref(0) - -const MAX_AUTO_SESSION_ROWS = 200 - -/** 根据「活动日期 + 具体时间」回填各场次的开始/结束(必要时增补空场次行) */ -function syncBookingSessionTimesFromSchedule(): void { - if (!visible.value || !canSaveSessionsWithActivity.value) return - if (suppressSessionScheduleSync.value > 0) return - const pairs = buildSessionDatetimePairsFromActivityForm() - if (!pairs.length) return - - const rt = normalizeReservationKind(form.reservation_type) - while ( - pairs.length > bookingForm.days.length - && bookingForm.days.length < MAX_AUTO_SESSION_ROWS - ) { - addBookingDayRow() - } - const n = Math.min(pairs.length, bookingForm.days.length) - for (let i = 0; i < n; i++) { - const row = bookingForm.days[i] - row.session_start_at = pairs[i].session_start_at - row.session_end_at = pairs[i].session_end_at - if (rt === 'online') ensureOnlineBookingTimesConsistent(row) - } -} - -/** 与 sync 解耦的驱动签名:suppress 或关弹窗时为 __off__,避免 deep watch + 新对象导致递归 effect */ -const sessionScheduleDrivingSignature = computed(() => { - if (!visible.value || suppressSessionScheduleSync.value > 0) return '__off__' - const slotKey = form.specific_time_slots - .map((x) => `${String(x.start ?? '').trim()}|${String(x.end ?? '').trim()}`) - .join(';') - return `${form.start_at}\n${form.end_at}\n${form.reservation_type}\n${slotKey}` -}) - -let inSessionScheduleSync = false - -watch( - sessionScheduleDrivingSignature, - (sig) => { - if (sig === '__off__' || inSessionScheduleSync) return - inSessionScheduleSync = true - try { - syncBookingSessionTimesFromSchedule() - } finally { - inSessionScheduleSync = false - } - }, - { flush: 'post' }, -) - function normalizeReservationKind(t?: string | null): string { const s = String(t || '').trim() if (!s || s === 'online' || s === 'none' || s === 'paid_study') return s || 'online' @@ -467,7 +256,7 @@ function formatActivityTableDateRange(record: Activity): string { return s || e || '-' } -/** 新增/编辑弹窗场次步骤顶栏:带出当前表单活动日期与时间说明 */ +/** 新增/编辑弹窗场次步骤顶栏:带出当前表单活动日期 */ const activityBookingStepIntro = computed(() => { const r: Activity = { id: editId.value ?? 0, @@ -478,9 +267,7 @@ const activityBookingStepIntro = computed(() => { is_active: !!form.is_active, } const datePart = formatActivityTableDateRange(r) - const timePart = - activitySpecificTimeDisplay(form.specific_time).trim() || '—' - return `本次活动日期 ${datePart},具体时间 ${timePart},请配置场次,保存时将一并提交活动信息与场次。` + return `本次活动日期 ${datePart},请逐项手动填写各场次的场次开始、场次结束${normalizeReservationKind(form.reservation_type) === 'online' ? '、预约开始与预约截止' : ''}及可预约人数;保存时将一并提交活动信息与场次。` }) /** 审核弹窗内「提交人」展示文案 */ @@ -516,8 +303,6 @@ const form = reactive({ check_in_meeting_point: '', lat: undefined as number | undefined, lng: undefined as number | undefined, - specific_time: '', - specific_time_slots: [] as TimeSlotForm[], external_url: '', title: '', contact_name: '', @@ -552,9 +337,35 @@ const formErrors = reactive>({ detail_html: '', contact_name: '', contact_phone: '', - specific_time_slots: '', }) +/** openCreate/openEdit 批量赋值时不要触发「切换活动性质清空文案」逻辑 */ +const suppressReservationTypeBookingReset = ref(0) + +watch( + () => form.reservation_type, + () => { + if (!visible.value || suppressReservationTypeBookingReset.value > 0) return + const z = normalizeReservationKind(form.reservation_type) + if (z === 'online') { + form.booking_method_note = '平台预约' + form.fee_note = '免费' + formErrors.booking_method_note = '' + formErrors.fee_note = '' + } else if (z === 'none') { + form.booking_method_note = '' + form.fee_note = '免费' + formErrors.booking_method_note = '' + formErrors.fee_note = '' + } else if (z === 'paid_study') { + form.booking_method_note = '' + form.fee_note = '' + formErrors.booking_method_note = '' + formErrors.fee_note = '' + } + }, +) + const activityDateRange = computed({ get(): [string, string] | undefined { if (form.start_at && form.end_at) return [form.start_at, form.end_at] @@ -610,10 +421,13 @@ const showBookingAudienceFields = computed( ) const sessionRulesHint = computed(() => { + const groupNote = + '团体每单最少人数以及团体每单最多人数不是场次的可预约人数,仅限制每场次团体用户可预约的人数;' + const sameDay = '每场次的开始时间与结束时间须为同一天内;' if (normalizeReservationKind(form.reservation_type) === 'online') { - return '每场次的开始时间与结束时间须为同一天内;预约开始、预约截止时间须填写;预约截止时间须早于场次开始时间;预约开始不得晚于预约截止。更改名额不可低于已约人数。' + return `${groupNote}${sameDay}场次开始、场次结束、预约开始、预约截止时间须填写;预约截止时间须早于场次开始时间;预约开始不得晚于预约截止;更改可预约人数不可低于已约人数;` } - return '每场次的开始时间与结束时间须为同一天内' + return `${groupNote}${sameDay}场次开始、场次结束须填写;更改可预约人数不可低于已约人数;` }) const sessionTableScrollX = computed(() => @@ -622,46 +436,14 @@ const sessionTableScrollX = computed(() => const activityModalTitle = computed(() => (isCreate.value ? '新增活动' : '编辑活动')) -function unifiedBookingContextActivity(): Activity | null { - if (!form.start_at && !form.end_at) return null - return { - id: editId.value ?? 0, - venue_id: Number(form.venue_id) || 0, - title: form.title, - start_at: form.start_at, - end_at: form.end_at, - reservation_type: form.reservation_type, - is_active: !!form.is_active, - schedule_status: form.display_schedule_status, - } as Activity -} - -function defaultSessionTimesForActivity(act: Activity | null): { start: string; end: string } { - const d = act?.start_at - ? formatYmdInShanghai(String(act.start_at)) - : new Intl.DateTimeFormat('en-CA', { - timeZone: 'Asia/Shanghai', - year: 'numeric', - month: '2-digit', - day: '2-digit', - }).format(new Date()) - return { - start: `${d} 09:00:00`, - end: `${d} 11:00:00`, - } -} - function addBookingDayRow() { - const act = unifiedBookingContextActivity() - const { start, end } = defaultSessionTimesForActivity(act) - const dayStr = start.slice(0, 10) bookingForm.days.push({ session_name: `场次 ${bookingForm.days.length + 1}`, - session_start_at: start, - session_end_at: end, - booking_opens_at: `${dayStr} 00:00:00`, + session_start_at: '', + session_end_at: '', + booking_opens_at: '', booking_deadline_at: '', - day_quota: 1, + day_quota: undefined, quota_note: '', booked_count: 0, }) @@ -677,13 +459,52 @@ function removeBookingDayRow(index: number) { } function normalizeBookingDayTime(raw: unknown): string { - const s = String(raw ?? '').trim().replace('T', ' ') + let s = String(raw ?? '').trim().replace('T', ' ') if (!s) return '' - return s.length >= 19 ? s.slice(0, 19) : s + s = s.replace(/\.\d+Z?$/, '').trim() + const m = /^(\d{4}-\d{2}-\d{2})\s+(\d{1,2}):(\d{2})(?::\d{2})?/.exec(s) + if (m) { + const h = Math.min(23, Math.max(0, +m[2])) + const mi = Math.min(59, Math.max(0, +m[3])) + return `${m[1]} ${String(h).padStart(2, '0')}:${String(mi).padStart(2, '0')}` + } + return s.length > 16 ? s.slice(0, 16) : s +} + +function sessionDatetimeYmdPart(raw: string): string { + const m = /^(\d{4}-\d{2}-\d{2})/.exec(String(raw ?? '').trim()) + return m ? m[1] : '' +} + +/** 日历格对应本地日期的 YYYY-MM-DD(与活动日期控件一致) */ +function calendarYmdFromPickerDate(d: Date): string { + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + return `${y}-${m}-${day}` +} + +/** 场次开始/结束:仅可选择活动日期范围内(含首尾)的日历日(Arco 传入多为 Dayjs) */ +function sessionDayDisabledDate(cell?: unknown): boolean { + if (cell == null) return false + const startYmd = String(form.start_at || '').trim().slice(0, 10) + const endYmd = String(form.end_at || '').trim().slice(0, 10) + if (!/^\d{4}-\d{2}-\d{2}$/.test(startYmd) || !/^\d{4}-\d{2}-\d{2}$/.test(endYmd)) return false + + const c = cell as { format?: (f: string) => string } + let curYmd: string + if (typeof c.format === 'function') { + curYmd = c.format('YYYY-MM-DD') + } else if (cell instanceof Date) { + curYmd = calendarYmdFromPickerDate(cell) + } else { + return false + } + if (!/^\d{4}-\d{2}-\d{2}$/.test(curYmd)) return false + return curYmd < startYmd || curYmd > endYmd } async function loadBookingForEditActivity(activityId: number) { - suppressSessionScheduleSync.value += 1 activityModalBookingLoading.value = true try { const { data } = await http.get(`/activities/${activityId}/booking-settings`) @@ -696,6 +517,7 @@ async function loadBookingForEditActivity(activityId: number) { const days = Array.isArray(data?.days) ? data.days : [] bookingForm.days = days.map((d: Record) => { const nid = Number(d.id) + const dq = Number(d.day_quota) return { id: Number.isFinite(nid) && nid > 0 ? nid : undefined, session_name: String(d.session_name ?? ''), @@ -703,7 +525,7 @@ async function loadBookingForEditActivity(activityId: number) { session_end_at: normalizeBookingDayTime(d.session_end_at), booking_opens_at: normalizeBookingDayTime(d.booking_opens_at), booking_deadline_at: normalizeBookingDayTime(d.booking_deadline_at), - day_quota: Math.max(0, Number(d.day_quota) || 0), + day_quota: Number.isFinite(dq) && dq >= 1 ? dq : undefined, quota_note: String(d.quota_note ?? ''), booked_count: Math.max(0, Number(d.booked_count) || 0), } @@ -713,9 +535,6 @@ async function loadBookingForEditActivity(activityId: number) { throw e } finally { activityModalBookingLoading.value = false - nextTick(() => { - suppressSessionScheduleSync.value -= 1 - }) } } @@ -733,8 +552,34 @@ function validateBookingFormInternal(): boolean { Message.warning(`第 ${i + 1} 行请填写场次名称`) return false } - if (!d.session_start_at || !d.session_end_at) { - Message.warning(`第 ${i + 1} 行请填写场次开始与结束时间`) + const startN = normalizeBookingDayTime(d.session_start_at) + const endN = normalizeBookingDayTime(d.session_end_at) + if (!String(startN).trim() || !String(endN).trim()) { + Message.warning(`第 ${i + 1} 行请填写场次开始与场次结束时间`) + return false + } + const yStart = sessionDatetimeYmdPart(startN) + const yEnd = sessionDatetimeYmdPart(endN) + if (!yStart || !yEnd || yStart !== yEnd) { + Message.warning(`第 ${i + 1} 行场次开始与场次结束须为同一天内`) + return false + } + const actStart = String(form.start_at || '').trim().slice(0, 10) + const actEnd = String(form.end_at || '').trim().slice(0, 10) + if (/^\d{4}-\d{2}-\d{2}$/.test(actStart) && /^\d{4}-\d{2}-\d{2}$/.test(actEnd)) { + if (yStart < actStart || yStart > actEnd) { + Message.warning(`第 ${i + 1} 行场次日期须落在活动日期 ${actStart}~${actEnd} 范围内`) + return false + } + } + const sm = parseShanghaiLocalToMs(startN) + const em = parseShanghaiLocalToMs(endN) + if (!Number.isFinite(sm) || !Number.isFinite(em)) { + Message.warning(`第 ${i + 1} 行场次开始或场次结束时间格式无效`) + return false + } + if (em <= sm) { + Message.warning(`第 ${i + 1} 行场次结束时间须晚于场次开始时间`) return false } if (rt === 'online' && !String(d.booking_deadline_at || '').trim()) { @@ -745,11 +590,35 @@ function validateBookingFormInternal(): boolean { Message.warning(`第 ${i + 1} 行请填写预约开始时间`) return false } + if (rt === 'online') { + const opensN = normalizeBookingDayTime(d.booking_opens_at) + const dlN = normalizeBookingDayTime(d.booking_deadline_at) + const opensMs = parseShanghaiLocalToMs(opensN) + const dlMs = parseShanghaiLocalToMs(dlN) + if (!Number.isFinite(opensMs) || !Number.isFinite(dlMs)) { + Message.warning(`第 ${i + 1} 行预约开始或预约截止时间格式无效`) + return false + } + if (opensMs > dlMs) { + Message.warning(`第 ${i + 1} 行预约开始不得晚于预约截止`) + return false + } + if (dlMs >= sm) { + Message.warning(`第 ${i + 1} 行预约截止时间须早于场次开始时间`) + return false + } + } const booked = Math.max(0, Number(d.booked_count) || 0) const minQ = Math.max(1, booked) - if (!d.day_quota || d.day_quota < minQ) { + const quotaRaw = d.day_quota + if (quotaRaw === undefined || quotaRaw === null || !Number.isFinite(Number(quotaRaw))) { + Message.warning(`第 ${i + 1} 行请填写可预约人数`) + return false + } + const quotaNum = Math.floor(Number(quotaRaw)) + if (quotaNum < minQ) { Message.warning( - `第 ${i + 1} 行预约名额须≥${minQ}${booked > 0 ? `(已约 ${booked} 人)` : ''}`, + `第 ${i + 1} 行可预约人数须≥${minQ}${booked > 0 ? `(已约 ${booked} 人)` : ''}`, ) return false } @@ -758,11 +627,11 @@ function validateBookingFormInternal(): boolean { aud !== 'individual' && bookingForm.max_people_per_order < bookingForm.min_people_per_order ) { - Message.warning('每单最多人数必须大于等于每单最少人数') + Message.warning('团体每单最多人数必须大于等于团体每单最少人数') return false } if (aud !== 'individual' && bookingForm.max_people_per_order === 1) { - Message.warning('团体或个人+团体时,每单最多人数不可为 1,且须大于等于每单最少人数') + Message.warning('团体或个人+团体时,团体每单最多人数不可为 1,且须大于等于团体每单最少人数') return false } return true @@ -785,7 +654,7 @@ function buildBookingPayload(): Record { session_start_at: d.session_start_at, session_end_at: d.session_end_at, booking_deadline_at: deadlineIn || null, - day_quota: d.day_quota, + day_quota: Math.floor(Number(d.day_quota)), quota_note: (d.quota_note || '').trim() || null, } if (rt === 'online') { @@ -909,14 +778,6 @@ function formSignature() { return JSON.stringify(form) } -function addSpecificTimeRow() { - form.specific_time_slots.push({ start: '09:00', end: '10:00' }) -} - -function removeSpecificTimeRow(i: number) { - form.specific_time_slots.splice(i, 1) -} - function editFlowSignature() { return JSON.stringify({ form: formSignature(), @@ -1646,6 +1507,8 @@ async function loadMe() { } function openCreate() { + suppressReservationTypeBookingReset.value += 1 + try { isCreate.value = true editId.value = null Object.keys(formErrors).forEach((key) => { formErrors[key] = '' }) @@ -1657,8 +1520,6 @@ function openCreate() { form.check_in_meeting_point = '' form.lat = undefined form.lng = undefined - form.specific_time = '' - form.specific_time_slots = [] form.external_url = '' form.title = '' form.contact_name = '' @@ -1682,10 +1543,17 @@ function openCreate() { resetEditors() captureFormBaseline() visible.value = true + } finally { + void nextTick(() => { + suppressReservationTypeBookingReset.value -= 1 + }) + } } function openEdit(row: Activity) { if (!assertCanOpenActivityEditor(row)) return + suppressReservationTypeBookingReset.value += 1 + try { isCreate.value = false editId.value = row.id bookingForm.booking_audience = 'both' @@ -1711,8 +1579,6 @@ function openEdit(row: Activity) { form.check_in_meeting_point = row.check_in_meeting_point || '' form.lat = parseCoord(row.lat) form.lng = parseCoord(row.lng) - form.specific_time = row.specific_time || '' - form.specific_time_slots = parseSpecificTimeToSlots(row.specific_time) form.external_url = '' form.title = row.title form.contact_name = row.contact_name ?? '' @@ -1732,6 +1598,11 @@ function openEdit(row: Activity) { captureFormBaseline() visible.value = true void loadBookingForEditActivity(row.id).catch(() => undefined) + } finally { + void nextTick(() => { + suppressReservationTypeBookingReset.value -= 1 + }) + } } function onSearch() { @@ -1883,16 +1754,6 @@ function validateForm(): boolean { } } - if (reservationTypeSupportsSessionSettings(form.reservation_type)) { - const hasValidSlot = form.specific_time_slots.some((row) => - isValidHmPair(String(row.start || '').trim(), String(row.end || '').trim())) - if (!hasValidSlot) { - formErrors.specific_time_slots = - '请至少添加一个具体时间时段,并完整填写开始与结束(格式 HH:mm,结束须晚于开始)' - isValid = false - } - } - return isValid } @@ -1904,7 +1765,7 @@ function onCancelUnifiedModal() { } /** 已通过校验后的实际保存(由确认弹窗的 onBeforeOk 调用) */ -async function executeUnifiedActivitySave(specificPayload: string | null): Promise { +async function executeUnifiedActivitySave(): Promise { const shouldSyncBooking = canSaveSessionsWithActivity.value && reservationTypeSupportsSessionSettings(form.reservation_type) @@ -1916,7 +1777,6 @@ async function executeUnifiedActivitySave(specificPayload: string | null): Promi reservation_type: effectiveRt, location: form.location.trim(), check_in_meeting_point: form.check_in_meeting_point.trim() || null, - specific_time: specificPayload, external_url: null, title: form.title.trim(), summary: form.summary.trim() || null, @@ -1940,6 +1800,7 @@ async function executeUnifiedActivitySave(specificPayload: string | null): Promi payload.offline_reservation_method = null payload.booking_method_note = form.booking_method_note.trim() || null payload.ticket_fee_note = form.fee_note.trim() || null + payload.specific_time = null } else if (!isCreate.value && editContextRowSnapshot.value) { const snap = editContextRowSnapshot.value payload.offline_reservation_method = snap.offline_reservation_method ?? null @@ -1983,8 +1844,6 @@ function submitUnifiedActivity() { Message.warning('请填写所有必填项') return } - const specificPayload = slotsToSpecificTimePayload(form.specific_time_slots) - form.specific_time = specificPayload ?? '' const shouldSyncBooking = canSaveSessionsWithActivity.value && reservationTypeSupportsSessionSettings(form.reservation_type) @@ -2001,7 +1860,7 @@ function submitUnifiedActivity() { async onBeforeOk() { saving.value = true try { - await executeUnifiedActivitySave(specificPayload) + await executeUnifiedActivitySave() return true } catch (error: any) { Message.error(error?.response?.data?.message ?? '保存失败') @@ -2232,6 +2091,14 @@ async function removeActivity(row: Activity) { 活动名称
{{ auditActivityRecord.title || '—' }}
+
+ 活动性质 +
{{ reservationTypeLabel(auditActivityRecord.reservation_type) }}
+
+
+ {{ auditBookingWayLabel(auditActivityRecord) }} +
{{ auditActivityRecord.booking_method_note?.trim() || '—' }}
+
举办场馆
{{ auditActivityRecord.venue?.name || '—' }}
@@ -2244,29 +2111,13 @@ async function removeActivity(row: Activity) { 联系电话
{{ auditActivityRecord.contact_phone || '—' }}
-
- 活动日期 -
{{ formatActivityTableDateRange(auditActivityRecord) }}
-
-
- 具体时间 -
{{ activitySpecificTimeDisplay(auditActivityRecord.specific_time) || '—' }}
-
-
- 活动性质 -
{{ reservationTypeLabel(auditActivityRecord.reservation_type) }}
-
-
- {{ auditBookingWayLabel(auditActivityRecord) }} -
{{ auditActivityRecord.booking_method_note?.trim() || '—' }}
-
费用
{{ auditFeeDisplay(auditActivityRecord) }}
-
- 外链地址 -
{{ auditActivityRecord.external_url || '—' }}
+
+ 活动日期 +
{{ formatActivityTableDateRange(auditActivityRecord) }}
标签 @@ -2279,6 +2130,10 @@ async function removeActivity(row: Activity) {
+
+ 外链地址 +
{{ auditActivityRecord.external_url || '—' }}
+
活动地点
{{ auditActivityRecord.location?.trim() ? auditActivityRecord.location : (auditActivityRecord.address || '—') }}
@@ -2333,18 +2188,6 @@ async function removeActivity(row: Activity) {
-
-
活动详情
-
-
- -
-
-
- + @@ -2855,8 +2666,11 @@ async function removeActivity(row: Activity) { v-model="bookingForm.days[rowIndex].session_end_at" show-time format="YYYY-MM-DD HH:mm" - value-format="YYYY-MM-DD HH:mm:ss" + value-format="YYYY-MM-DD HH:mm" + :time-picker-props="{ format: 'HH:mm' }" + :disabled-date="sessionDayDisabledDate" style="width: 100%" + placeholder="必填" :disabled="!canSaveSessionsWithActivity" /> @@ -2867,8 +2681,10 @@ async function removeActivity(row: Activity) { v-model="bookingForm.days[rowIndex].booking_opens_at" show-time format="YYYY-MM-DD HH:mm" - value-format="YYYY-MM-DD HH:mm:ss" + value-format="YYYY-MM-DD HH:mm" + :time-picker-props="{ format: 'HH:mm' }" style="width: 100%" + placeholder="必填" :disabled="!canSaveSessionsWithActivity" /> @@ -2879,17 +2695,21 @@ async function removeActivity(row: Activity) { v-model="bookingForm.days[rowIndex].booking_deadline_at" show-time format="YYYY-MM-DD HH:mm" - value-format="YYYY-MM-DD HH:mm:ss" + value-format="YYYY-MM-DD HH:mm" + :time-picker-props="{ format: 'HH:mm' }" style="width: 100%" + placeholder="必填" :disabled="!canSaveSessionsWithActivity" /> - +