diff --git a/frontend/src/features/seller/composables/usePublishDraft.ts b/frontend/src/features/seller/composables/usePublishDraft.ts index f6063a6..6087139 100644 --- a/frontend/src/features/seller/composables/usePublishDraft.ts +++ b/frontend/src/features/seller/composables/usePublishDraft.ts @@ -92,6 +92,9 @@ function normalizeDraftForm(value: unknown): PublishForm { if (!next.accelerated_sale_ratio || next.accelerated_sale_ratio === 0) { next.accelerated_sale_ratio = '' } + if (next.haf_coin_amount === 0) next.haf_coin_amount = '' + if (String(next.secret_kd).trim() === '0') next.secret_kd = '' + if (next.fire_level === 0) next.fire_level = '' return next } diff --git a/frontend/src/features/seller/composables/usePublishForm.ts b/frontend/src/features/seller/composables/usePublishForm.ts index 2110acc..a3cd613 100644 --- a/frontend/src/features/seller/composables/usePublishForm.ts +++ b/frontend/src/features/seller/composables/usePublishForm.ts @@ -79,6 +79,12 @@ export function usePublishForm(options: UsePublishFormOptions) { const canPublishAfterAgreements = computed( () => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value ) + const disabledOnlineStartOptions = computed(() => + commonOnlineTimes.filter(time => !canUseOnlineStart(time)) + ) + const disabledOnlineEndOptions = computed(() => + commonOnlineTimes.filter(time => !canUseOnlineEnd(time)) + ) onMounted(() => { restoreDraft() @@ -240,6 +246,103 @@ export function usePublishForm(options: UsePublishFormOptions) { } } + function isAllDayOnline() { + return form.online_start === '00:00' && form.online_end === '23:59' + } + + function isOnlineStartPresetActive(value: string) { + return !isAllDayOnline() && form.online_start === value + } + + function isOnlineEndPresetActive(value: string) { + return !isAllDayOnline() && form.online_end === value + } + + function selectAllDayOnline() { + form.online_start = '00:00' + form.online_end = '23:59' + } + + function selectOnlineStart(value: string | number) { + const time = String(value) + if (!canUseOnlineStart(time)) { + options.notifyWarning('开始时间必须早于结束时间') + return + } + form.online_start = time + } + + function selectOnlineEnd(value: string | number) { + const time = String(value) + if (!canUseOnlineEnd(time)) { + options.notifyWarning('结束时间必须晚于开始时间') + return + } + form.online_end = time + } + + function handleOnlineStartChange(value: string | number | null | undefined) { + const time = normalizeOnlineTimeValue(value) + form.online_start = time + const start = parseOnlineTime(time) + if (start === null) return + const lastTime = parseOnlineTime('23:59') + if (lastTime !== null && start >= lastTime) { + form.online_start = '' + options.notifyWarning('开始时间必须早于结束时间') + return + } + const end = parseOnlineTime(form.online_end) + if (end !== null && start >= end) { + form.online_end = '' + options.notifyWarning('结束时间已清空,请选择晚于开始时间的结束时间') + } + } + + function handleOnlineEndChange(value: string | number | null | undefined) { + const time = normalizeOnlineTimeValue(value) + form.online_end = time + const end = parseOnlineTime(time) + if (end === null) return + const firstTime = parseOnlineTime('00:00') + if (firstTime !== null && end <= firstTime) { + form.online_end = '' + options.notifyWarning('结束时间必须晚于开始时间') + return + } + const start = parseOnlineTime(form.online_start) + if (start !== null && end <= start) { + form.online_end = '' + options.notifyWarning('结束时间必须晚于开始时间') + } + } + + function canUseOnlineStart(value: string) { + const start = parseOnlineTime(value) + const lastTime = parseOnlineTime('23:59') + if (start === null || lastTime === null || start >= lastTime) return false + const end = parseOnlineTime(form.online_end) + return end === null || start < end + } + + function canUseOnlineEnd(value: string) { + const end = parseOnlineTime(value) + const firstTime = parseOnlineTime('00:00') + if (end === null || firstTime === null || end <= firstTime) return false + const start = parseOnlineTime(form.online_start) + return start === null || end > start + } + + function validateOnlineTime() { + const start = parseOnlineTime(form.online_start) + const end = parseOnlineTime(form.online_end) + if (start === null) return '请选择在线开始时间' + if (end === null) return '请选择在线结束时间' + if (start === end) return '在线开始和结束时间不能相同' + if (end < start) return '在线结束时间必须晚于开始时间' + return '' + } + async function handleResetDraft() { try { await options.confirmReset?.() @@ -496,6 +599,8 @@ export function usePublishForm(options: UsePublishFormOptions) { if (!form.stamina_level) return '请选择体力等级' if (!form.load_level) return '请选择负重等级' if (!dailyLossOptions.includes(pricing.dailyLossMAmount.value)) return '请选择每日损耗' + const onlineTimeError = validateOnlineTime() + if (onlineTimeError) return onlineTimeError if (pricing.hasAcceleratedSaleRatioInput()) { const ratio = Number(form.accelerated_sale_ratio) if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确' @@ -669,11 +774,21 @@ export function usePublishForm(options: UsePublishFormOptions) { selectedSkins, uploadedScreenshotCount, requiredScreenshotCount, + disabledOnlineStartOptions, + disabledOnlineEndOptions, loadPublishOptions, saveDraft, handleSaveDraft, resetDraftState, handleResetDraft, + isAllDayOnline, + isOnlineStartPresetActive, + isOnlineEndPresetActive, + selectAllDayOnline, + selectOnlineStart, + selectOnlineEnd, + handleOnlineStartChange, + handleOnlineEndChange, toggleSkin, toggleRegion, triggerUpload, @@ -710,3 +825,17 @@ function readError(error: unknown, fallback: string) { } return fallback } + +function parseOnlineTime(value: string) { + const match = /^(\d{2}):(\d{2})$/.exec(value) + if (!match) return null + const hour = Number(match[1]) + const minute = Number(match[2]) + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null + return hour * 60 + minute +} + +function normalizeOnlineTimeValue(value: string | number | null | undefined) { + if (value === null || value === undefined) return '' + return String(value) +} diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.css b/frontend/src/features/seller/views/MobileSellerListingCreateView.css index 765ca73..d662cce 100644 --- a/frontend/src/features/seller/views/MobileSellerListingCreateView.css +++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.css @@ -210,6 +210,14 @@ color: #fff; } +.radio-btn.disabled, +.radio-btn:disabled { + border-color: #e5e7eb; + background: #f3f4f6; + color: #a8b0bd; + cursor: not-allowed; +} + .time-row, .result-grid { display: grid; diff --git a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue index eee4076..e6ed237 100644 --- a/frontend/src/features/seller/views/MobileSellerListingCreateView.vue +++ b/frontend/src/features/seller/views/MobileSellerListingCreateView.vue @@ -23,6 +23,8 @@ const { quantityValues, quantityModes, selectedSkins, + disabledOnlineStartOptions, + disabledOnlineEndOptions, serverOptions, faceOptions, rankOptions, @@ -61,6 +63,14 @@ const { isMultiScreenshotSlot, canAddScreenshot, getScreenshotLimitHint, + isAllDayOnline, + isOnlineStartPresetActive, + isOnlineEndPresetActive, + selectAllDayOnline, + selectOnlineStart, + selectOnlineEnd, + handleOnlineStartChange, + handleOnlineEndChange, handleFireLevelInput, handleAcceleratedSaleRatioInput, useRecommendedDeposit, @@ -100,31 +110,6 @@ function openPublishAgreement(agreement: AgreementContent) { function selectRadio(value: T, setter: (value: T) => void) { setter(value) } - -function isAllDayOnline() { - return form.online_start === '00:00' && form.online_end === '23:59' -} - -function isOnlineStartPresetActive(value: string) { - return !isAllDayOnline() && form.online_start === value -} - -function isOnlineEndPresetActive(value: string) { - return !isAllDayOnline() && form.online_end === value -} - -function selectAllDayOnline() { - form.online_start = '00:00' - form.online_end = '23:59' -} - -function selectOnlineStart(value: string) { - form.online_start = value -} - -function selectOnlineEnd(value: string) { - form.online_end = value -}