优化首页返回定位与发布在线时间表单

移动端首页用列表缓存和内部滚动恢复浏览位置;发布页在线时间改为全天+起止时间单行布局。
This commit is contained in:
yml2213
2026-07-12 23:02:01 +08:00
parent 42b250cd2c
commit acd9d9b7f1
11 changed files with 359 additions and 327 deletions
@@ -1,13 +1,27 @@
.mobile-shell {
width: 100%;
max-width: 430px;
min-height: 100vh;
height: 100dvh;
height: 100vh;
margin: 0 auto;
padding-bottom: calc(72px + env(safe-area-inset-bottom));
display: flex;
flex-direction: column;
overflow: hidden;
background: #f6f8fb;
color: #17233d;
}
/* 列表独立滚动:与详情页 window 滚动隔离,返回时恢复 scrollTop */
.mobile-scroll {
flex: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
padding-bottom: calc(72px + env(safe-area-inset-bottom));
}
.mobile-hero {
padding: 12px 12px 10px;
background: #fff;
@@ -1,15 +1,6 @@
<script setup lang="ts">
import {
computed,
nextTick,
onActivated,
onBeforeUnmount,
onDeactivated,
onMounted,
ref,
watch,
} from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { onBeforeRouteLeave, RouterLink, useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue'
import { ensureSupportChat } from '@/features/chats/api/chats'
@@ -58,13 +49,17 @@ import {
hasAcceleratedSaleRatio,
hasGiftResources,
} from '@/shared/utils/listingDisplay'
import {
buildMobileHomeListSignature,
useMobileHomeCacheStore,
} from '@/stores/mobileHomeCache'
import { useSessionStore } from '@/stores/session'
defineOptions({ name: 'MobileHomeView' })
const router = useRouter()
const route = useRoute()
const session = useSessionStore()
const homeCache = useMobileHomeCacheStore()
const scrollEl = ref<HTMLElement | null>(null)
const loading = ref(false)
const loadingMore = ref(false)
const loadFailed = ref(false)
@@ -87,12 +82,9 @@ const bannerSlides = ref<HomeBannerSlide[]>(defaultHomeBanners)
const supportLoading = ref(false)
const showBackTop = ref(false)
const mobilePageSize = 10
/** KeepAlive 缓存期间为 false,避免详情页路由变化误触发首页筛选/刷新 */
const pageActive = ref(true)
let listingRequestSeq = 0
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
let savedScrollY = 0
let homeConfigLoaded = false
const sortOptions = [
{ key: 'recommended', label: '综合推荐' },
@@ -301,77 +293,101 @@ const visibleZoneOptions = computed(() =>
applyRouteQueryToMobileState()
function isMobileHomeRoute() {
return (
route.name === 'mobile-home' ||
route.name === 'mobile-listings' ||
route.path === '/m' ||
route.path === '/m/listings'
)
function currentListSignature() {
return buildMobileHomeListSignature({
searchValue: searchValue.value,
activeSort: activeSort.value,
activeZone: activeZone.value,
selectedFilters: selectedFilters.value,
rangeFilters: rangeFilters.value,
})
}
function bindScrollListener() {
window.addEventListener('scroll', handleWindowScroll, { passive: true })
function persistListCache() {
homeCache.saveList({
signature: currentListSignature(),
listings: listings.value.slice(),
totalListings: totalListings.value,
zoneCounts: { ...zoneCounts.value },
currentPage: currentPage.value,
hasMoreListings: hasMoreListings.value,
})
}
function unbindScrollListener() {
window.removeEventListener('scroll', handleWindowScroll)
function persistScrollTop() {
if (!scrollEl.value) return
homeCache.saveScrollTop(scrollEl.value.scrollTop)
}
function restoreFromCache() {
const signature = currentListSignature()
if (!homeCache.matchList(signature)) return false
// 拷贝快照,避免页面内变更直接写穿 store
listings.value = homeCache.listings.slice()
totalListings.value = homeCache.totalListings
zoneCounts.value = { ...homeCache.zoneCounts }
currentPage.value = homeCache.currentPage
hasMoreListings.value = homeCache.hasMoreListings
loadFailed.value = false
loading.value = false
loadingMore.value = false
return true
}
function restoreHomeConfigFromCache() {
if (!homeCache.homeConfigLoaded) return false
announcements.value = homeCache.announcements.slice()
bannerSlides.value = homeCache.bannerSlides.map(slide => ({ ...slide }))
publishOptions.value = homeCache.publishOptions
return true
}
function restoreScrollPosition() {
const top = savedScrollY
const apply = () => window.scrollTo({ top, left: 0, behavior: 'auto' })
const top = homeCache.scrollTop
const apply = () => {
if (scrollEl.value) scrollEl.value.scrollTop = top
}
apply()
// 布局/图片回流后再校正一次,避免回顶
nextTick(() => {
apply()
requestAnimationFrame(() => {
apply()
handleWindowScroll()
requestAnimationFrame(() => {
apply()
handleListScroll()
})
})
})
}
onMounted(() => {
// 首次进入加载;KeepAlive 返回时走 onActivated,不再整页重拉
if (!listings.value.length && !loadFailed.value) {
const restoredList = restoreFromCache()
if (!restoredList) {
loadListings()
}
if (!homeConfigLoaded) {
if (!restoreHomeConfigFromCache()) {
loadHomeConfig()
}
if (restoredList) {
restoreScrollPosition()
}
})
onActivated(() => {
pageActive.value = true
bindScrollListener()
// 缓存实例仍持有列表数据;只把筛选写回 URL,避免整表重拉
if (isMobileHomeRoute()) {
syncMobileHomeQuery()
}
restoreScrollPosition()
})
onDeactivated(() => {
savedScrollY = window.scrollY || window.pageYOffset || 0
pageActive.value = false
unbindScrollListener()
if (searchDebounceTimer) {
clearTimeout(searchDebounceTimer)
searchDebounceTimer = null
}
onBeforeRouteLeave(() => {
persistScrollTop()
})
onBeforeUnmount(() => {
unbindScrollListener()
persistScrollTop()
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
})
watch(
() => filterQuerySignature(),
() => {
if (!pageActive.value || !isMobileHomeRoute()) return
syncMobileHomeQuery()
// 筛选变化时清空旧列表缓存,避免返回时命中过期数据
homeCache.clearList()
loadListings(true)
}
)
@@ -379,17 +395,15 @@ watch(
watch(
() => route.query,
() => {
if (!pageActive.value || !isMobileHomeRoute()) return
applyRouteQueryToMobileState()
}
)
watch(searchValue, () => {
if (!pageActive.value || !isMobileHomeRoute()) return
syncMobileHomeQuery()
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
searchDebounceTimer = setTimeout(() => {
if (!pageActive.value || !isMobileHomeRoute()) return
homeCache.clearList()
loadListings(true)
}, 300)
})
@@ -412,7 +426,12 @@ async function loadListings(reset = true) {
zoneCounts.value = page.zone_counts
hasMoreListings.value = listings.value.length < page.total
currentPage.value = page.page + 1
requestAnimationFrame(handleWindowScroll)
persistListCache()
if (reset) {
homeCache.saveScrollTop(0)
if (scrollEl.value) scrollEl.value.scrollTop = 0
}
requestAnimationFrame(handleListScroll)
} catch {
if (reset) {
listings.value = []
@@ -420,6 +439,7 @@ async function loadListings(reset = true) {
zoneCounts.value = {}
hasMoreListings.value = false
loadFailed.value = true
homeCache.clearList()
}
} finally {
if (requestSeq === listingRequestSeq) {
@@ -435,7 +455,11 @@ async function loadHomeConfig() {
announcements.value = config.announcements
bannerSlides.value = config.banners
publishOptions.value = config.publish_options
homeConfigLoaded = true
homeCache.saveHomeConfig({
announcements: config.announcements,
bannerSlides: config.banners,
publishOptions: config.publish_options,
})
} catch {
announcements.value = defaultHomeAnnouncements
bannerSlides.value = defaultHomeBanners
@@ -446,10 +470,16 @@ async function loadHomeConfig() {
async function onRefresh() {
refreshing.value = true
try {
homeCache.clearList()
const [, nextHomeConfig] = await Promise.all([loadListings(true), fetchMobileHomeConfig()])
announcements.value = nextHomeConfig.announcements
bannerSlides.value = nextHomeConfig.banners
publishOptions.value = nextHomeConfig.publish_options
homeCache.saveHomeConfig({
announcements: nextHomeConfig.announcements,
bannerSlides: nextHomeConfig.banners,
publishOptions: nextHomeConfig.publish_options,
})
showToast({ message: '刷新成功', icon: 'passed' })
} catch {
// 静默处理
@@ -578,7 +608,7 @@ function applyRouteQueryToMobileState() {
decodeHomeQueryJson<Record<string, unknown>>(query, 'ranges', {})
)
// 内容未变时避免赋值,防止 KeepAlive 激活或路由回写时误触发列表刷新
// 内容未变时避免赋值,防止 URL 回写时误触发列表刷新
if (
searchValue.value === nextSearch &&
activeSort.value === nextSort &&
@@ -637,14 +667,18 @@ function normalizeRangeFilters(value: Record<string, unknown>) {
return normalized
}
function handleWindowScroll() {
showBackTop.value = window.scrollY > 520
if (window.innerHeight + window.scrollY < document.documentElement.scrollHeight - 360) return
function handleListScroll() {
const el = scrollEl.value
if (!el) return
homeCache.saveScrollTop(el.scrollTop)
showBackTop.value = el.scrollTop > 520
if (el.scrollTop + el.clientHeight < el.scrollHeight - 360) return
loadListings(false)
}
function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' })
scrollEl.value?.scrollTo({ top: 0, behavior: 'smooth' })
homeCache.saveScrollTop(0)
}
function isSkinGroupKey(key: string) {
@@ -699,6 +733,7 @@ syncMobileHomeQuery()
<template>
<main class="mobile-shell">
<div ref="scrollEl" class="mobile-scroll" @scroll.passive="handleListScroll">
<!-- ========== Hero 区域顶部搜索与公告 ========== -->
<section class="mobile-hero">
<div class="mobile-topbar">
@@ -937,6 +972,18 @@ syncMobileHomeQuery()
</section>
</van-pull-refresh>
<footer class="home-footer">
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
class="home-footer-icp"
>湘ICP备2025146668号</a>
<span class="home-footer-divider">|</span>
<span class="home-footer-copyright">版权所有 ©2026 大锤网络游戏有限公司</span>
</footer>
</div>
<MobileHomeFilterSheet
v-model:show="filterOpen"
v-model:selected-filters="selectedFilters"
@@ -956,17 +1003,6 @@ syncMobileHomeQuery()
<van-icon name="arrow-up" :size="20" />
</button>
<footer class="home-footer">
<a
href="https://beian.miit.gov.cn/"
target="_blank"
rel="noopener noreferrer"
class="home-footer-icp"
>湘ICP备2025146668号</a>
<span class="home-footer-divider">|</span>
<span class="home-footer-copyright">版权所有 ©2026 大锤网络游戏有限公司</span>
</footer>
<MobileBottomNav />
</main>
</template>
@@ -33,12 +33,7 @@ import {
} from '@/features/seller/composables/usePublishDraft'
import type { PublishForm } from '@/shared/types/publish'
import { yuanToCent } from '@/shared/utils/money'
import {
commonOnlineTimes,
dailyLossOptions,
formatNumber,
roundRatio,
} from '@/shared/utils/pricing'
import { dailyLossOptions, formatNumber, roundRatio } from '@/shared/utils/pricing'
const draftSaveDelay = 400
const multiScreenshotLimit = 3
@@ -104,13 +99,6 @@ export function usePublishForm(options: UsePublishFormOptions) {
sellerAgreementChecked.value &&
passwordAndDeviceConfirmed.value
)
const disabledOnlineStartOptions = computed(() =>
commonOnlineTimes.filter(time => !canUseOnlineStart(time))
)
const disabledOnlineEndOptions = computed(() =>
commonOnlineTimes.filter(time => !canUseOnlineEnd(time))
)
onMounted(async () => {
if (!isEditMode.value) restoreDraft()
if (options.persistBeforeUnload) window.addEventListener('beforeunload', handleBeforeUnload)
@@ -360,37 +348,11 @@ export function usePublishForm(options: UsePublishFormOptions) {
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
@@ -427,22 +389,6 @@ export function usePublishForm(options: UsePublishFormOptions) {
}
}
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)
@@ -944,7 +890,6 @@ export function usePublishForm(options: UsePublishFormOptions) {
return {
...pricing,
dailyLossOptions,
commonOnlineTimes,
formatNumber,
router,
loading,
@@ -969,19 +914,13 @@ export function usePublishForm(options: UsePublishFormOptions) {
pageTitle,
submitButtonText,
submitLoadingText,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
loadPublishOptions,
saveDraft,
handleSaveDraft,
resetDraftState,
handleResetDraft,
isAllDayOnline,
isOnlineStartPresetActive,
isOnlineEndPresetActive,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
toggleSkin,
@@ -1025,5 +964,9 @@ function parseOnlineTime(value: string) {
function normalizeOnlineTimeValue(value: string | number | null | undefined) {
if (value === null || value === undefined) return ''
return String(value)
const raw = String(value).trim()
// 兼容原生 time 可能带秒:08:00:00 -> 08:00
const match = /^(\d{1,2}):(\d{2})(?::\d{2})?/.exec(raw)
if (!match?.[1] || !match[2]) return raw
return `${match[1].padStart(2, '0')}:${match[2]}`
}
@@ -200,24 +200,58 @@
cursor: not-allowed;
}
.time-row,
.result-grid {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
.time-field-group {
display: grid;
.online-time-field :deep(.van-field__control) {
min-height: 32px;
}
.online-time-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
width: 100%;
}
.online-time-range {
display: flex;
flex: 1;
align-items: center;
gap: 6px;
min-width: 0;
}
.time-field-group .publish-field {
margin-bottom: 0;
.online-time-input {
flex: 1;
min-width: 0;
height: 32px;
padding: 0 10px;
border: 1px solid #e2e8f0;
border-radius: 999px;
background: #f8fafc;
color: #1e293b;
font-size: 13px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.time-preset-chips {
padding: 0 2px 4px;
.online-time-input:focus {
outline: none;
border-color: #ffb074;
background: #fff;
box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.1);
}
.online-time-sep {
flex: 0 0 auto;
color: #94a3b8;
font-size: 12px;
font-weight: 700;
}
.level-panel {
@@ -832,11 +866,7 @@
background: #ff6a00 !important;
}
@media (min-width: 430px) {
.time-row {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 370px) {
.level-options,
@@ -10,7 +10,6 @@ import GuideImage from '@/components/GuideImage.vue'
import tencentSecurityGuide from '@/assets/tencent-security-guide.webp'
const {
commonOnlineTimes,
dailyLossOptions,
formatNumber,
router,
@@ -32,8 +31,6 @@ const {
quantityValues,
quantityModes,
selectedSkins,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
serverOptions,
faceOptions,
rankOptions,
@@ -73,11 +70,7 @@ const {
canAddScreenshot,
getScreenshotLimitHint,
isAllDayOnline,
isOnlineStartPresetActive,
isOnlineEndPresetActive,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
handleFireLevelInput,
@@ -386,17 +379,9 @@ const screenshotGuides: Record<string, string> = {
</template>
</van-field>
<div class="time-row">
<div class="time-field-group">
<van-field
v-model="form.online_start"
label="在线开始"
required
type="time"
class="publish-field time-field"
@update:model-value="handleOnlineStartChange"
/>
<div class="radio-group small time-preset-chips">
<van-field label="在线时间" required class="publish-field online-time-field">
<template #input>
<div class="online-time-controls">
<button
type="button"
class="radio-btn"
@@ -405,57 +390,26 @@ const screenshotGuides: Record<string, string> = {
>
全天
</button>
<button
v-for="opt in commonOnlineTimes"
:key="`start-${opt}`"
type="button"
class="radio-btn"
:class="{
active: isOnlineStartPresetActive(opt),
disabled: disabledOnlineStartOptions.includes(opt),
}"
:disabled="disabledOnlineStartOptions.includes(opt)"
@click="selectOnlineStart(opt)"
>
{{ opt }}
</button>
<div class="online-time-range">
<input
v-model="form.online_start"
type="time"
class="online-time-input"
aria-label="在线开始时间"
@change="handleOnlineStartChange(form.online_start)"
/>
<span class="online-time-sep"></span>
<input
v-model="form.online_end"
type="time"
class="online-time-input"
aria-label="在线结束时间"
@change="handleOnlineEndChange(form.online_end)"
/>
</div>
</div>
</div>
<div class="time-field-group">
<van-field
v-model="form.online_end"
label="在线结束"
required
type="time"
class="publish-field time-field"
@update:model-value="handleOnlineEndChange"
/>
<div class="radio-group small time-preset-chips">
<button
type="button"
class="radio-btn"
:class="{ active: isAllDayOnline() }"
@click="selectAllDayOnline"
>
全天
</button>
<button
v-for="opt in commonOnlineTimes"
:key="`end-${opt}`"
type="button"
class="radio-btn"
:class="{
active: isOnlineEndPresetActive(opt),
disabled: disabledOnlineEndOptions.includes(opt),
}"
:disabled="disabledOnlineEndOptions.includes(opt)"
@click="selectOnlineEnd(opt)"
>
{{ opt }}
</button>
</div>
</div>
</div>
</template>
</van-field>
<p class="field-hint">
此在线时间指的是百分百能够联系上您的时间若是在此期间联系不上您导致无法上号会扣除您的部分订单金额或上架押金在线时长太短可能无法上架请预留充足时间用于扫码以及冻结人脸请谨慎填写
</p>
@@ -72,7 +72,6 @@
.skin-group,
.region-panel,
.ratio-panel,
.time-preset-row,
.price-cell {
padding: 12px;
border-radius: var(--radius-8);
@@ -98,7 +97,6 @@
.field-row label span,
.input-block b,
.time-preset-row strong span,
.quantity-meta span,
.upload-copy span {
color: var(--color-danger);
@@ -178,7 +176,6 @@
}
.level-panel,
.time-preset-panel,
.skin-groups {
display: grid;
gap: 10px;
@@ -371,41 +368,41 @@
font-weight: 700;
}
.time-preset-row {
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 12px;
align-items: start;
.online-time-row {
align-items: center;
}
.time-preset-row strong {
color: var(--color-text-main);
font-size: 13px;
font-weight: 800;
line-height: 32px;
.online-time-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
min-width: 0;
}
.time-preset-content {
display: grid;
.online-time-range {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.time-preset-actions {
display: flex;
flex-wrap: wrap;
gap: 7px;
min-width: 0;
.online-time-sep {
flex: 0 0 auto;
color: #94a3b8;
font-size: 13px;
font-weight: 700;
}
.time-chip {
min-height: 30px;
padding: 0 12px;
flex: 0 0 auto;
min-height: 32px;
padding: 0 14px;
border: 1px solid #d6dce5;
border-radius: 15px;
border-radius: 999px;
background: #fff;
color: #596474;
font-size: 12px;
font-size: 13px;
font-weight: 800;
cursor: pointer;
transition:
@@ -414,14 +411,23 @@
color 0.16s ease;
}
.time-chip:hover {
border-color: #c4ccd8;
}
.time-chip.active {
border-color: var(--color-orange-primary);
background: var(--color-orange-primary);
color: #fff;
box-shadow: 0 4px 10px rgba(255, 106, 0, 0.22);
}
.time-input {
width: 118px;
width: 128px;
}
.online-time-range :deep(.el-input__wrapper) {
border-radius: 999px;
}
.login-region-panel {
@@ -927,12 +933,21 @@
@media (max-width: 860px) {
.field-row,
.time-preset-row,
.quantity-header,
.quantity-item {
grid-template-columns: 1fr;
}
.online-time-range {
width: 100%;
}
.time-input {
flex: 1;
width: auto;
min-width: 0;
}
.field-hint {
grid-column: auto;
}
@@ -18,7 +18,6 @@ import GuideImage from '@/components/GuideImage.vue'
import tencentSecurityGuide from '@/assets/tencent-security-guide.webp'
const {
commonOnlineTimes,
dailyLossOptions,
formatNumber,
loading,
@@ -39,8 +38,6 @@ const {
requiredScreenshotCount,
isEditMode,
submitButtonText,
disabledOnlineStartOptions,
disabledOnlineEndOptions,
serverOptions,
faceOptions,
rankOptions,
@@ -85,8 +82,6 @@ const {
getScreenshotLimitHint,
isAllDayOnline,
selectAllDayOnline,
selectOnlineStart,
selectOnlineEnd,
handleOnlineStartChange,
handleOnlineEndChange,
handleFireLevelInput,
@@ -344,81 +339,51 @@ function selectDailyLoss(value: string | number) {
/>
</div>
<div class="time-preset-panel">
<div class="time-preset-row">
<strong>在线开始<span>*</span></strong>
<div class="time-preset-content">
<div class="field-row panel-field-row online-time-row">
<label>在线时间<span>*</span></label>
<div class="online-time-controls">
<button
type="button"
class="time-chip"
:class="{ active: isAllDayOnline() }"
@click="selectAllDayOnline"
>
全天
</button>
<div class="online-time-range">
<el-time-picker
v-model="form.online_start"
value-format="HH:mm"
format="HH:mm"
placeholder="开始时间"
placeholder="开始"
class="time-input"
@change="handleOnlineStartChange"
/>
<div class="time-preset-actions">
<button
type="button"
class="time-chip"
:class="{ active: isAllDayOnline() }"
@click="selectAllDayOnline"
>
全天
</button>
<OptionChips
:options="commonOnlineTimes"
:model-value="isAllDayOnline() ? '' : form.online_start"
:disabled-values="disabledOnlineStartOptions"
key-prefix="start-"
@select="selectOnlineStart"
/>
</div>
</div>
</div>
<div class="time-preset-row">
<strong>在线结束<span>*</span></strong>
<div class="time-preset-content">
<span class="online-time-sep"></span>
<el-time-picker
v-model="form.online_end"
value-format="HH:mm"
format="HH:mm"
placeholder="结束时间"
placeholder="结束"
class="time-input"
@change="handleOnlineEndChange"
/>
<div class="time-preset-actions">
<button
type="button"
class="time-chip"
:class="{ active: isAllDayOnline() }"
@click="selectAllDayOnline"
>
全天
</button>
<OptionChips
:options="commonOnlineTimes"
:model-value="isAllDayOnline() ? '' : form.online_end"
:disabled-values="disabledOnlineEndOptions"
key-prefix="end-"
@select="selectOnlineEnd"
/>
</div>
</div>
</div>
<p class="field-hint">请填写能稳定联系上您的时间便于扫码冻结人脸和订单交接</p>
</div>
<p class="field-hint">请填写能稳定联系上您的时间便于扫码冻结人脸和订单交接</p>
<div v-if="banRecordOptions.length" class="field-row">
<div v-if="banRecordOptions.length" class="field-row panel-field-row">
<label>封禁记录<span>*</span></label>
<OptionChips
:options="banRecordOptions"
:model-value="form.ban_record"
@select="form.ban_record = String($event)"
/>
<p v-if="showBanRecordRiskHint" class="field-hint">
刚出租结算完的账号需冷号七天才可再次出租IP频繁变动会导致封号
</p>
</div>
<p v-if="showBanRecordRiskHint && banRecordOptions.length" class="field-hint">
刚出租结算完的账号需冷号七天才可再次出租IP频繁变动会导致封号
</p>
<div v-if="regionOptions.length" class="region-panel login-region-panel">
<div class="region-title">