From 438a4259f928e712a7b6efebc0fe1db37bb7e7fe Mon Sep 17 00:00:00 2001 From: yml Date: Fri, 19 Jun 2026 07:01:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=A2=E5=8D=95=E4=BA=A4?= =?UTF-8?q?=E6=8E=A5=E5=92=8C=E7=BB=93=E8=B4=A6=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/chats/views/ChatView.vue | 31 +- .../components/OrderHandoffTimeline.vue | 81 ++- .../components/OrderResourceUsageEditor.vue | 257 +++++-- .../orders/composables/useOrderActions.ts | 30 +- .../features/orders/views/OrderDetailView.vue | 648 ++++++++++++------ 5 files changed, 752 insertions(+), 295 deletions(-) diff --git a/frontend/src/features/chats/views/ChatView.vue b/frontend/src/features/chats/views/ChatView.vue index e5650ab..3f3e46a 100644 --- a/frontend/src/features/chats/views/ChatView.vue +++ b/frontend/src/features/chats/views/ChatView.vue @@ -2,7 +2,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { ElMessage } from 'element-plus' -import { ArrowLeft, Close, Loading, Picture } from '@element-plus/icons-vue' +import { ArrowLeft, Close, DocumentChecked, Loading, Picture } from '@element-plus/icons-vue' import { fetchChat, fetchChatMessages, @@ -33,6 +33,9 @@ const fileInputRef = ref(null) const conversationID = computed(() => Number(route.params.id || 0)) const canSend = computed(() => content.value.trim() !== '' || attachments.value.length > 0) +const activeOrderId = computed( + () => conversation.value?.order_id || conversation.value?.latest_order_id || null +) const memberText = computed(() => { const participants = conversation.value?.participants || [] if (participants.length === 0) @@ -252,6 +255,11 @@ function handleKeydown(e: KeyboardEvent) { handleSend() } } + +function goOrderHandoff() { + if (!activeOrderId.value) return + router.push({ path: `/orders/${activeOrderId.value}`, query: { focus: 'handoff' } }) +} diff --git a/frontend/src/features/orders/composables/useOrderActions.ts b/frontend/src/features/orders/composables/useOrderActions.ts index 34c9cb3..aa3a853 100644 --- a/frontend/src/features/orders/composables/useOrderActions.ts +++ b/frontend/src/features/orders/composables/useOrderActions.ts @@ -48,9 +48,9 @@ export interface UseOrderActionsOptions { notifyError: (message: string) => void /** 警告提示,用于表单校验未通过等非致命场景。 */ notifyWarning: (message: string) => void - /** 二次确认。返回 true 表示用户确认继续,false 表示取消。PC 注入直接 resolve(true),Mobile 注入 showDialog。 */ + /** 二次确认。返回 true 表示用户确认继续,false 表示取消。 */ confirm: (opts: { title: string; message: string }) => Promise - /** 需要走二次确认的 handler 名集合。Mobile 传 ['cancel','confirmCheckout','acceptCheckout','cancelDispute'],PC 传 []。 */ + /** 需要走二次确认的 handler 名集合。 */ actionsNeedingConfirm: readonly string[] /** 支付前选择支付方式。返回 null 表示用户取消支付。 */ selectPayWay: () => Promise @@ -78,6 +78,7 @@ export const CONFIRMABLE_ACTIONS = [ 'confirmCheckout', 'acceptCheckout', 'cancelDispute', + 'submitCheckout', ] as const export interface CheckoutFormState { @@ -152,6 +153,7 @@ export function useOrderActions(options: UseOrderActionsOptions) { const disputeType = ref('cannot_login') const disputeDescription = ref('') const disputeEvidenceText = ref('') + let checkoutDefaultsHydratedOrderID: number | null = null // 结账快照能力(资源金额、内容摘要、资源使用量规范化) const { @@ -217,12 +219,28 @@ export function useOrderActions(options: UseOrderActionsOptions) { counterForm.value = hydrateCounterFormFromCheckout(order.value.checkout) } + function hydrateCheckoutDefaults() { + if (!order.value || !isRenter.value || !['renting', 'overdue'].includes(order.value.status)) { + return + } + if (checkoutDefaultsHydratedOrderID === order.value.id) return + const defaultCoinConsumedM = snapshotHafCoinM.value + if (defaultCoinConsumedM > 0 && Number(checkoutForm.value.coin_consumed_m || 0) <= 0) { + checkoutForm.value = { + ...checkoutForm.value, + coin_consumed_m: defaultCoinConsumedM, + } + } + checkoutDefaultsHydratedOrderID = order.value.id + } + async function loadOrder() { loading.value = true try { order.value = await fetchOrder(String(route.params.id)) handoffRecords.value = await fetchHandoffRecords(String(route.params.id)) hydrateResourceUsage() + hydrateCheckoutDefaults() hydrateCounterForm() if ( !autoPayHandled && @@ -317,6 +335,14 @@ export function useOrderActions(options: UseOrderActionsOptions) { async function handleSubmitCheckout() { if (!order.value) return + if (needsConfirm('submitCheckout')) { + const ok = await options.confirm({ + title: '确认提交结账', + message: + '结账数据会影响租金、押金赔付和双方结算金额。请确认哈夫币消耗、额外消耗品、押金赔付和证据链接已认真核对,提交后将发送给号主确认。', + }) + if (!ok) return + } returning.value = true try { const consumableAmount = resourceChargeAmount.value diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue index 431fcf3..d52d26c 100644 --- a/frontend/src/features/orders/views/OrderDetailView.vue +++ b/frontend/src/features/orders/views/OrderDetailView.vue @@ -1,7 +1,8 @@