From b298c95a5c25b6bec00888c150ca1ae15dc859c9 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 24 May 2026 20:05:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=89=E8=AE=A2=E5=8D=95=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E7=BB=93=E8=B4=A6=E6=B6=88=E8=80=97=E9=87=91?= =?UTF-8?q?=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/account/OrderDetailView.vue | 212 +++++++++++++++++- 1 file changed, 206 insertions(+), 6 deletions(-) diff --git a/frontend/src/views/account/OrderDetailView.vue b/frontend/src/views/account/OrderDetailView.vue index e510319..19888ba 100644 --- a/frontend/src/views/account/OrderDetailView.vue +++ b/frontend/src/views/account/OrderDetailView.vue @@ -48,6 +48,7 @@ const checkoutForm = ref({ other_amount: 0, evidenceText: '', }) +const resourceUsage = ref>({}) const counterForm = ref({ consumable_amount: 0, coin_consumed_m: 0, @@ -70,6 +71,26 @@ const canOpenDispute = computed(() => { const isCheckoutDisputeStage = computed(() => { return !!order.value && ['pending_checkout_confirm', 'pending_checkout_accept'].includes(order.value.status) }) +const checkoutResources = computed(() => { + const resources = readSnapshotResources() + return resources.filter((item) => item.quantity > 0) +}) +const resourceChargeAmount = computed(() => { + return roundMoney( + checkoutResources.value.reduce((sum, item) => { + if (!isChargedResource(item)) return sum + const used = readResourceUsage(item.key) + return sum + used * item.unitPrice + }, 0), + ) +}) +const snapshotHafCoinM = computed(() => { + const snapshot = readSnapshot() + return roundMoney(readNumber(snapshot?.haf_coin_amount) / 1000000) +}) +const remainingHafCoinM = computed(() => { + return roundMoney(Math.max(snapshotHafCoinM.value - Number(checkoutForm.value.coin_consumed_m || 0), 0)) +}) onMounted(loadOrder) @@ -78,6 +99,7 @@ async function loadOrder() { try { order.value = await fetchOrder(String(route.params.id)) handoffRecords.value = await fetchHandoffRecords(String(route.params.id)) + hydrateResourceUsage() hydrateCounterForm() } finally { loading.value = false @@ -145,9 +167,11 @@ async function handleSubmitCheckout() { if (!order.value) return returning.value = true try { + const consumableAmount = resourceChargeAmount.value + checkoutForm.value.consumable_amount = consumableAmount await submitCheckout(order.value.id, { - content: checkoutForm.value.content, - consumable_amount: checkoutForm.value.consumable_amount, + content: checkoutContentWithSummary(), + consumable_amount: consumableAmount, coin_consumed_m: checkoutForm.value.coin_consumed_m, other_amount: checkoutForm.value.other_amount, evidence_urls: linesToList(checkoutForm.value.evidenceText), @@ -159,6 +183,7 @@ async function handleSubmitCheckout() { other_amount: 0, evidenceText: '', } + resourceUsage.value = {} ElMessage.success('结账已发起,等待号主确认') await loadOrder() } catch (error) { @@ -295,6 +320,114 @@ function orderEstimatedEndAt() { return new Date(new Date(rentedAt).getTime() + durationHours * 60 * 60 * 1000).toISOString() } +interface CheckoutResource { + key: string + label: string + price: string + mode: string + quantity: number + unitPrice: number +} + +function readSnapshot() { + const snapshot = order.value?.account_snapshot + if (isRecord(snapshot)) return snapshot + return null +} + +function readAssetSummary() { + const summary = readSnapshot()?.asset_summary + if (isRecord(summary)) return summary + return null +} + +function readSnapshotResources(): CheckoutResource[] { + const resources = readAssetSummary()?.resources + if (!Array.isArray(resources)) return [] + return resources + .filter(isRecord) + .map((item) => { + const key = String(item.key || item.label || '') + const label = String(item.label || key || '额外消耗品') + const price = String(item.price || '') + return { + key, + label, + price, + mode: String(item.mode || '收费'), + quantity: readNumber(item.quantity), + unitPrice: readUnitPrice(price), + } + }) + .filter((item) => item.key && item.quantity > 0) +} + +function hydrateResourceUsage() { + const next: Record = {} + for (const item of checkoutResources.value) { + next[item.key] = Math.min(Math.max(Number(resourceUsage.value[item.key] || 0), 0), item.quantity) + } + resourceUsage.value = next +} + +function readResourceUsage(key: string) { + return Math.max(Number(resourceUsage.value[key] || 0), 0) +} + +function isChargedResource(item: CheckoutResource) { + return item.mode !== '赠送' +} + +function resourceLineAmount(item: CheckoutResource) { + return roundMoney(readResourceUsage(item.key) * item.unitPrice) +} + +function checkoutContentWithSummary() { + const lines = [checkoutForm.value.content.trim()].filter(Boolean) + const usedResources = checkoutResources.value.filter((item) => readResourceUsage(item.key) > 0) + if (usedResources.length) { + lines.push( + `额外消耗品:${usedResources + .map((item) => `${item.label} ${readResourceUsage(item.key)}/${item.quantity}${isChargedResource(item) ? `,扣款¥${resourceLineAmount(item).toFixed(2)}` : ',赠送不扣款'}`) + .join(';')}`, + ) + } + if (Number(checkoutForm.value.coin_consumed_m || 0) > 0) { + lines.push(`哈夫币消耗:${Number(checkoutForm.value.coin_consumed_m).toFixed(2)}M,预计剩余${remainingHafCoinM.value.toFixed(2)}M`) + } + if (lines.length === 0) { + lines.push('租客发起结账。') + } + return lines.join('\n') +} + +function readUnitPrice(priceText: string) { + const normalized = priceText.replace(/,/g, ',').trim() + const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/) + if (fractionMatch) { + const amount = Number(fractionMatch[1]) + const count = Number(fractionMatch[2]) + return count > 0 ? roundMoney(amount / count) : 0 + } + const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/) + if (singleMatch) return Number(singleMatch[1]) + const fallback = normalized.match(/(\d+(?:\.\d+)?)/) + return fallback ? Number(fallback[1]) : 0 +} + +function roundMoney(value: number) { + return Math.round(value * 100) / 100 +} + +function readNumber(value: unknown) { + const number = Number(value || 0) + return Number.isFinite(number) ? number : 0 +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null +} + function hydrateCounterForm() { if (!order.value?.checkout) return const checkout = order.value.checkout @@ -379,12 +512,31 @@ function linesToList(value: string) {

发起结账

+
+
+ 额外消耗品 + 扣款合计:¥{{ resourceChargeAmount.toFixed(2) }} +
+ +
+
+ {{ item.label }} + 库存 {{ item.quantity }},{{ item.mode }},{{ item.price || '未设置单价' }} +
+ + ¥{{ resourceLineAmount(item).toFixed(2) }} +
+
- - - - + + 订单快照 {{ snapshotHafCoinM.toFixed(2) }}M,预计剩余 {{ remainingHafCoinM.toFixed(2) }}M @@ -475,3 +627,51 @@ function linesToList(value: string) {
+ +