优化发布页默认输入与在线时间校验

This commit is contained in:
yml2213
2026-06-08 07:12:26 +08:00
parent 04f42e5a96
commit ff2aa2df9d
6 changed files with 203 additions and 55 deletions
@@ -92,6 +92,9 @@ function normalizeDraftForm(value: unknown): PublishForm {
if (!next.accelerated_sale_ratio || next.accelerated_sale_ratio === 0) { if (!next.accelerated_sale_ratio || next.accelerated_sale_ratio === 0) {
next.accelerated_sale_ratio = '' 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 return next
} }
@@ -79,6 +79,12 @@ export function usePublishForm(options: UsePublishFormOptions) {
const canPublishAfterAgreements = computed( const canPublishAfterAgreements = computed(
() => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value () => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value
) )
const disabledOnlineStartOptions = computed(() =>
commonOnlineTimes.filter(time => !canUseOnlineStart(time))
)
const disabledOnlineEndOptions = computed(() =>
commonOnlineTimes.filter(time => !canUseOnlineEnd(time))
)
onMounted(() => { onMounted(() => {
restoreDraft() 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() { async function handleResetDraft() {
try { try {
await options.confirmReset?.() await options.confirmReset?.()
@@ -496,6 +599,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
if (!form.stamina_level) return '请选择体力等级' if (!form.stamina_level) return '请选择体力等级'
if (!form.load_level) return '请选择负重等级' if (!form.load_level) return '请选择负重等级'
if (!dailyLossOptions.includes(pricing.dailyLossMAmount.value)) return '请选择每日损耗' if (!dailyLossOptions.includes(pricing.dailyLossMAmount.value)) return '请选择每日损耗'
const onlineTimeError = validateOnlineTime()
if (onlineTimeError) return onlineTimeError
if (pricing.hasAcceleratedSaleRatioInput()) { if (pricing.hasAcceleratedSaleRatioInput()) {
const ratio = Number(form.accelerated_sale_ratio) const ratio = Number(form.accelerated_sale_ratio)
if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确' if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确'
@@ -669,11 +774,21 @@ export function usePublishForm(options: UsePublishFormOptions) {
selectedSkins, selectedSkins,
uploadedScreenshotCount, uploadedScreenshotCount,
requiredScreenshotCount, requiredScreenshotCount,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
loadPublishOptions, loadPublishOptions,
saveDraft, saveDraft,
handleSaveDraft, handleSaveDraft,
resetDraftState, resetDraftState,
handleResetDraft, handleResetDraft,
isAllDayOnline,
isOnlineStartPresetActive,
isOnlineEndPresetActive,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
toggleSkin, toggleSkin,
toggleRegion, toggleRegion,
triggerUpload, triggerUpload,
@@ -710,3 +825,17 @@ function readError(error: unknown, fallback: string) {
} }
return fallback 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)
}
@@ -210,6 +210,14 @@
color: #fff; color: #fff;
} }
.radio-btn.disabled,
.radio-btn:disabled {
border-color: #e5e7eb;
background: #f3f4f6;
color: #a8b0bd;
cursor: not-allowed;
}
.time-row, .time-row,
.result-grid { .result-grid {
display: grid; display: grid;
@@ -23,6 +23,8 @@ const {
quantityValues, quantityValues,
quantityModes, quantityModes,
selectedSkins, selectedSkins,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
serverOptions, serverOptions,
faceOptions, faceOptions,
rankOptions, rankOptions,
@@ -61,6 +63,14 @@ const {
isMultiScreenshotSlot, isMultiScreenshotSlot,
canAddScreenshot, canAddScreenshot,
getScreenshotLimitHint, getScreenshotLimitHint,
isAllDayOnline,
isOnlineStartPresetActive,
isOnlineEndPresetActive,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
handleFireLevelInput, handleFireLevelInput,
handleAcceleratedSaleRatioInput, handleAcceleratedSaleRatioInput,
useRecommendedDeposit, useRecommendedDeposit,
@@ -100,31 +110,6 @@ function openPublishAgreement(agreement: AgreementContent) {
function selectRadio<T extends string>(value: T, setter: (value: T) => void) { function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
setter(value) 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
}
</script> </script>
<template> <template>
@@ -373,6 +358,7 @@ function selectOnlineEnd(value: string) {
label="在线开始" label="在线开始"
type="time" type="time"
class="publish-field time-field" class="publish-field time-field"
@update:model-value="handleOnlineStartChange"
/> />
<div class="radio-group small time-preset-chips"> <div class="radio-group small time-preset-chips">
<button <button
@@ -388,7 +374,11 @@ function selectOnlineEnd(value: string) {
:key="`start-${opt}`" :key="`start-${opt}`"
type="button" type="button"
class="radio-btn" class="radio-btn"
:class="{ active: isOnlineStartPresetActive(opt) }" :class="{
active: isOnlineStartPresetActive(opt),
disabled: disabledOnlineStartOptions.includes(opt),
}"
:disabled="disabledOnlineStartOptions.includes(opt)"
@click="selectOnlineStart(opt)" @click="selectOnlineStart(opt)"
> >
{{ opt }} {{ opt }}
@@ -401,6 +391,7 @@ function selectOnlineEnd(value: string) {
label="在线结束" label="在线结束"
type="time" type="time"
class="publish-field time-field" class="publish-field time-field"
@update:model-value="handleOnlineEndChange"
/> />
<div class="radio-group small time-preset-chips"> <div class="radio-group small time-preset-chips">
<button <button
@@ -416,7 +407,11 @@ function selectOnlineEnd(value: string) {
:key="`end-${opt}`" :key="`end-${opt}`"
type="button" type="button"
class="radio-btn" class="radio-btn"
:class="{ active: isOnlineEndPresetActive(opt) }" :class="{
active: isOnlineEndPresetActive(opt),
disabled: disabledOnlineEndOptions.includes(opt),
}"
:disabled="disabledOnlineEndOptions.includes(opt)"
@click="selectOnlineEnd(opt)" @click="selectOnlineEnd(opt)"
> >
{{ opt }} {{ opt }}
@@ -32,6 +32,8 @@ const {
selectedSkins, selectedSkins,
uploadedScreenshotCount, uploadedScreenshotCount,
requiredScreenshotCount, requiredScreenshotCount,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
serverOptions, serverOptions,
faceOptions, faceOptions,
rankOptions, rankOptions,
@@ -74,6 +76,12 @@ const {
isMultiScreenshotSlot, isMultiScreenshotSlot,
canAddScreenshot, canAddScreenshot,
getScreenshotLimitHint, getScreenshotLimitHint,
isAllDayOnline,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
handleFireLevelInput, handleFireLevelInput,
handleAcceleratedSaleRatioInput, handleAcceleratedSaleRatioInput,
useRecommendedDeposit, useRecommendedDeposit,
@@ -116,23 +124,6 @@ function openPublishAgreement(agreement: AgreementContent) {
function selectDailyLoss(value: string | number) { function selectDailyLoss(value: string | number) {
form.daily_loss_m = Number(value) form.daily_loss_m = Number(value)
} }
function isAllDayOnline() {
return form.online_start === '00:00' && form.online_end === '23:59'
}
function selectAllDayOnline() {
form.online_start = '00:00'
form.online_end = '23:59'
}
function selectOnlineStart(value: string | number) {
form.online_start = String(value)
}
function selectOnlineEnd(value: string | number) {
form.online_end = String(value)
}
</script> </script>
<template> <template>
@@ -161,29 +152,32 @@ function selectOnlineEnd(value: string | number) {
<div class="compact-grid panel-input-grid"> <div class="compact-grid panel-input-grid">
<label class="input-block"> <label class="input-block">
<span>哈夫币/M<b>*</b></span> <span>哈夫币/M<b>*</b></span>
<el-input-number <el-input
v-model="form.haf_coin_amount" v-model="form.haf_coin_amount"
:min="0" type="number"
:controls="false" inputmode="decimal"
min="0"
placeholder="100M 写 100" placeholder="100M 写 100"
/> />
</label> </label>
<label class="input-block"> <label class="input-block">
<span>绝密KD</span> <span>绝密KD</span>
<el-input-number <el-input
v-model="form.secret_kd" v-model="form.secret_kd"
:min="0" type="number"
:controls="false" inputmode="decimal"
:step="0.1" min="0"
step="0.1"
placeholder="填写绝密KD" placeholder="填写绝密KD"
/> />
</label> </label>
<label class="input-block"> <label class="input-block">
<span>烽火等级<b>*</b></span> <span>烽火等级<b>*</b></span>
<el-input-number <el-input
:model-value="form.fire_level" :model-value="form.fire_level"
:min="0" type="number"
:controls="false" inputmode="numeric"
min="0"
:placeholder="fireLevelPlaceholder" :placeholder="fireLevelPlaceholder"
@update:model-value="handleFireLevelInput" @update:model-value="handleFireLevelInput"
/> />
@@ -335,6 +329,7 @@ function selectOnlineEnd(value: string | number) {
format="HH:mm" format="HH:mm"
placeholder="开始时间" placeholder="开始时间"
class="time-input" class="time-input"
@change="handleOnlineStartChange"
/> />
<div class="time-preset-actions"> <div class="time-preset-actions">
<button <button
@@ -348,6 +343,7 @@ function selectOnlineEnd(value: string | number) {
<OptionChips <OptionChips
:options="commonOnlineTimes" :options="commonOnlineTimes"
:model-value="isAllDayOnline() ? '' : form.online_start" :model-value="isAllDayOnline() ? '' : form.online_start"
:disabled-values="disabledOnlineStartOptions"
key-prefix="start-" key-prefix="start-"
@select="selectOnlineStart" @select="selectOnlineStart"
/> />
@@ -363,6 +359,7 @@ function selectOnlineEnd(value: string | number) {
format="HH:mm" format="HH:mm"
placeholder="结束时间" placeholder="结束时间"
class="time-input" class="time-input"
@change="handleOnlineEndChange"
/> />
<div class="time-preset-actions"> <div class="time-preset-actions">
<button <button
@@ -376,6 +373,7 @@ function selectOnlineEnd(value: string | number) {
<OptionChips <OptionChips
:options="commonOnlineTimes" :options="commonOnlineTimes"
:model-value="isAllDayOnline() ? '' : form.online_end" :model-value="isAllDayOnline() ? '' : form.online_end"
:disabled-values="disabledOnlineEndOptions"
key-prefix="end-" key-prefix="end-"
@select="selectOnlineEnd" @select="selectOnlineEnd"
/> />
@@ -4,6 +4,7 @@ const props = withDefaults(
options: Array<string | number> options: Array<string | number>
modelValue?: string | number modelValue?: string | number
activeValues?: Array<string | number> activeValues?: Array<string | number>
disabledValues?: Array<string | number>
keyPrefix?: string keyPrefix?: string
compact?: boolean compact?: boolean
suffix?: string suffix?: string
@@ -11,6 +12,7 @@ const props = withDefaults(
{ {
modelValue: undefined, modelValue: undefined,
activeValues: () => [], activeValues: () => [],
disabledValues: () => [],
keyPrefix: '', keyPrefix: '',
compact: false, compact: false,
suffix: '', suffix: '',
@@ -26,7 +28,12 @@ function isActive(value: string | number) {
return props.activeValues.includes(value) || props.modelValue === value return props.activeValues.includes(value) || props.modelValue === value
} }
function isDisabled(value: string | number) {
return props.disabledValues.includes(value)
}
function selectValue(value: string | number) { function selectValue(value: string | number) {
if (isDisabled(value)) return
emit('update:modelValue', value) emit('update:modelValue', value)
emit('select', value) emit('select', value)
} }
@@ -39,7 +46,8 @@ function selectValue(value: string | number) {
:key="`${keyPrefix}${option}`" :key="`${keyPrefix}${option}`"
type="button" type="button"
class="chip" class="chip"
:class="{ active: isActive(option) }" :class="{ active: isActive(option), disabled: isDisabled(option) }"
:disabled="isDisabled(option)"
@click="selectValue(option)" @click="selectValue(option)"
> >
{{ option }}{{ suffix }} {{ option }}{{ suffix }}
@@ -81,4 +89,11 @@ function selectValue(value: string | number) {
background: #ff6a00; background: #ff6a00;
color: #fff; color: #fff;
} }
.chip.disabled {
border-color: #e5e7eb;
background: #f3f4f6;
color: #a8b0bd;
cursor: not-allowed;
}
</style> </style>