From a0b636a2979abde58dad84e7825e0925044791f7 Mon Sep 17 00:00:00 2001 From: MaDaLei Date: Tue, 14 Apr 2026 22:12:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A2=84=E7=BA=A6=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E5=8F=8A=E5=8D=8A=E5=B0=8F=E6=97=B6=E6=97=B6=E9=97=B4=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appointment/CustAppointmentCreate.vue | 30 ++++++-- src/pages/home/Home.vue | 30 ++++++-- src/pages/report/Report.vue | 35 +++++++-- src/utils/halfHourTime.js | 72 +++++++++++++++++++ 4 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 src/utils/halfHourTime.js diff --git a/src/pages/appointment/CustAppointmentCreate.vue b/src/pages/appointment/CustAppointmentCreate.vue index 4a2e4dc..ffdf371 100644 --- a/src/pages/appointment/CustAppointmentCreate.vue +++ b/src/pages/appointment/CustAppointmentCreate.vue @@ -68,7 +68,13 @@ {{ appointmentDate || '日期' }} - + {{ appointmentTime || '时间' }} @@ -90,6 +96,12 @@ import { createAppointment, getServiceTypeList, getPetList, getStoreList } from import AppIcon from '../../components/AppIcon.vue' import { getUserSession, getStoreSession, setStoreSession } from '../../utils/session.js' import { haversineKm } from '../../utils/geo.js' +import { + HALF_HOUR_TIME_SLOTS, + normalizeToHalfHour, + halfHourPickerIndex, + suggestHalfHourForYMD +} from '../../utils/halfHourTime.js' const userInfo = getUserSession() const storeInfo = getStoreSession() @@ -239,17 +251,25 @@ const appointmentDate = computed(() => { const appointmentTime = computed(() => { const raw = newAppt.value.appointmentTime || '' if (!raw.includes('T')) return '' - return (raw.split('T')[1] || '').slice(0, 5) + const part = (raw.split('T')[1] || '').slice(0, 5) + return normalizeToHalfHour(part) }) +const halfHourTimeIndex = computed(() => halfHourPickerIndex(appointmentTime.value)) + const onAppointmentDateChange = (e) => { const date = e?.detail?.value || '' - const time = appointmentTime.value || '00:00' - newAppt.value.appointmentTime = date ? `${date}T${time}` : '' + if (!date) { + newAppt.value.appointmentTime = '' + return + } + const time = normalizeToHalfHour(suggestHalfHourForYMD(date)) + newAppt.value.appointmentTime = `${date}T${time}` } const onAppointmentTimeOnlyChange = (e) => { - const time = e?.detail?.value || '' + const idx = Number(e.detail.value) + const time = HALF_HOUR_TIME_SLOTS[idx] const date = appointmentDate.value if (!date) { uni.showToast({ title: '请先选择日期', icon: 'none' }); return } newAppt.value.appointmentTime = `${date}T${time}` diff --git a/src/pages/home/Home.vue b/src/pages/home/Home.vue index ef7feb0..470a165 100644 --- a/src/pages/home/Home.vue +++ b/src/pages/home/Home.vue @@ -147,7 +147,13 @@ {{ appointmentDate || '日期' }} - + {{ appointmentTime || '时间' }} @@ -182,6 +188,12 @@ import { getUserSession, getStoreSession } from '../../utils/session.js' import { useNavigator } from '../../composables/useNavigator.js' import { formatDateTimeCN } from '../../utils/datetime.js' import { getAppointmentStatusText } from '../../utils/appointment.js' +import { + HALF_HOUR_TIME_SLOTS, + normalizeToHalfHour, + halfHourPickerIndex, + suggestHalfHourForYMD +} from '../../utils/halfHourTime.js' const userInfo = getUserSession() const storeInfo = getStoreSession() @@ -225,9 +237,12 @@ const appointmentDate = computed(() => { const appointmentTime = computed(() => { const raw = newAppt.value.appointmentTime || '' if (!raw.includes('T')) return '' - return (raw.split('T')[1] || '').slice(0, 5) + const part = (raw.split('T')[1] || '').slice(0, 5) + return normalizeToHalfHour(part) }) +const halfHourTimeIndex = computed(() => halfHourPickerIndex(appointmentTime.value)) + const filteredOrders = computed(() => orders.value.filter(o => { if (currentStatus.value === 'new') return o.status === 'new' if (currentStatus.value === 'doing') return o.status === 'doing' @@ -237,12 +252,17 @@ const filteredOrders = computed(() => orders.value.filter(o => { const onAppointmentDateChange = (e) => { const date = e?.detail?.value || '' - const time = appointmentTime.value || '00:00' - newAppt.value.appointmentTime = date ? `${date}T${time}` : '' + if (!date) { + newAppt.value.appointmentTime = '' + return + } + const time = normalizeToHalfHour(suggestHalfHourForYMD(date)) + newAppt.value.appointmentTime = `${date}T${time}` } const onAppointmentTimeOnlyChange = (e) => { - const time = e?.detail?.value || '' + const idx = Number(e.detail.value) + const time = HALF_HOUR_TIME_SLOTS[idx] const date = appointmentDate.value if (!date) { uni.showToast({ title: '请先选择日期', icon: 'none' }); return } newAppt.value.appointmentTime = `${date}T${time}` diff --git a/src/pages/report/Report.vue b/src/pages/report/Report.vue index eb2afa6..d7981c4 100644 --- a/src/pages/report/Report.vue +++ b/src/pages/report/Report.vue @@ -33,7 +33,13 @@ {{ serviceDate || '日期' }} - + {{ serviceTimePart || '时间' }} @@ -117,6 +123,13 @@ import TabBar from '../../components/TabBar.vue' import AppIcon from '../../components/AppIcon.vue' import { getUserSession, getStoreSession } from '../../utils/session.js' import { useNavigator } from '../../composables/useNavigator.js' +import { + HALF_HOUR_TIME_SLOTS, + DEFAULT_HALF_HOUR_TIME, + normalizeToHalfHour, + halfHourPickerIndex, + suggestHalfHourForYMD +} from '../../utils/halfHourTime.js' const userInfo = getUserSession() const storeInfo = getStoreSession() @@ -196,17 +209,25 @@ const serviceDate = computed(() => { const serviceTimePart = computed(() => { const raw = report.value.appointmentTime || '' if (!raw.includes('T')) return '' - return (raw.split('T')[1] || '').slice(0, 5) + const part = (raw.split('T')[1] || '').slice(0, 5) + return normalizeToHalfHour(part) }) +const halfHourServiceTimeIndex = computed(() => halfHourPickerIndex(serviceTimePart.value)) + const onServiceDateChange = (e) => { const date = e?.detail?.value || '' - const time = serviceTimePart.value || '00:00' - report.value.appointmentTime = date ? `${date}T${time}` : '' + if (!date) { + report.value.appointmentTime = '' + return + } + const time = normalizeToHalfHour(suggestHalfHourForYMD(date)) + report.value.appointmentTime = `${date}T${time}` } const onServiceTimeChange = (e) => { - const time = e?.detail?.value || '' + const idx = Number(e.detail.value) + const time = HALF_HOUR_TIME_SLOTS[idx] const date = serviceDate.value if (!date) { uni.showToast({ title: '请先选择日期', icon: 'none' }); return } report.value.appointmentTime = `${date}T${time}` @@ -262,8 +283,8 @@ onMounted(async () => { const raw = String(prefill.appointmentTime) if (raw.includes('T')) { const [d, rest] = raw.split('T') - const hm = (rest || '').match(/^(\d{1,2}:\d{2})/)?.[1] || '00:00' - report.value.appointmentTime = `${d}T${hm}` + const hm = (rest || '').match(/^(\d{1,2}:\d{2})/)?.[1] || DEFAULT_HALF_HOUR_TIME + report.value.appointmentTime = `${d}T${normalizeToHalfHour(hm)}` } } uni.removeStorageSync('petstore_report_prefill') diff --git a/src/utils/halfHourTime.js b/src/utils/halfHourTime.js new file mode 100644 index 0000000..b8e4967 --- /dev/null +++ b/src/utils/halfHourTime.js @@ -0,0 +1,72 @@ +/** 未选时间时的默认展示与选中日期的默认时刻 */ +export const DEFAULT_HALF_HOUR_TIME = '09:00' + +/** 全天半小时间隔:00:00 … 23:30 */ +export const HALF_HOUR_TIME_SLOTS = (() => { + const a = [] + for (let h = 0; h < 24; h++) { + const p = (n) => String(n).padStart(2, '0') + a.push(`${p(h)}:00`) + a.push(`${p(h)}:30`) + } + return a +})() + +/** + * 将 HH:mm 规范到最近的半点档;空值或无法解析时返回 {@link DEFAULT_HALF_HOUR_TIME} + */ +export function normalizeToHalfHour(hhmm) { + if (hhmm == null || hhmm === '') return DEFAULT_HALF_HOUR_TIME + if (typeof hhmm !== 'string') return DEFAULT_HALF_HOUR_TIME + const m = hhmm.match(/^(\d{1,2}):(\d{2})$/) + if (!m) return DEFAULT_HALF_HOUR_TIME + let h = parseInt(m[1], 10) + let mi = parseInt(m[2], 10) + if (Number.isNaN(h) || Number.isNaN(mi)) return DEFAULT_HALF_HOUR_TIME + h = Math.min(23, Math.max(0, h)) + mi = Math.min(59, Math.max(0, mi)) + const total = h * 60 + mi + const snapped = Math.min(1410, Math.round(total / 30) * 30) + const H = Math.floor(snapped / 60) + const M = snapped % 60 + const key = `${String(H).padStart(2, '0')}:${String(M).padStart(2, '0')}` + return HALF_HOUR_TIME_SLOTS.includes(key) ? key : DEFAULT_HALF_HOUR_TIME +} + +/** 供 picker mode=selector 的 :value(索引) */ +export function halfHourPickerIndex(hhmm) { + const n = normalizeToHalfHour(hhmm) + const i = HALF_HOUR_TIME_SLOTS.indexOf(n) + return i >= 0 ? i : 0 +} + +const pad2 = (n) => String(n).padStart(2, '0') + +/** 本地日历 YYYY-MM-DD */ +export function ymdLocal(d = new Date()) { + return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` +} + +/** + * 当前时刻之后的下一个半点档(含恰落在整点/半点时取该档)。 + * 若已超过 23:30,返回 null。 + */ +export function getNextHalfHourAfterNow(now = new Date()) { + const total = now.getHours() * 60 + now.getMinutes() + now.getSeconds() / 60 + let slotM = Math.ceil(total / 30) * 30 + if (slotM >= 24 * 60) return null + const H = Math.floor(slotM / 60) + const M = slotM % 60 + return `${pad2(H)}:${pad2(M)}` +} + +/** + * 选中日期的默认时刻:当天 → 当前时刻往后的下一个半点档;其它日期 → 09:00。 + * 当天已无可用半点时(理论上仅深夜),退回 23:30。 + */ +export function suggestHalfHourForYMD(dateStr) { + if (!dateStr || typeof dateStr !== 'string') return DEFAULT_HALF_HOUR_TIME + if (dateStr !== ymdLocal(new Date())) return DEFAULT_HALF_HOUR_TIME + const next = getNextHalfHourAfterNow() + return next != null ? next : '23:30' +}