From a8dc96aa3b155ded1d67ede3ad7b2169ce280c21 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 24 May 2026 23:59:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96pc=20=E5=87=BA=E5=94=AE?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/MobileSellerListingCreateView.vue | 4 +- .../views/seller/SellerListingCreateView.vue | 404 ++++++++++++++---- 2 files changed, 324 insertions(+), 84 deletions(-) diff --git a/frontend/src/views/mobile/MobileSellerListingCreateView.vue b/frontend/src/views/mobile/MobileSellerListingCreateView.vue index 2bf7f6a..679654b 100644 --- a/frontend/src/views/mobile/MobileSellerListingCreateView.vue +++ b/frontend/src/views/mobile/MobileSellerListingCreateView.vue @@ -490,7 +490,7 @@ async function handleSubmit() { loading.value = true; try { const title = `${form.server_region} ${form.rank_level} ${form.haf_coin_amount}M哈夫币`; - const dailyPrice = calculatedFinalPrice.value; + const listingPrice = calculatedFinalPrice.value; const listing = await createListing({ title, @@ -501,7 +501,7 @@ async function handleSubmit() { haf_coin_amount: coinMAmount.value * 1000000, asset_summary: buildAssetSummary(), screenshot_urls: screenshotUrls.value, - price: dailyPrice, + price: listingPrice, deposit_amount: Number(form.deposit_amount), }); localStorage.removeItem(draftKey); diff --git a/frontend/src/views/seller/SellerListingCreateView.vue b/frontend/src/views/seller/SellerListingCreateView.vue index b52a166..51d7b96 100644 --- a/frontend/src/views/seller/SellerListingCreateView.vue +++ b/frontend/src/views/seller/SellerListingCreateView.vue @@ -2,7 +2,7 @@ import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { Delete, Picture, RefreshRight, UploadFilled } from '@element-plus/icons-vue' +import { Delete, DocumentChecked, Picture, RefreshRight, UploadFilled } from '@element-plus/icons-vue' import { fetchFileBlobByURL, uploadFile } from '@/api/files' import { @@ -49,10 +49,12 @@ interface PublishDraft { const draftKey = 'hfb.pc.publish.draft' const dailyLossOptions = [10, 20, 30, 40, 50] +const commonOnlineTimes = ['00:00', '08:00', '10:00', '12:00', '14:00', '18:00', '20:00', '22:00', '23:59'] const router = useRouter() const loading = ref(false) const uploading = ref(false) const suppressDraftSave = ref(false) +const draftReady = ref(false) const publishOptions = ref(emptyListingPublishOptions) const salePriceConfig = ref(emptyListingSalePriceConfig) const fileInput = ref(null) @@ -141,10 +143,18 @@ const acceleratedSaleRatioPlaceholder = computed(() => { if (calculatedDefaultSaleRatio.value <= 0) return '填写资料后自动生成可设置范围' return `默认 ${formatNumber(calculatedDefaultSaleRatio.value)},最高 ${formatNumber(maxAcceleratedSaleRatio.value)}` }) -const selectedSkinCount = computed(() => selectedSkins.value.length) const uploadedScreenshotCount = computed(() => screenshotUrls.value.length) const requiredScreenshotCount = computed(() => screenshotSlots.value.filter((item) => isScreenshotRequired(item)).length) const recommendedDepositAmount = computed(() => calculateRecommendedDeposit()) +const depositBreakdownItems = computed(() => buildDepositBreakdownItems()) +const platformRuleLabel = computed(() => { + const labels: Record = { + fixed_markup: '固定加价', + ratio_subtract: '比例修正', + none: '无加价', + } + return labels[calculatedPlatformPricing.value.ruleType] || calculatedPlatformPricing.value.ruleType +}) const publishTitle = computed(() => { const parts = [form.server_region, form.rank_level, coinMAmount.value ? `${coinMAmount.value}M哈夫币` : ''].filter(Boolean) return parts.length ? parts.join(' ') : '待完善账号信息' @@ -152,10 +162,14 @@ const publishTitle = computed(() => { onMounted(() => { restoreDraft() + draftReady.value = true + window.addEventListener('beforeunload', handleBeforeUnload) loadPublishOptions() }) onBeforeUnmount(() => { + saveDraft({ force: true }) + window.removeEventListener('beforeunload', handleBeforeUnload) revokeAllScreenshotPreviews() }) @@ -206,11 +220,21 @@ function buildDraft(): PublishDraft { } } -function saveDraft() { +function saveDraft(options: { force?: boolean } = {}) { if (suppressDraftSave.value) return + if (!options.force && !draftReady.value) return localStorage.setItem(draftKey, JSON.stringify(buildDraft())) } +function handleSaveDraft() { + saveDraft({ force: true }) + ElMessage.success('草稿已保存') +} + +function handleBeforeUnload() { + saveDraft({ force: true }) +} + function restoreDraft() { const raw = localStorage.getItem(draftKey) if (!raw) return @@ -388,6 +412,28 @@ function calculateRecommendedDeposit() { return roundMoney(baseAmount + skinAmount) } +function buildDepositBreakdownItems() { + const items = [ + { + label: '基础押金', + amount: Number(depositRecommendConfig.value.base_amount || 0), + count: 1, + }, + ] + for (const rule of depositRecommendConfig.value.skin_group_rules) { + const group = skinGroups.value.find((item) => item.key === rule.group_key) + if (!group) continue + const count = group.options.filter((skin) => selectedSkins.value.includes(skin)).length + if (count <= 0) continue + items.push({ + label: rule.label, + amount: Number(rule.amount_per_item || 0) * count, + count, + }) + } + return items +} + function useRecommendedDeposit() { if (recommendedDepositAmount.value > 0) { form.deposit_amount = recommendedDepositAmount.value @@ -457,6 +503,7 @@ async function handleSubmit() { deposit_amount: Number(form.deposit_amount), }) localStorage.removeItem(draftKey) + suppressDraftSave.value = true ElMessage.success( listing.status === 'published' && listing.review_status === 'approved' ? '发布成功,已上架' @@ -753,7 +800,7 @@ function readError(error: unknown, fallback: string) { {{ publishTitle }} -
+
-
+
-
+
-
+
-
+
-
+ -
+ @@ -1194,16 +1302,17 @@ function readError(error: unknown, fallback: string) { .publish-layout { display: grid; - grid-template-columns: minmax(0, 1fr) 320px; - gap: 20px; - max-width: 1480px; + grid-template-columns: minmax(760px, 920px) 300px; + align-items: start; + gap: 18px; + max-width: 1238px; margin: 0 auto; } .form-column { display: grid; min-width: 0; - gap: 16px; + gap: 18px; } .form-section, @@ -1215,15 +1324,16 @@ function readError(error: unknown, fallback: string) { } .form-section { - padding: 18px; + padding: 20px; } .section-heading { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 16px; - margin-bottom: 16px; + min-height: 28px; + margin-bottom: 18px; } .section-heading h2 { @@ -1241,10 +1351,21 @@ function readError(error: unknown, fallback: string) { .field-row { display: grid; - grid-template-columns: 118px minmax(0, 1fr); - gap: 12px; - align-items: start; - margin-top: 14px; + grid-template-columns: 112px minmax(0, 1fr); + gap: 14px; + align-items: center; + margin-top: 12px; +} + +.panel-field-row, +.panel-input-grid { + padding: 12px; + border-radius: 8px; + background: #f7f9fc; +} + +.panel-input-grid { + align-items: end; } .field-row > label, @@ -1252,7 +1373,7 @@ function readError(error: unknown, fallback: string) { color: #303743; font-size: 13px; font-weight: 800; - line-height: 32px; + line-height: 22px; } .field-row label span, @@ -1265,7 +1386,7 @@ function readError(error: unknown, fallback: string) { .chip-group { display: flex; flex-wrap: wrap; - gap: 8px; + gap: 7px; min-width: 0; } @@ -1288,10 +1409,10 @@ function readError(error: unknown, fallback: string) { } .chip { - min-height: 32px; - padding: 0 13px; - border-radius: 16px; - font-size: 13px; + min-height: 30px; + padding: 0 12px; + border-radius: 15px; + font-size: 12px; } .chip.active, @@ -1305,8 +1426,8 @@ function readError(error: unknown, fallback: string) { .compact-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; - margin-top: 14px; + gap: 16px; + margin-top: 16px; } .compact-grid.two { @@ -1316,7 +1437,7 @@ function readError(error: unknown, fallback: string) { .input-block { display: grid; min-width: 0; - gap: 7px; + gap: 8px; } .input-block :deep(.el-input-number), @@ -1339,10 +1460,10 @@ function readError(error: unknown, fallback: string) { .level-row { display: grid; - grid-template-columns: 74px minmax(0, 1fr); - gap: 10px; + grid-template-columns: 72px minmax(0, 1fr); + gap: 12px; align-items: center; - padding: 12px; + padding: 10px 12px; border-radius: 8px; background: #f7f9fc; } @@ -1354,13 +1475,13 @@ function readError(error: unknown, fallback: string) { .level-options { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); - gap: 8px; + gap: 9px; } .level-btn { - height: 32px; + height: 30px; min-width: 0; - border-radius: 16px; + border-radius: 15px; } .field-hint { @@ -1371,18 +1492,39 @@ function readError(error: unknown, fallback: string) { line-height: 1.55; } -.quantity-grid { +.login-method-row { + padding: 12px; + border-radius: 8px; + background: #f7f9fc; +} + +.login-method-row .field-hint { + margin-top: -2px; +} + +.quantity-table { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.quantity-header, +.quantity-item { + display: grid; + grid-template-columns: minmax(160px, 1fr) 90px 112px 112px 92px; gap: 12px; + align-items: center; +} + +.quantity-header { + min-height: 30px; + padding: 0 12px; + color: #7b8492; + font-size: 12px; + font-weight: 800; } .quantity-item { - display: grid; - grid-template-columns: minmax(0, 1fr) 112px 112px; - gap: 10px; - align-items: center; - min-height: 76px; + min-height: 58px; padding: 12px; border-radius: 8px; background: #f7f9fc; @@ -1391,7 +1533,7 @@ function readError(error: unknown, fallback: string) { .quantity-meta { display: grid; min-width: 0; - gap: 5px; + gap: 4px; } .quantity-meta strong { @@ -1402,14 +1544,20 @@ function readError(error: unknown, fallback: string) { } .quantity-meta small, +.quantity-price, +.quantity-status, .upload-copy small, -.skin-title span, .region-title span { color: #7f8895; font-size: 12px; font-weight: 700; } +.quantity-price, +.quantity-status { + white-space: nowrap; +} + .quantity-item.disabled { opacity: 0.62; } @@ -1427,10 +1575,10 @@ function readError(error: unknown, fallback: string) { } .mode-toggle button { - height: 32px; + height: 30px; min-width: 0; border-radius: 8px; - font-size: 13px; + font-size: 12px; white-space: nowrap; } @@ -1445,11 +1593,11 @@ function readError(error: unknown, fallback: string) { } .recommend-button { - justify-self: start; - height: 28px; - padding: 0 10px; + flex: 0 0 auto; + height: 32px; + padding: 0 12px; border: 1px solid #ffba86; - border-radius: 14px; + border-radius: 8px; background: #fff7f0; color: #e65f00; font-size: 12px; @@ -1457,9 +1605,32 @@ function readError(error: unknown, fallback: string) { cursor: pointer; } +.deposit-control { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: center; +} + +.deposit-breakdown { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.deposit-breakdown span { + min-height: 24px; + padding: 4px 8px; + border-radius: 12px; + background: #fff7f0; + color: #e65f00; + font-size: 12px; + font-weight: 800; +} + .skin-groups { display: grid; - gap: 12px; + gap: 10px; } .skin-group, @@ -1475,11 +1646,63 @@ function readError(error: unknown, fallback: string) { .skin-title, .region-title { display: flex; + align-items: center; justify-content: space-between; gap: 12px; font-size: 13px; } +.skin-group { + border: none; +} + +.skin-title { + min-height: 34px; + margin-bottom: 8px; +} + +.time-preset-panel { + display: grid; + gap: 10px; + margin-top: 16px; +} + +.time-preset-row { + display: grid; + grid-template-columns: 88px minmax(0, 1fr); + gap: 12px; + align-items: start; + padding: 12px; + border-radius: 8px; + background: #f7f9fc; +} + +.time-preset-row strong { + color: #303743; + font-size: 13px; + font-weight: 800; + line-height: 32px; +} + +.time-preset-content { + display: grid; + gap: 8px; + min-width: 0; +} + +.time-input { + width: 118px; +} + +.time-input :deep(.el-input__wrapper) { + border-radius: 8px; + box-shadow: 0 0 0 1px #dfe5ee inset; +} + +.login-region-panel { + margin-top: 18px; +} + .region-grid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); @@ -1684,11 +1907,13 @@ function readError(error: unknown, fallback: string) { .summary-column { min-width: 0; + position: sticky; + top: 76px; + align-self: start; + height: fit-content; } .summary-panel { - position: sticky; - top: 152px; padding: 16px; } @@ -1730,6 +1955,7 @@ function readError(error: unknown, fallback: string) { color: #20242a; font-size: 13px; text-align: right; + overflow-wrap: anywhere; } .summary-actions { @@ -1745,6 +1971,7 @@ function readError(error: unknown, fallback: string) { @media (max-width: 1180px) { .publish-layout { grid-template-columns: 1fr; + max-width: 920px; } .summary-column { @@ -1756,12 +1983,15 @@ function readError(error: unknown, fallback: string) { .field-row, .compact-grid, .compact-grid.two, - .quantity-grid, .upload-grid, .price-grid { grid-template-columns: 1fr; } + .quantity-header { + display: none; + } + .field-hint { grid-column: auto; } @@ -1771,6 +2001,16 @@ function readError(error: unknown, fallback: string) { grid-template-columns: 1fr; } + .deposit-control, + .ratio-mode-grid, + .time-preset-row { + grid-template-columns: 1fr; + } + + .time-input { + width: 140px; + } + .upload-add, .upload-preview { width: 100%;