增加前端格式检查配置
This commit is contained in:
@@ -73,11 +73,11 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
selectedSkins,
|
||||
})
|
||||
const uploadedScreenshotCount = computed(
|
||||
() => pricing.screenshotSlots.value.filter((item) => getScreenshotCount(item.key) > 0).length,
|
||||
() => pricing.screenshotSlots.value.filter(item => getScreenshotCount(item.key) > 0).length
|
||||
)
|
||||
const requiredScreenshotCount = ref(0)
|
||||
const canPublishAfterAgreements = computed(
|
||||
() => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value,
|
||||
() => virtualAssetSaleAgreementChecked.value && sellerAgreementChecked.value
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
@@ -98,7 +98,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
() => {
|
||||
saveDraft()
|
||||
},
|
||||
{ deep: true },
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
@@ -106,7 +106,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
() => {
|
||||
syncRecommendedDeposit()
|
||||
},
|
||||
{ immediate: true },
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
@@ -114,15 +114,17 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
() => {
|
||||
clearForbiddenQuantityItems()
|
||||
},
|
||||
{ deep: true },
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
[pricing.screenshotSlots, () => form.ban_record],
|
||||
() => {
|
||||
requiredScreenshotCount.value = pricing.screenshotSlots.value.filter((item) => isScreenshotRequired(item)).length
|
||||
requiredScreenshotCount.value = pricing.screenshotSlots.value.filter(item =>
|
||||
isScreenshotRequired(item)
|
||||
).length
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
async function loadPublishOptions() {
|
||||
@@ -186,7 +188,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
quantityModes,
|
||||
screenshotFiles,
|
||||
selectedSkins: selectedSkins.value,
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -256,13 +258,13 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
|
||||
function toggleSkin(skin: string) {
|
||||
selectedSkins.value = selectedSkins.value.includes(skin)
|
||||
? selectedSkins.value.filter((item) => item !== skin)
|
||||
? selectedSkins.value.filter(item => item !== skin)
|
||||
: [...selectedSkins.value, skin]
|
||||
}
|
||||
|
||||
function toggleRegion(region: string) {
|
||||
form.common_regions = form.common_regions.includes(region)
|
||||
? form.common_regions.filter((item) => item !== region)
|
||||
? form.common_regions.filter(item => item !== region)
|
||||
: [...form.common_regions, region]
|
||||
}
|
||||
|
||||
@@ -288,7 +290,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
}
|
||||
const uploadFiles = files.slice(0, availableCount)
|
||||
if (files.length > availableCount) {
|
||||
options.notifyWarning(`${getScreenshotLabel(key)}最多上传${getScreenshotLimit(key)}张,本次仅上传${availableCount}张`)
|
||||
options.notifyWarning(
|
||||
`${getScreenshotLabel(key)}最多上传${getScreenshotLimit(key)}张,本次仅上传${availableCount}张`
|
||||
)
|
||||
}
|
||||
uploading.value = true
|
||||
try {
|
||||
@@ -302,7 +306,9 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
options.notifySuccess(uploadFiles.length > 1 ? `已上传${uploadFiles.length}张截图` : '截图已上传')
|
||||
options.notifySuccess(
|
||||
uploadFiles.length > 1 ? `已上传${uploadFiles.length}张截图` : '截图已上传'
|
||||
)
|
||||
} catch (error) {
|
||||
options.notifyError(readError(error, '截图上传失败'))
|
||||
} finally {
|
||||
@@ -353,7 +359,12 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
async function hydrateScreenshotPreviews() {
|
||||
for (const [key, fileURLs] of Object.entries(screenshotFiles)) {
|
||||
for (const [index, fileURL] of fileURLs.entries()) {
|
||||
if (!fileURL || screenshotPreviews[key]?.[index] || !fileURL.startsWith('/api/files/object')) continue
|
||||
if (
|
||||
!fileURL ||
|
||||
screenshotPreviews[key]?.[index] ||
|
||||
!fileURL.startsWith('/api/files/object')
|
||||
)
|
||||
continue
|
||||
try {
|
||||
const blob = await fetchFileBlobByURL(fileURL)
|
||||
setScreenshotPreview(key, index, URL.createObjectURL(blob))
|
||||
@@ -411,14 +422,18 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
}
|
||||
|
||||
function clampAcceleratedSaleRatioInput() {
|
||||
if (!pricing.hasAcceleratedSaleRatioInput() || pricing.calculatedDefaultSaleRatio.value <= 0) return
|
||||
if (!pricing.hasAcceleratedSaleRatioInput() || pricing.calculatedDefaultSaleRatio.value <= 0)
|
||||
return
|
||||
const ratio = Number(form.accelerated_sale_ratio)
|
||||
if (!Number.isFinite(ratio)) {
|
||||
form.accelerated_sale_ratio = ''
|
||||
return
|
||||
}
|
||||
form.accelerated_sale_ratio = roundRatio(
|
||||
Math.min(Math.max(ratio, pricing.calculatedDefaultSaleRatio.value), pricing.maxAcceleratedSaleRatio.value),
|
||||
Math.min(
|
||||
Math.max(ratio, pricing.calculatedDefaultSaleRatio.value),
|
||||
pricing.maxAcceleratedSaleRatio.value
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -460,7 +475,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
options.notifySuccess(
|
||||
listing.status === 'published' && listing.review_status === 'approved'
|
||||
? '发布成功,已上架'
|
||||
: '发布成功,等待后台审核',
|
||||
: '发布成功,等待后台审核'
|
||||
)
|
||||
await router.push(options.submitSuccessPath)
|
||||
} catch (error) {
|
||||
@@ -475,7 +490,8 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
if (pricing.coinMAmount.value <= 0) return '请填写哈夫币/M'
|
||||
if (!form.rank_level) return '请选择段位'
|
||||
if (!form.fire_level) return '请填写烽火等级'
|
||||
if (Number(form.fire_level) < pricing.fireLevelMin.value) return `烽火等级低于${pricing.fireLevelMin.value}级的号无法发布`
|
||||
if (Number(form.fire_level) < pricing.fireLevelMin.value)
|
||||
return `烽火等级低于${pricing.fireLevelMin.value}级的号无法发布`
|
||||
if (!form.season_insurance) return '请选择赛季保险'
|
||||
if (!form.stamina_level) return '请选择体力等级'
|
||||
if (!form.load_level) return '请选择负重等级'
|
||||
@@ -483,27 +499,41 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
if (pricing.hasAcceleratedSaleRatioInput()) {
|
||||
const ratio = Number(form.accelerated_sale_ratio)
|
||||
if (!Number.isFinite(ratio) || ratio <= 0) return '加速出售比例格式不正确'
|
||||
if (pricing.calculatedDefaultSaleRatio.value > 0 && ratio < pricing.calculatedDefaultSaleRatio.value) {
|
||||
if (
|
||||
pricing.calculatedDefaultSaleRatio.value > 0 &&
|
||||
ratio < pricing.calculatedDefaultSaleRatio.value
|
||||
) {
|
||||
return `加速出售比例不能低于默认比例 1:${formatNumber(pricing.calculatedDefaultSaleRatio.value)}`
|
||||
}
|
||||
if (pricing.maxAcceleratedSaleRatio.value > 0 && ratio > pricing.maxAcceleratedSaleRatio.value) {
|
||||
if (
|
||||
pricing.maxAcceleratedSaleRatio.value > 0 &&
|
||||
ratio > pricing.maxAcceleratedSaleRatio.value
|
||||
) {
|
||||
return `加速出售比例不能超过 1:${formatNumber(pricing.maxAcceleratedSaleRatio.value)}`
|
||||
}
|
||||
}
|
||||
if (pricing.banRecordOptions.value.length && !form.ban_record) return '请选择封禁记录'
|
||||
if (form.deposit_amount === '' || Number(form.deposit_amount) < 0) return '请填写押金'
|
||||
if (!Number.isFinite(Number(form.deposit_amount))) return '押金格式不正确'
|
||||
if (pricing.recommendedDepositAmount.value > 0 && Number(form.deposit_amount) < pricing.recommendedDepositAmount.value) {
|
||||
if (
|
||||
pricing.recommendedDepositAmount.value > 0 &&
|
||||
Number(form.deposit_amount) < pricing.recommendedDepositAmount.value
|
||||
) {
|
||||
return `押金不能低于智能推荐 ¥${pricing.recommendedDepositAmount.value}`
|
||||
}
|
||||
if (pricing.calculatedConsumablePrice.value > 0 && Number(form.deposit_amount) <= pricing.calculatedConsumablePrice.value) {
|
||||
if (
|
||||
pricing.calculatedConsumablePrice.value > 0 &&
|
||||
Number(form.deposit_amount) <= pricing.calculatedConsumablePrice.value
|
||||
) {
|
||||
return `押金必须大于额外消耗品总价值 ¥${pricing.calculatedConsumablePrice.value}`
|
||||
}
|
||||
if (!pricing.calculatedFinalPrice.value) return '请完善币数、保险、体力和负重后再发布'
|
||||
if (!Number.isFinite(pricing.calculatedFinalPrice.value)) return '发布价格计算异常,请检查填写内容'
|
||||
if (!Number.isFinite(pricing.calculatedFinalPrice.value))
|
||||
return '发布价格计算异常,请检查填写内容'
|
||||
if (!canPublishAfterAgreements.value) return '请先阅读并勾选两份发布协议'
|
||||
for (const item of pricing.screenshotSlots.value) {
|
||||
if (isScreenshotRequired(item) && getScreenshotCount(item.key) === 0) return `请上传${item.label}`
|
||||
if (isScreenshotRequired(item) && getScreenshotCount(item.key) === 0)
|
||||
return `请上传${item.label}`
|
||||
}
|
||||
for (const item of pricing.quantityItems.value) {
|
||||
if (!pricing.isQuantityItemDisabled(item)) {
|
||||
@@ -558,7 +588,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
season_insurance: form.season_insurance,
|
||||
stamina_level: form.stamina_level,
|
||||
load_level: form.load_level,
|
||||
resources: pricing.quantityItems.value.map((item) => ({
|
||||
resources: pricing.quantityItems.value.map(item => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
price: item.price,
|
||||
@@ -566,7 +596,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
mode: pricing.isQuantityItemDisabled(item) ? '赠送' : quantityModes[item.key] || '收费',
|
||||
})),
|
||||
skin_groups: pricing.skinGroups.value.reduce<Record<string, string[]>>((groups, group) => {
|
||||
groups[group.key] = group.options.filter((skin) => selectedSkins.value.includes(skin))
|
||||
groups[group.key] = group.options.filter(skin => selectedSkins.value.includes(skin))
|
||||
return groups
|
||||
}, {}),
|
||||
screenshot_groups: buildScreenshotGroups(),
|
||||
@@ -614,7 +644,7 @@ export function usePublishForm(options: UsePublishFormOptions) {
|
||||
}
|
||||
|
||||
function getScreenshotLabel(key: ScreenshotKey) {
|
||||
return pricing.screenshotSlots.value.find((item) => item.key === key)?.label || '该截图'
|
||||
return pricing.screenshotSlots.value.find(item => item.key === key)?.label || '该截图'
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -128,13 +128,15 @@
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.16s ease, visibility 0.16s ease;
|
||||
transition:
|
||||
opacity 0.16s ease,
|
||||
visibility 0.16s ease;
|
||||
}
|
||||
|
||||
.hint-label::before,
|
||||
.backend-hint-target.has-hint::before,
|
||||
.upload-title.has-hint::before {
|
||||
content: "";
|
||||
content: '';
|
||||
top: 50%;
|
||||
left: calc(100% + 2px);
|
||||
transform: translateY(-50%);
|
||||
@@ -753,8 +755,6 @@
|
||||
background: #ff6a00 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width: 430px) {
|
||||
.time-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
@@ -83,9 +83,9 @@ const {
|
||||
showCancelButton: true,
|
||||
})
|
||||
},
|
||||
notifySuccess: (message) => showToast({ message, icon: 'passed' }),
|
||||
notifyWarning: (message) => showToast({ message, icon: 'warning-o' }),
|
||||
notifyError: (message) => showToast({ message, icon: 'cross' }),
|
||||
notifySuccess: message => showToast({ message, icon: 'passed' }),
|
||||
notifyWarning: message => showToast({ message, icon: 'warning-o' }),
|
||||
notifyError: message => showToast({ message, icon: 'cross' }),
|
||||
})
|
||||
|
||||
const agreementPopupVisible = ref(false)
|
||||
@@ -151,7 +151,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.server_region === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.server_region = value))"
|
||||
@click="selectRadio(opt, value => (form.server_region = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -168,7 +168,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.face_owner === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.face_owner = value))"
|
||||
@click="selectRadio(opt, value => (form.face_owner = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -194,7 +194,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.rank_level === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.rank_level = value))"
|
||||
@click="selectRadio(opt, value => (form.rank_level = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -227,7 +227,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.season_insurance === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.season_insurance = value))"
|
||||
@click="selectRadio(opt, value => (form.season_insurance = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -245,7 +245,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="level-btn"
|
||||
:class="{ active: form.stamina_level === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.stamina_level = value))"
|
||||
@click="selectRadio(opt, value => (form.stamina_level = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -260,7 +260,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="level-btn"
|
||||
:class="{ active: form.load_level === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.load_level = value))"
|
||||
@click="selectRadio(opt, value => (form.load_level = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -287,7 +287,9 @@ function selectOnlineEnd(value: string) {
|
||||
>
|
||||
<span>*</span>{{ item.label }}
|
||||
</strong>
|
||||
<small>{{ isQuantityItemDisabled(item) ? "3*3 已包含,不能填写" : item.price }}</small>
|
||||
<small>{{
|
||||
isQuantityItemDisabled(item) ? '3*3 已包含,不能填写' : item.price
|
||||
}}</small>
|
||||
</div>
|
||||
<input
|
||||
v-model="quantityValues[item.key]"
|
||||
@@ -327,10 +329,7 @@ function selectOnlineEnd(value: string) {
|
||||
<div class="skin-group-title">
|
||||
<span>{{ group.title }}</span>
|
||||
<small>
|
||||
{{
|
||||
group.options.filter((skin) => selectedSkins.includes(skin))
|
||||
.length
|
||||
}} 项
|
||||
{{ group.options.filter(skin => selectedSkins.includes(skin)).length }} 项
|
||||
</small>
|
||||
</div>
|
||||
<div class="multi-chip-group">
|
||||
@@ -357,7 +356,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.login_method === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.login_method = value))"
|
||||
@click="selectRadio(opt, value => (form.login_method = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -439,7 +438,7 @@ function selectOnlineEnd(value: string) {
|
||||
type="button"
|
||||
class="radio-btn"
|
||||
:class="{ active: form.ban_record === opt }"
|
||||
@click="selectRadio(opt, (value) => (form.ban_record = value))"
|
||||
@click="selectRadio(opt, value => (form.ban_record = value))"
|
||||
>
|
||||
{{ opt }}
|
||||
</button>
|
||||
@@ -484,11 +483,20 @@ function selectOnlineEnd(value: string) {
|
||||
{{ slot.label }}<span v-if="isScreenshotRequired(slot)">*</span>
|
||||
</strong>
|
||||
<small>{{ slot.hint }}</small>
|
||||
<small v-if="getScreenshotLimitHint(slot.key)">{{ getScreenshotLimitHint(slot.key) }}</small>
|
||||
<small v-if="getScreenshotLimitHint(slot.key)">{{
|
||||
getScreenshotLimitHint(slot.key)
|
||||
}}</small>
|
||||
</div>
|
||||
<div v-if="getScreenshotCount(slot.key)" class="upload-preview-list">
|
||||
<div v-for="(url, index) in getScreenshotURLs(slot.key)" :key="`${slot.key}-${url}`" class="upload-preview">
|
||||
<img :src="getScreenshotPreviewURL(slot.key, index)" :alt="`${slot.label}${index + 1}`" />
|
||||
<div
|
||||
v-for="(url, index) in getScreenshotURLs(slot.key)"
|
||||
:key="`${slot.key}-${url}`"
|
||||
class="upload-preview"
|
||||
>
|
||||
<img
|
||||
:src="getScreenshotPreviewURL(slot.key, index)"
|
||||
:alt="`${slot.label}${index + 1}`"
|
||||
/>
|
||||
<button type="button" @click="removeScreenshot(slot.key, index)">移除</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -499,7 +507,7 @@ function selectOnlineEnd(value: string) {
|
||||
@click="triggerUpload(slot.key)"
|
||||
>
|
||||
<van-icon name="photograph" :size="22" color="#999" />
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? "上传中" : "继续上传" }}</span>
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? '上传中' : '继续上传' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -510,7 +518,7 @@ function selectOnlineEnd(value: string) {
|
||||
@click="triggerUpload(slot.key)"
|
||||
>
|
||||
<van-icon name="photograph" :size="22" color="#999" />
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? "上传中" : "上传" }}</span>
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? '上传中' : '上传' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -537,7 +545,11 @@ function selectOnlineEnd(value: string) {
|
||||
class="publish-field"
|
||||
>
|
||||
<template #label>
|
||||
<span class="hint-label deposit-label" :data-hint="priceConfig.deposit_placeholder" tabindex="0">
|
||||
<span
|
||||
class="hint-label deposit-label"
|
||||
:data-hint="priceConfig.deposit_placeholder"
|
||||
tabindex="0"
|
||||
>
|
||||
押金<em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em>
|
||||
</span>
|
||||
</template>
|
||||
@@ -583,7 +595,7 @@ function selectOnlineEnd(value: string) {
|
||||
{{
|
||||
calculatedDefaultSaleRatio > 0
|
||||
? `1元=${formatNumber(calculatedDefaultSaleRatio)}万`
|
||||
: "自动计算"
|
||||
: '自动计算'
|
||||
}}
|
||||
</span>
|
||||
</button>
|
||||
@@ -599,7 +611,7 @@ function selectOnlineEnd(value: string) {
|
||||
{{
|
||||
maxAcceleratedSaleRatio > 0
|
||||
? `1元=${formatNumber(calculatedDefaultSaleRatio)}~${formatNumber(maxAcceleratedSaleRatio)}万`
|
||||
: "自动计算"
|
||||
: '自动计算'
|
||||
}}
|
||||
</span>
|
||||
</button>
|
||||
@@ -626,7 +638,9 @@ function selectOnlineEnd(value: string) {
|
||||
class="publish-field result-field"
|
||||
>
|
||||
<template #label>
|
||||
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0">卖家比例</span>
|
||||
<span class="hint-label" :data-hint="priceConfig.ratio_description" tabindex="0"
|
||||
>卖家比例</span
|
||||
>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
@@ -638,7 +652,9 @@ function selectOnlineEnd(value: string) {
|
||||
class="publish-field result-field"
|
||||
>
|
||||
<template #label>
|
||||
<span class="hint-label" :data-hint="priceConfig.price_placeholder" tabindex="0">纯币基础价</span>
|
||||
<span class="hint-label" :data-hint="priceConfig.price_placeholder" tabindex="0"
|
||||
>纯币基础价</span
|
||||
>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field
|
||||
@@ -656,9 +672,7 @@ function selectOnlineEnd(value: string) {
|
||||
class="publish-field result-field total-price-field"
|
||||
/>
|
||||
</div>
|
||||
<p class="price-breakdown-hint">
|
||||
卖家价格 = 纯币基础价 + 额外消耗品。
|
||||
</p>
|
||||
<p class="price-breakdown-hint">卖家价格 = 纯币基础价 + 额外消耗品。</p>
|
||||
|
||||
<van-field
|
||||
v-model="form.remark"
|
||||
@@ -699,12 +713,7 @@ function selectOnlineEnd(value: string) {
|
||||
</div>
|
||||
|
||||
<div class="publish-actions">
|
||||
<van-button
|
||||
round
|
||||
class="reset-btn"
|
||||
:disabled="loading"
|
||||
@click="handleResetDraft"
|
||||
>
|
||||
<van-button round class="reset-btn" :disabled="loading" @click="handleResetDraft">
|
||||
重置
|
||||
</van-button>
|
||||
<van-button
|
||||
@@ -721,7 +730,14 @@ function selectOnlineEnd(value: string) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<van-popup v-model:show="agreementPopupVisible" round closeable position="bottom" lock-scroll class="publish-agreement-popup">
|
||||
<van-popup
|
||||
v-model:show="agreementPopupVisible"
|
||||
round
|
||||
closeable
|
||||
position="bottom"
|
||||
lock-scroll
|
||||
class="publish-agreement-popup"
|
||||
>
|
||||
<div class="agreement-popup-body">
|
||||
<h3>{{ activeAgreementTitle }}</h3>
|
||||
<div class="agreement-popup-content">{{ activeAgreementContent }}</div>
|
||||
|
||||
@@ -12,9 +12,9 @@ const loading = ref(false)
|
||||
const orders = ref<Order[]>([])
|
||||
const status = ref('')
|
||||
|
||||
const sellerOrders = computed(() => orders.value.filter((order) => order.owner_id === session.userId))
|
||||
const sellerOrders = computed(() => orders.value.filter(order => order.owner_id === session.userId))
|
||||
const todoOrders = computed(() =>
|
||||
sellerOrders.value.filter((order) =>
|
||||
sellerOrders.value.filter(order =>
|
||||
[
|
||||
'pending_handoff',
|
||||
'renting',
|
||||
@@ -24,22 +24,28 @@ const todoOrders = computed(() =>
|
||||
'checkout_disputing',
|
||||
'disputing',
|
||||
'abnormal',
|
||||
].includes(order.status),
|
||||
),
|
||||
].includes(order.status)
|
||||
)
|
||||
)
|
||||
const displayOrders = computed(() => {
|
||||
const source = todoOrders.value
|
||||
if (!status.value) return source
|
||||
return source.filter((order) => order.status === status.value)
|
||||
return source.filter(order => order.status === status.value)
|
||||
})
|
||||
const pendingHandoffCount = computed(
|
||||
() => sellerOrders.value.filter((order) => order.status === 'pending_handoff' && order.handoff_status === 'pending_owner').length,
|
||||
() =>
|
||||
sellerOrders.value.filter(
|
||||
order => order.status === 'pending_handoff' && order.handoff_status === 'pending_owner'
|
||||
).length
|
||||
)
|
||||
const pendingCheckoutCount = computed(
|
||||
() => sellerOrders.value.filter((order) => order.status === 'pending_checkout_confirm').length,
|
||||
() => sellerOrders.value.filter(order => order.status === 'pending_checkout_confirm').length
|
||||
)
|
||||
const abnormalCount = computed(
|
||||
() => sellerOrders.value.filter((order) => ['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status)).length,
|
||||
() =>
|
||||
sellerOrders.value.filter(order =>
|
||||
['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status)
|
||||
).length
|
||||
)
|
||||
|
||||
onMounted(loadOrders)
|
||||
@@ -54,10 +60,12 @@ async function loadOrders() {
|
||||
}
|
||||
|
||||
function actionText(order: Order) {
|
||||
if (order.status === 'pending_handoff' && order.handoff_status === 'pending_owner') return '提交交接'
|
||||
if (order.status === 'pending_handoff' && order.handoff_status === 'pending_owner')
|
||||
return '提交交接'
|
||||
if (order.status === 'pending_checkout_confirm') return '处理结账'
|
||||
if (order.status === 'pending_checkout_accept') return '等待租客'
|
||||
if (['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status)) return '查看处理'
|
||||
if (['overdue', 'checkout_disputing', 'disputing', 'abnormal'].includes(order.status))
|
||||
return '查看处理'
|
||||
return '详情'
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
.publish-page :deep(.el-textarea__inner) {
|
||||
border-radius: var(--radius-8);
|
||||
box-shadow: 0 0 0 1px var(--color-border) inset;
|
||||
transition: box-shadow 0.2s ease, background-color 0.2s ease;
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.publish-page :deep(.el-input__wrapper:hover),
|
||||
@@ -37,7 +39,9 @@
|
||||
|
||||
.publish-page :deep(.el-input__wrapper.is-focus),
|
||||
.publish-page :deep(.el-textarea__inner:focus) {
|
||||
box-shadow: 0 0 0 1px var(--color-orange-primary) inset, 0 0 8px rgba(255, 106, 0, 0.15) !important;
|
||||
box-shadow:
|
||||
0 0 0 1px var(--color-orange-primary) inset,
|
||||
0 0 8px rgba(255, 106, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.publish-layout {
|
||||
@@ -211,7 +215,11 @@
|
||||
color: #596474;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, transform 0.16s ease;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
background 0.16s ease,
|
||||
color 0.16s ease,
|
||||
transform 0.16s ease;
|
||||
}
|
||||
|
||||
.level-btn,
|
||||
@@ -246,7 +254,9 @@
|
||||
.quantity-header,
|
||||
.quantity-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 3fr) minmax(80px, 1fr) minmax(100px, 1.2fr) minmax(140px, 1.5fr) minmax(80px, 1fr);
|
||||
grid-template-columns:
|
||||
minmax(180px, 3fr) minmax(80px, 1fr) minmax(100px, 1.2fr) minmax(140px, 1.5fr)
|
||||
minmax(80px, 1fr);
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -397,7 +407,10 @@
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
background 0.16s ease,
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.time-chip.active {
|
||||
@@ -548,7 +561,9 @@
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background 0.2s ease;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background 0.2s ease;
|
||||
}
|
||||
|
||||
.recommend-button:hover {
|
||||
@@ -636,7 +651,10 @@
|
||||
gap: 6px;
|
||||
border: 1px solid #d6dce5;
|
||||
background: #fff;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
background 0.16s ease,
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.ratio-custom-card strong {
|
||||
@@ -676,7 +694,9 @@
|
||||
min-height: 82px;
|
||||
padding: 14px;
|
||||
border-left: 4px solid #ccd5e0;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.price-cell:hover {
|
||||
@@ -883,7 +903,9 @@
|
||||
background-color: var(--color-orange-primary);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.summary-actions :deep(.el-button.btn-publish:hover) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { Delete, DocumentChecked, Picture, RefreshRight, UploadFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Delete,
|
||||
DocumentChecked,
|
||||
Picture,
|
||||
RefreshRight,
|
||||
UploadFilled,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@@ -88,9 +94,9 @@ const {
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
notifySuccess: (message) => ElMessage.success(message),
|
||||
notifyWarning: (message) => ElMessage.warning(message),
|
||||
notifyError: (message) => ElMessage.error(message),
|
||||
notifySuccess: message => ElMessage.success(message),
|
||||
notifyWarning: message => ElMessage.warning(message),
|
||||
notifyError: message => ElMessage.error(message),
|
||||
})
|
||||
|
||||
const agreementDialogVisible = ref(false)
|
||||
@@ -136,22 +142,41 @@ function selectOnlineEnd(value: string | number) {
|
||||
<PublishSection title="基础资料" :description="publishTitle">
|
||||
<div class="field-row panel-field-row">
|
||||
<label>区服<span>*</span></label>
|
||||
<OptionChips :options="serverOptions" :model-value="form.server_region" @select="form.server_region = String($event)" />
|
||||
<OptionChips
|
||||
:options="serverOptions"
|
||||
:model-value="form.server_region"
|
||||
@select="form.server_region = String($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="faceOptions.length" class="field-row panel-field-row">
|
||||
<label>是否本人人脸</label>
|
||||
<OptionChips :options="faceOptions" :model-value="form.face_owner" @select="form.face_owner = String($event)" />
|
||||
<OptionChips
|
||||
:options="faceOptions"
|
||||
:model-value="form.face_owner"
|
||||
@select="form.face_owner = String($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="compact-grid panel-input-grid">
|
||||
<label class="input-block">
|
||||
<span>哈夫币/M<b>*</b></span>
|
||||
<el-input-number v-model="form.haf_coin_amount" :min="0" :controls="false" placeholder="100M 写 100" />
|
||||
<el-input-number
|
||||
v-model="form.haf_coin_amount"
|
||||
:min="0"
|
||||
:controls="false"
|
||||
placeholder="100M 写 100"
|
||||
/>
|
||||
</label>
|
||||
<label class="input-block">
|
||||
<span>绝密KD</span>
|
||||
<el-input-number v-model="form.secret_kd" :min="0" :controls="false" :step="0.1" placeholder="填写绝密KD" />
|
||||
<el-input-number
|
||||
v-model="form.secret_kd"
|
||||
:min="0"
|
||||
:controls="false"
|
||||
:step="0.1"
|
||||
placeholder="填写绝密KD"
|
||||
/>
|
||||
</label>
|
||||
<label class="input-block">
|
||||
<span>烽火等级<b>*</b></span>
|
||||
@@ -167,7 +192,11 @@ function selectOnlineEnd(value: string | number) {
|
||||
|
||||
<div class="field-row panel-field-row">
|
||||
<label>段位<span>*</span></label>
|
||||
<OptionChips :options="rankOptions" :model-value="form.rank_level" @select="form.rank_level = String($event)" />
|
||||
<OptionChips
|
||||
:options="rankOptions"
|
||||
:model-value="form.rank_level"
|
||||
@select="form.rank_level = String($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="field-row panel-field-row">
|
||||
@@ -213,7 +242,11 @@ function selectOnlineEnd(value: string | number) {
|
||||
</div>
|
||||
</PublishSection>
|
||||
|
||||
<PublishSection v-if="quantityItems.length" title="额外消耗品" description="收费项会计入发布价格">
|
||||
<PublishSection
|
||||
v-if="quantityItems.length"
|
||||
title="额外消耗品"
|
||||
description="收费项会计入发布价格"
|
||||
>
|
||||
<div class="quantity-table">
|
||||
<div class="quantity-header">
|
||||
<span>物资</span>
|
||||
@@ -229,7 +262,9 @@ function selectOnlineEnd(value: string | number) {
|
||||
:class="{ disabled: isQuantityItemDisabled(item) }"
|
||||
>
|
||||
<div class="quantity-meta">
|
||||
<strong :title="item.placeholder || item.label"><span>*</span>{{ item.label }}</strong>
|
||||
<strong :title="item.placeholder || item.label"
|
||||
><span>*</span>{{ item.label }}</strong
|
||||
>
|
||||
<small v-if="item.placeholder">{{ item.placeholder }}</small>
|
||||
</div>
|
||||
<span class="quantity-price">{{ item.price }}</span>
|
||||
@@ -266,15 +301,15 @@ function selectOnlineEnd(value: string | number) {
|
||||
|
||||
<PublishSection title="皮肤与交接">
|
||||
<div v-if="skinGroups.length" class="skin-groups">
|
||||
<div
|
||||
v-for="group in skinGroups"
|
||||
:key="group.key"
|
||||
class="skin-group"
|
||||
>
|
||||
<div v-for="group in skinGroups" :key="group.key" class="skin-group">
|
||||
<div class="skin-title">
|
||||
<strong>{{ group.title }}</strong>
|
||||
</div>
|
||||
<OptionChips :options="group.options" :active-values="selectedSkins" @select="toggleSkinOption" />
|
||||
<OptionChips
|
||||
:options="group.options"
|
||||
:active-values="selectedSkins"
|
||||
@select="toggleSkinOption"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -285,7 +320,9 @@ function selectOnlineEnd(value: string | number) {
|
||||
:model-value="form.login_method"
|
||||
@select="form.login_method = String($event)"
|
||||
/>
|
||||
<p class="field-hint">优先推荐账密登录,出售速度通常更快;请结合账号安全情况自行选择。</p>
|
||||
<p class="field-hint">
|
||||
优先推荐账密登录,出售速度通常更快;请结合账号安全情况自行选择。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="time-preset-panel">
|
||||
@@ -300,7 +337,12 @@ function selectOnlineEnd(value: string | number) {
|
||||
class="time-input"
|
||||
/>
|
||||
<div class="time-preset-actions">
|
||||
<button type="button" class="time-chip" :class="{ active: isAllDayOnline() }" @click="selectAllDayOnline">
|
||||
<button
|
||||
type="button"
|
||||
class="time-chip"
|
||||
:class="{ active: isAllDayOnline() }"
|
||||
@click="selectAllDayOnline"
|
||||
>
|
||||
全天
|
||||
</button>
|
||||
<OptionChips
|
||||
@@ -323,7 +365,12 @@ function selectOnlineEnd(value: string | number) {
|
||||
class="time-input"
|
||||
/>
|
||||
<div class="time-preset-actions">
|
||||
<button type="button" class="time-chip" :class="{ active: isAllDayOnline() }" @click="selectAllDayOnline">
|
||||
<button
|
||||
type="button"
|
||||
class="time-chip"
|
||||
:class="{ active: isAllDayOnline() }"
|
||||
@click="selectAllDayOnline"
|
||||
>
|
||||
全天
|
||||
</button>
|
||||
<OptionChips
|
||||
@@ -340,7 +387,11 @@ function selectOnlineEnd(value: string | number) {
|
||||
|
||||
<div v-if="banRecordOptions.length" class="field-row">
|
||||
<label>封禁记录<span>*</span></label>
|
||||
<OptionChips :options="banRecordOptions" :model-value="form.ban_record" @select="form.ban_record = String($event)" />
|
||||
<OptionChips
|
||||
:options="banRecordOptions"
|
||||
:model-value="form.ban_record"
|
||||
@select="form.ban_record = String($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="regionOptions.length" class="region-panel login-region-panel">
|
||||
@@ -363,27 +414,53 @@ function selectOnlineEnd(value: string | number) {
|
||||
</div>
|
||||
</PublishSection>
|
||||
|
||||
<PublishSection title="截图材料" :description="`${uploadedScreenshotCount}/${screenshotSlots.length} 项已上传`">
|
||||
<PublishSection
|
||||
title="截图材料"
|
||||
:description="`${uploadedScreenshotCount}/${screenshotSlots.length} 项已上传`"
|
||||
>
|
||||
<div class="upload-grid">
|
||||
<div v-for="slot in screenshotSlots" :key="slot.key" class="upload-item">
|
||||
<div class="upload-copy">
|
||||
<strong>{{ slot.label }}<span v-if="isScreenshotRequired(slot)">*</span></strong>
|
||||
<small>{{ slot.hint }}</small>
|
||||
<em v-if="getScreenshotLimitHint(slot.key)">{{ getScreenshotLimitHint(slot.key) }}</em>
|
||||
<em v-if="getScreenshotLimitHint(slot.key)">{{
|
||||
getScreenshotLimitHint(slot.key)
|
||||
}}</em>
|
||||
</div>
|
||||
<div v-if="getScreenshotCount(slot.key)" class="upload-preview-list">
|
||||
<div v-for="(url, index) in getScreenshotURLs(slot.key)" :key="`${slot.key}-${url}`" class="upload-preview">
|
||||
<img :src="getScreenshotPreviewURL(slot.key, index)" :alt="`${slot.label}${index + 1}`" />
|
||||
<div
|
||||
v-for="(url, index) in getScreenshotURLs(slot.key)"
|
||||
:key="`${slot.key}-${url}`"
|
||||
class="upload-preview"
|
||||
>
|
||||
<img
|
||||
:src="getScreenshotPreviewURL(slot.key, index)"
|
||||
:alt="`${slot.label}${index + 1}`"
|
||||
/>
|
||||
<button type="button" @click="removeScreenshot(slot.key, index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
<button v-if="canAddScreenshot(slot.key)" type="button" class="upload-add" :disabled="uploading" @click="triggerUpload(slot.key)">
|
||||
<button
|
||||
v-if="canAddScreenshot(slot.key)"
|
||||
type="button"
|
||||
class="upload-add"
|
||||
:disabled="uploading"
|
||||
@click="triggerUpload(slot.key)"
|
||||
>
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? '上传中' : '继续上传' }}</span>
|
||||
<span>{{
|
||||
uploading && activeUploadKey === slot.key ? '上传中' : '继续上传'
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button v-else type="button" class="upload-add" :disabled="uploading" @click="triggerUpload(slot.key)">
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="upload-add"
|
||||
:disabled="uploading"
|
||||
@click="triggerUpload(slot.key)"
|
||||
>
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>{{ uploading && activeUploadKey === slot.key ? '上传中' : '上传' }}</span>
|
||||
</button>
|
||||
@@ -402,7 +479,10 @@ function selectOnlineEnd(value: string | number) {
|
||||
<PublishSection title="押金与价格" description="系统按资料自动计算售价">
|
||||
<div class="compact-grid two">
|
||||
<label class="input-block">
|
||||
<span class="deposit-label">押金<b>*</b><em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em></span>
|
||||
<span class="deposit-label"
|
||||
>押金<b>*</b
|
||||
><em v-if="priceConfig.deposit_hint">{{ priceConfig.deposit_hint }}</em></span
|
||||
>
|
||||
<div class="deposit-control">
|
||||
<el-input-number
|
||||
v-model="form.deposit_amount"
|
||||
@@ -416,7 +496,9 @@ function selectOnlineEnd(value: string | number) {
|
||||
</div>
|
||||
<div class="deposit-breakdown">
|
||||
<span v-for="item in depositBreakdownItems" :key="`${item.label}-${item.count}`">
|
||||
{{ item.label }}<template v-if="item.count > 1"> x{{ item.count }}</template> ¥{{ item.amount }}
|
||||
{{ item.label }}<template v-if="item.count > 1"> x{{ item.count }}</template> ¥{{
|
||||
item.amount
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
@@ -447,21 +529,37 @@ function selectOnlineEnd(value: string | number) {
|
||||
@click="useReferenceSaleRatio"
|
||||
>
|
||||
<strong>参考比例</strong>
|
||||
<span>{{ calculatedDefaultSaleRatio > 0 ? `1元=${formatNumber(calculatedDefaultSaleRatio)}万` : '自动计算' }}</span>
|
||||
<span>{{
|
||||
calculatedDefaultSaleRatio > 0
|
||||
? `1元=${formatNumber(calculatedDefaultSaleRatio)}万`
|
||||
: '自动计算'
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ratio-mode-btn"
|
||||
:class="{ active: form.accelerated_sale_ratio === maxAcceleratedSaleRatio && maxAcceleratedSaleRatio > 0 }"
|
||||
:class="{
|
||||
active:
|
||||
form.accelerated_sale_ratio === maxAcceleratedSaleRatio &&
|
||||
maxAcceleratedSaleRatio > 0,
|
||||
}"
|
||||
:disabled="maxAcceleratedSaleRatio <= 0"
|
||||
@click="useMaxAcceleratedSaleRatio"
|
||||
>
|
||||
<strong>最高比例 (加速)</strong>
|
||||
<span>{{ maxAcceleratedSaleRatio > 0 ? `最高 1元=${formatNumber(maxAcceleratedSaleRatio)}万` : '自动计算' }}</span>
|
||||
<span>{{
|
||||
maxAcceleratedSaleRatio > 0
|
||||
? `最高 1元=${formatNumber(maxAcceleratedSaleRatio)}万`
|
||||
: '自动计算'
|
||||
}}</span>
|
||||
</button>
|
||||
<div
|
||||
class="ratio-custom-card"
|
||||
:class="{ active: hasAcceleratedSaleRatioInput() && form.accelerated_sale_ratio !== maxAcceleratedSaleRatio }"
|
||||
:class="{
|
||||
active:
|
||||
hasAcceleratedSaleRatioInput() &&
|
||||
form.accelerated_sale_ratio !== maxAcceleratedSaleRatio,
|
||||
}"
|
||||
>
|
||||
<strong>自定比例</strong>
|
||||
<el-input-number
|
||||
@@ -577,14 +675,23 @@ function selectOnlineEnd(value: string | number) {
|
||||
>
|
||||
立即发布
|
||||
</el-button>
|
||||
<el-button :icon="DocumentChecked" :disabled="loading" @click="handleSaveDraft">保存草稿</el-button>
|
||||
<el-button :icon="RefreshRight" :disabled="loading" @click="handleResetDraft">重置草稿</el-button>
|
||||
<el-button :icon="DocumentChecked" :disabled="loading" @click="handleSaveDraft"
|
||||
>保存草稿</el-button
|
||||
>
|
||||
<el-button :icon="RefreshRight" :disabled="loading" @click="handleResetDraft"
|
||||
>重置草稿</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="agreementDialogVisible" :title="activeAgreementTitle" width="720px" class="publish-agreement-dialog">
|
||||
<el-dialog
|
||||
v-model="agreementDialogVisible"
|
||||
:title="activeAgreementTitle"
|
||||
width="720px"
|
||||
class="publish-agreement-dialog"
|
||||
>
|
||||
<div class="agreement-dialog-content">{{ activeAgreementContent }}</div>
|
||||
</el-dialog>
|
||||
</main>
|
||||
|
||||
@@ -3,7 +3,12 @@ import { Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import { fetchSellerListings, offlineListing, submitListingReview, type Listing } from '@/features/listings'
|
||||
import {
|
||||
fetchSellerListings,
|
||||
offlineListing,
|
||||
submitListingReview,
|
||||
type Listing,
|
||||
} from '@/features/listings'
|
||||
import { listingReviewStatusLabel, listingStatusLabel } from '@/utils/statusLabels'
|
||||
import { getListingSellerPrice } from '@/utils/listingDisplay'
|
||||
|
||||
@@ -15,15 +20,27 @@ const statusFilter = ref('all')
|
||||
|
||||
const displayListings = computed(() => {
|
||||
if (statusFilter.value === 'all') return listings.value
|
||||
if (statusFilter.value === 'pending') return listings.value.filter((item) => isPendingReview(item))
|
||||
return listings.value.filter((item) => item.status === statusFilter.value)
|
||||
if (statusFilter.value === 'pending') return listings.value.filter(item => isPendingReview(item))
|
||||
return listings.value.filter(item => item.status === statusFilter.value)
|
||||
})
|
||||
|
||||
const stats = computed(() => [
|
||||
{ key: 'all', label: '全部发布', value: listings.value.length },
|
||||
{ key: 'published', label: '已上架', value: listings.value.filter((item) => item.status === 'published').length },
|
||||
{ key: 'pending', label: '待审核', value: listings.value.filter((item) => isPendingReview(item)).length },
|
||||
{ key: 'offline', label: '已下架', value: listings.value.filter((item) => item.status === 'offline').length },
|
||||
{
|
||||
key: 'published',
|
||||
label: '已上架',
|
||||
value: listings.value.filter(item => item.status === 'published').length,
|
||||
},
|
||||
{
|
||||
key: 'pending',
|
||||
label: '待审核',
|
||||
value: listings.value.filter(item => isPendingReview(item)).length,
|
||||
},
|
||||
{
|
||||
key: 'offline',
|
||||
label: '已下架',
|
||||
value: listings.value.filter(item => item.status === 'offline').length,
|
||||
},
|
||||
])
|
||||
|
||||
onMounted(loadListings)
|
||||
@@ -45,7 +62,7 @@ async function submitReview(row: Listing) {
|
||||
ElMessage.success(
|
||||
listing.status === 'published' && listing.review_status === 'approved'
|
||||
? '已上架'
|
||||
: '已提交审核,等待后台处理',
|
||||
: '已提交审核,等待后台处理'
|
||||
)
|
||||
} finally {
|
||||
submittingID.value = null
|
||||
@@ -54,11 +71,15 @@ async function submitReview(row: Listing) {
|
||||
|
||||
async function offline(row: Listing) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认下架「${row.title}」吗?下架后后台待审列表会同步移除。`, '下架确认', {
|
||||
confirmButtonText: '确认下架',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
await ElMessageBox.confirm(
|
||||
`确认下架「${row.title}」吗?下架后后台待审列表会同步移除。`,
|
||||
'下架确认',
|
||||
{
|
||||
confirmButtonText: '确认下架',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -73,7 +94,7 @@ async function offline(row: Listing) {
|
||||
}
|
||||
|
||||
function replaceListing(next: Listing) {
|
||||
const index = listings.value.findIndex((item) => item.id === next.id)
|
||||
const index = listings.value.findIndex(item => item.id === next.id)
|
||||
if (index >= 0) {
|
||||
listings.value.splice(index, 1, next)
|
||||
} else {
|
||||
@@ -166,7 +187,9 @@ function isPendingReview(row: Listing) {
|
||||
<div class="listing-title-row">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<div class="listing-tags">
|
||||
<span class="status-pill" :class="statusTone(item.status)">{{ listingStatusLabel(item.status) }}</span>
|
||||
<span class="status-pill" :class="statusTone(item.status)">{{
|
||||
listingStatusLabel(item.status)
|
||||
}}</span>
|
||||
<span class="status-pill" :class="reviewTone(effectiveReviewStatus(item))">
|
||||
{{ listingReviewStatusLabel(effectiveReviewStatus(item)) }}
|
||||
</span>
|
||||
@@ -273,7 +296,10 @@ function isPendingReview(row: Listing) {
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.04);
|
||||
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.stat-card span {
|
||||
|
||||
@@ -14,7 +14,7 @@ const props = withDefaults(
|
||||
keyPrefix: '',
|
||||
compact: false,
|
||||
suffix: '',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -70,7 +70,10 @@ function selectValue(value: string | number) {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
background 0.16s ease,
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.chip.active {
|
||||
|
||||
Reference in New Issue
Block a user