修复卖家发布页样式和全天时间选择

This commit is contained in:
yml2213
2026-06-04 21:45:07 +08:00
parent 4ebf7f75fe
commit c1b3dba83d
10 changed files with 2045 additions and 216 deletions
@@ -0,0 +1,666 @@
<script setup lang="ts">
import { showDialog, showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue'
import { usePublishForm } from '@/features/seller/composables/usePublishForm'
const {
commonOnlineTimes,
dailyLossOptions,
formatNumber,
router,
loading,
uploading,
fileInput,
activeUploadKey,
form,
quantityValues,
quantityModes,
screenshotFiles,
selectedSkins,
serverOptions,
faceOptions,
rankOptions,
insuranceOptions,
levelOptions,
loginMethodOptions,
regionOptions,
banRecordOptions,
skinGroups,
quantityItems,
screenshotSlots,
priceConfig,
fireLevelPlaceholder,
coinMAmount,
dailyLossMAmount,
calculatedDefaultSaleRatio,
maxAcceleratedSaleRatio,
calculatedCoinBasePrice,
calculatedConsumablePrice,
calculatedSellerPrice,
calculatedRatioText,
calculatedDefaultSaleRatioText,
saleRatioRangeText,
acceleratedSaleRatioPlaceholder,
recommendedDepositAmount,
hasAcceleratedSaleRatioInput,
isQuantityItemDisabled,
handleResetDraft,
toggleSkin,
toggleRegion,
triggerUpload,
handleScreenshotUpload,
removeScreenshot,
getScreenshotPreviewURL,
handleFireLevelInput,
handleAcceleratedSaleRatioInput,
useRecommendedDeposit,
setQuantityMode,
clampAcceleratedSaleRatioInput,
useReferenceSaleRatio,
useMaxAcceleratedSaleRatio,
handleSubmit,
isScreenshotRequired,
} = usePublishForm({
draftKey: 'hfb.mobile.publish.draft',
submitSuccessPath: '/m/profile',
async confirmReset() {
await showDialog({
title: '重置发布内容',
message: '将清空当前填写内容和本地草稿。',
confirmButtonText: '重置',
cancelButtonText: '取消',
showCancelButton: true,
})
},
notifySuccess: (message) => showToast({ message, icon: 'passed' }),
notifyWarning: (message) => showToast({ message, icon: 'warning-o' }),
notifyError: (message) => showToast({ message, icon: 'cross' }),
})
function selectRadio<T extends string>(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
}
</script>
<template>
<main class="mobile-publish">
<header class="page-header">
<button class="back-btn" type="button" @click="router.back()">
<van-icon name="arrow-left" :size="20" />
</button>
<h1>发布账号</h1>
<span class="header-spacer"></span>
</header>
<section class="form-body">
<div class="form-section">
<h2 class="section-title">基础资料</h2>
<van-field label="区服" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in serverOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.server_region === opt }"
@click="selectRadio(opt, (value) => (form.server_region = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<van-field label="是否本人人脸" class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in faceOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.face_owner === opt }"
@click="selectRadio(opt, (value) => (form.face_owner = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<van-field
v-model="form.haf_coin_amount"
label="哈夫币/M"
type="digit"
required
placeholder="只填写仓库右上角纯币数额,总资产不计算在内;100M 写 100"
class="publish-field"
/>
<van-field label="段位" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in rankOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.rank_level === opt }"
@click="selectRadio(opt, (value) => (form.rank_level = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<van-field
v-model="form.secret_kd"
label="绝密KD"
type="number"
placeholder="填写绝密KD,如果数据对不上买家可以申请退款"
class="publish-field"
/>
<van-field
:model-value="form.fire_level"
label="烽火等级"
type="digit"
required
:placeholder="fireLevelPlaceholder"
class="publish-field"
@update:model-value="handleFireLevelInput"
/>
<van-field label="赛季保险" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in insuranceOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.season_insurance === opt }"
@click="selectRadio(opt, (value) => (form.season_insurance = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<div class="level-panel">
<div class="level-row">
<div class="level-label"><span>*</span>体力</div>
<div class="level-options">
<button
v-for="opt in levelOptions"
:key="`stamina-${opt}`"
type="button"
class="level-btn"
:class="{ active: form.stamina_level === opt }"
@click="selectRadio(opt, (value) => (form.stamina_level = value))"
>
{{ opt }}
</button>
</div>
</div>
<div class="level-row">
<div class="level-label"><span>*</span>负重</div>
<div class="level-options">
<button
v-for="opt in levelOptions"
:key="`load-${opt}`"
type="button"
class="level-btn"
:class="{ active: form.load_level === opt }"
@click="selectRadio(opt, (value) => (form.load_level = value))"
>
{{ opt }}
</button>
</div>
</div>
</div>
</div>
<div class="form-section">
<h2 class="section-title">额外消耗品</h2>
<div
v-for="item in quantityItems"
:key="item.key"
class="quantity-row"
:class="{ disabled: isQuantityItemDisabled(item) }"
>
<div class="quantity-card">
<div class="quantity-meta">
<strong
class="backend-hint-target"
:class="{ 'has-hint': Boolean(item.placeholder) }"
:data-hint="item.placeholder"
:tabindex="item.placeholder ? 0 : -1"
>
<span>*</span>{{ item.label }}
</strong>
<small>{{ isQuantityItemDisabled(item) ? "3*3 已包含,不能填写" : item.price }}</small>
</div>
<input
v-model="quantityValues[item.key]"
type="number"
inputmode="numeric"
min="0"
placeholder="0"
class="quantity-input"
:disabled="isQuantityItemDisabled(item)"
/>
</div>
<div class="mode-toggle">
<button
type="button"
:class="{ active: quantityModes[item.key] === '赠送' }"
:disabled="isQuantityItemDisabled(item)"
@click="setQuantityMode(item, '赠送')"
>
赠送
</button>
<button
type="button"
:class="{ active: (quantityModes[item.key] || '收费') === '收费' }"
:disabled="isQuantityItemDisabled(item)"
@click="setQuantityMode(item, '收费')"
>
收费
</button>
</div>
</div>
</div>
<div class="form-section">
<h2 class="section-title">皮肤与交接</h2>
<div class="skin-groups">
<div v-for="group in skinGroups" :key="group.key" class="skin-group">
<div class="skin-group-title">
<span>{{ group.title }}</span>
<small>
{{
group.options.filter((skin) => selectedSkins.includes(skin))
.length
}}
</small>
</div>
<div class="multi-chip-group">
<button
v-for="skin in group.options"
:key="skin"
type="button"
class="radio-btn"
:class="{ active: selectedSkins.includes(skin) }"
@click="toggleSkin(skin)"
>
{{ skin }}
</button>
</div>
</div>
</div>
<van-field label="上号方式" class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in loginMethodOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.login_method === opt }"
@click="selectRadio(opt, (value) => (form.login_method = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<p class="field-hint">
优先推荐账密登录出售速度约为扫码的数倍两种方式安全性一致账密登录更便捷请自行衡量
</p>
<div class="time-row">
<div class="time-field-group">
<van-field
v-model="form.online_start"
label="在线开始"
type="time"
class="publish-field time-field"
/>
<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="`start-${opt}`"
type="button"
class="radio-btn"
:class="{ active: isOnlineStartPresetActive(opt) }"
@click="selectOnlineStart(opt)"
>
{{ opt }}
</button>
</div>
</div>
<div class="time-field-group">
<van-field
v-model="form.online_end"
label="在线结束"
type="time"
class="publish-field time-field"
/>
<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) }"
@click="selectOnlineEnd(opt)"
>
{{ opt }}
</button>
</div>
</div>
</div>
<p class="field-hint">
此在线时间指的是百分百能够联系上您的时间若是在此期间联系不上您导致无法上号会扣除您的部分订单金额或上架押金在线时长太短可能无法上架请预留充足时间用于扫码以及冻结人脸请谨慎填写
</p>
<van-field label="封禁记录" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="opt in banRecordOptions"
:key="opt"
type="button"
class="radio-btn"
:class="{ active: form.ban_record === opt }"
@click="selectRadio(opt, (value) => (form.ban_record = value))"
>
{{ opt }}
</button>
</div>
</template>
</van-field>
<div class="region-panel">
<div class="region-title">
<span>常用登录地区</span>
<small>已选 {{ form.common_regions.length }}</small>
</div>
<div class="region-grid">
<button
v-for="region in regionOptions"
:key="region"
type="button"
class="region-btn"
:class="{ active: form.common_regions.includes(region) }"
@click="toggleRegion(region)"
>
{{ region }}
</button>
</div>
</div>
<p class="field-hint">
推荐勾选账号常用登录地便于同地区玩家租用可大幅减少账号触发异地登录保护
</p>
</div>
<div class="form-section">
<h2 class="section-title">截图材料</h2>
<div class="upload-list">
<div v-for="slot in screenshotSlots" :key="slot.key" class="upload-line">
<div class="upload-meta">
<strong
class="upload-title"
:class="{ 'has-hint': Boolean(slot.hint) }"
:data-hint="slot.hint"
:tabindex="slot.hint ? 0 : -1"
>
{{ slot.label }}<span v-if="isScreenshotRequired(slot)">*</span>
</strong>
<small>{{ slot.hint }}</small>
</div>
<div v-if="screenshotFiles[slot.key]" class="upload-preview">
<img :src="getScreenshotPreviewURL(slot.key)" :alt="slot.label" />
<button type="button" @click="removeScreenshot(slot.key)">移除</button>
</div>
<button
v-else
type="button"
class="upload-add"
:disabled="uploading"
@click="triggerUpload(slot.key)"
>
<van-icon name="photograph" :size="22" color="#999" />
<span>{{ uploading && activeUploadKey === slot.key ? "上传中" : "上传" }}</span>
</button>
</div>
</div>
<input
ref="fileInput"
type="file"
accept="image/jpeg,image/png,image/webp"
style="display: none"
@change="handleScreenshotUpload"
/>
</div>
<div class="form-section">
<h2 class="section-title">押金与价格</h2>
<van-field
v-model="form.deposit_amount"
label="押金"
type="number"
required
:placeholder="priceConfig.deposit_placeholder"
suffix="元"
class="publish-field"
>
<template #label>
<span class="hint-label" :data-hint="priceConfig.deposit_placeholder" tabindex="0">押金</span>
</template>
</van-field>
<button class="deposit-recommend-btn" type="button" @click="useRecommendedDeposit">
推荐押金 ¥{{ recommendedDepositAmount }}
</button>
<van-field label="每日损耗" required class="publish-field">
<template #input>
<div class="radio-group">
<button
v-for="loss in dailyLossOptions"
:key="loss"
type="button"
class="radio-btn"
:class="{ active: dailyLossMAmount === loss }"
@click="form.daily_loss_m = loss"
>
{{ loss }}M/
</button>
</div>
</template>
</van-field>
<p class="field-hint">
比例调整在默认10M/每增加10M出租比例相应+1请根据实际需求合理设置感谢您的配合
</p>
<div class="ratio-panel">
<div class="ratio-reference">
<span>参考比例</span>
<strong>{{ calculatedDefaultSaleRatioText }}</strong>
</div>
<p class="ratio-range">{{ saleRatioRangeText }}比例越高出租速度越快自行选择</p>
<div class="ratio-mode-grid">
<button
type="button"
class="ratio-mode-btn"
:class="{ active: !hasAcceleratedSaleRatioInput() }"
:disabled="calculatedDefaultSaleRatio <= 0"
@click="useReferenceSaleRatio"
>
<strong>参考比例</strong>
<span>
{{
calculatedDefaultSaleRatio > 0
? `1元=${formatNumber(calculatedDefaultSaleRatio)}`
: "自动计算"
}}
</span>
</button>
<button
type="button"
class="ratio-mode-btn"
:class="{ active: hasAcceleratedSaleRatioInput() }"
:disabled="maxAcceleratedSaleRatio <= 0"
@click="useMaxAcceleratedSaleRatio"
>
<strong>加速比例</strong>
<span>
{{
maxAcceleratedSaleRatio > 0
? `1元=${formatNumber(calculatedDefaultSaleRatio)}~${formatNumber(maxAcceleratedSaleRatio)}`
: "自动计算"
}}
</span>
</button>
</div>
<van-field
:model-value="form.accelerated_sale_ratio"
label="自定比例"
type="number"
:placeholder="acceleratedSaleRatioPlaceholder"
class="publish-field ratio-input-field"
@update:model-value="handleAcceleratedSaleRatioInput"
@blur="clampAcceleratedSaleRatioInput"
/>
<p class="field-hint ratio-input-hint">
留空使用参考比例填写后范围为参考比例到参考比例+10请根据实际需求合理设置
</p>
</div>
<div class="result-grid">
<van-field
:model-value="calculatedRatioText"
label="卖家比例"
readonly
:placeholder="priceConfig.ratio_description"
class="publish-field result-field"
>
<template #label>
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0">卖家比例</span>
</template>
</van-field>
<van-field
:model-value="calculatedCoinBasePrice ? `¥${calculatedCoinBasePrice}` : ''"
label="纯币基础价"
readonly
required
:placeholder="priceConfig.price_placeholder"
class="publish-field result-field"
>
<template #label>
<span class="hint-label" :data-hint="priceConfig.price_placeholder" tabindex="0">纯币基础价</span>
</template>
</van-field>
<van-field
:model-value="`¥${calculatedConsumablePrice}`"
label="额外消耗品"
readonly
class="publish-field result-field"
/>
<van-field
:model-value="calculatedSellerPrice ? `¥${calculatedSellerPrice}` : ''"
label="卖家价格"
readonly
required
:placeholder="priceConfig.price_placeholder"
class="publish-field result-field total-price-field"
/>
</div>
<p class="price-breakdown-hint">
卖家价格 = 纯币基础价 + 额外消耗品
</p>
<van-field
v-model="form.remark"
label="备注"
type="textarea"
rows="3"
autosize
placeholder="如有一些不可使用的物资,请在此备注,并且在买家下单后,主动在群聊处,再次提醒买家"
class="publish-field"
/>
</div>
<div class="publish-actions">
<van-button
round
class="reset-btn"
:disabled="loading"
@click="handleResetDraft"
>
重置
</van-button>
<van-button
type="primary"
round
class="submit-btn"
:loading="loading"
loading-text="发布中..."
@click="handleSubmit"
>
保存发布
</van-button>
</div>
</section>
<MobileBottomNav />
</main>
</template>
<style scoped src="./MobileSellerListingCreateView.css"></style>