feat(order): 结账备注与系统结算信息分开展示,系统结算信息改为逐行明细

- 结账内容写入分隔标记,展示端拆出「结账备注(不参与结算金额)」与「系统结算信息(参与结算金额)」
- 系统结算信息每件消耗品单独一行并给合计,哈夫币/押金各一行;兼容旧数据(同一行用「;」分隔自动拆行并补合计)
- 新增 CheckoutSystemDetail 明细组件,接入订单交接记录、结账明细(PC/移动端)与客服订单详情、聊天侧栏
- 结账提交表单与二次确认补充「备注不参与结算金额」提示
This commit is contained in:
yml2213
2026-10-15 18:14:31 +08:00
parent 93e102b7c0
commit 832a0628c6
13 changed files with 911 additions and 56 deletions
@@ -349,6 +349,30 @@
line-height: 1.6;
}
/* 交接记录里区分租客手填备注(不参与结算金额)与系统结算信息(参与结算金额) */
.order-side-section .handoff-remark {
padding: 8px 10px;
border: 1px solid #e5e9f2;
border-radius: 6px;
background: #f8fafc;
white-space: pre-wrap;
}
.order-side-section .handoff-system {
display: grid;
gap: 6px;
padding: 8px 10px;
border: 1px solid #dbeafe;
border-radius: 6px;
background: #f0f7ff;
}
.order-side-section .handoff-system-tag {
color: #2563eb;
font-size: 12px;
font-weight: 700;
}
.order-side-metrics {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -22,9 +22,11 @@ import {
fetchAdminLatestOrderByListing,
fetchAdminHandoffRecords,
fetchAdminOrder,
splitCheckoutContent,
type HandoffRecord,
type Order,
} from '@/features/orders'
import CheckoutSystemDetail from '@/features/orders/components/CheckoutSystemDetail.vue'
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
import { uploadAdminFile } from '@/shared/api/files'
import ChatAttachmentImage from '@/components/ChatAttachmentImage.vue'
@@ -108,6 +110,8 @@ const activeListingCode = computed(() =>
: '-'
)
const latestHandoffRecord = computed(() => activeHandoffRecords.value[0])
// 交接记录内容含「租客手填备注 + 系统结算信息」,拆分展示避免客服把备注当作结算依据
const latestHandoffParts = computed(() => splitCheckoutContent(latestHandoffRecord.value?.content))
const latestPaymentRecord = computed(() => activePaymentRecords.value[0])
const hasOrderContext = computed(() => Boolean(active.value?.order_id || active.value?.listing_id))
const orderDetailLink = computed(() =>
@@ -894,7 +898,16 @@ function firstQueryValue(value: unknown) {
<h3>交接记录</h3>
<template v-if="latestHandoffRecord">
<strong>{{ formatHandoffRecordType(latestHandoffRecord.type) }}</strong>
<p>{{ latestHandoffRecord.content }}</p>
<template v-if="latestHandoffParts.systemLines.length">
<p v-if="latestHandoffParts.remark" class="handoff-remark">
备注(不参与结算金额):{{ latestHandoffParts.remark }}
</p>
<div class="handoff-system">
<span class="handoff-system-tag">系统结算信息(参与结算金额)</span>
<CheckoutSystemDetail :lines="latestHandoffParts.systemLines" />
</div>
</template>
<p v-else>{{ latestHandoffRecord.content }}</p>
<p>{{ formatDateTime(latestHandoffRecord.created_at) }}</p>
</template>
<p v-else>暂无交接记录</p>
@@ -113,6 +113,12 @@
overflow-wrap: anywhere;
}
/* 结账备注与系统结算信息为多行文本,保留换行 */
.detail-list dd.detail-pre {
font-weight: 500;
white-space: pre-wrap;
}
.money-list {
display: grid;
gap: 8px;
@@ -192,6 +198,36 @@
white-space: pre-wrap;
}
/* 交接记录里租客手填备注与系统结算信息分块展示 */
.record-note {
display: grid;
gap: 4px;
padding: 10px 12px;
border: 1px solid #eef2f7;
border-radius: 6px;
background: #ffffff;
}
.record-note.remark {
border-color: #e5e9f2;
background: #f8fafc;
}
.record-note.system {
border-color: #dbeafe;
background: #f0f7ff;
}
.record-note-tag {
color: #8f9bba;
font-size: 12px;
font-weight: 700;
}
.record-note.system .record-note-tag {
color: #2563eb;
}
.snapshot-detail-list {
margin-bottom: 14px;
}
@@ -29,11 +29,13 @@ import {
} from '@/features/orders'
import { adminCreateOrderDispute } from '@/features/disputes'
import { fetchAdminPayments, type AdminPayment } from '@/features/admin/api/adminPayments'
import CheckoutSystemDetail from '@/features/orders/components/CheckoutSystemDetail.vue'
import {
getSnapshotHafCoinM,
linesToList,
readSnapshot,
readSnapshotResources,
splitCheckoutContent,
} from '@/features/orders/composables/useOrderSnapshot'
import { adminPath, isAdminPath } from '@/shared/utils/adminPath'
import { centToYuan, formatCentWithSymbol } from '@/shared/utils/money'
@@ -143,6 +145,12 @@ function goBackToList() {
const refunding = ref(false)
const snapshotText = computed(() => JSON.stringify(order.value?.account_snapshot || {}, null, 2))
// 交接记录内容含「租客手填备注 + 系统结算信息」,拆分展示便于客服区分二者
const handoffRecordEntries = computed(() =>
handoffRecords.value.map(record => ({ record, parts: splitCheckoutContent(record.content) }))
)
// 结账说明同样区分手填备注与系统结算信息
const checkoutContentParts = computed(() => splitCheckoutContent(order.value?.checkout?.content))
const canOperate = computed(
() => !!order.value && !['completed', 'cancelled', 'closed'].includes(order.value.status)
)
@@ -1353,9 +1361,13 @@ function formatRatioNumber(value: number) {
<dt>凭证数量</dt>
<dd>{{ order.checkout.evidence_urls?.length || 0 }} 张</dd>
</div>
<div v-if="order.checkout.content" class="wide">
<dt>结账说明</dt>
<dd>{{ order.checkout.content }}</dd>
<div v-if="checkoutContentParts.remark" class="wide">
<dt>结账备注(不参与结算金额)</dt>
<dd class="detail-pre">{{ checkoutContentParts.remark }}</dd>
</div>
<div v-if="checkoutContentParts.systemLines.length" class="wide">
<dt>系统结算信息(参与结算金额)</dt>
<dd><CheckoutSystemDetail :lines="checkoutContentParts.systemLines" /></dd>
</div>
<div v-if="order.checkout.owner_adjustment_reason" class="wide">
<dt>号主修正</dt>
@@ -1411,17 +1423,27 @@ function formatRatioNumber(value: number) {
</div>
<el-empty v-if="handoffRecords.length === 0" description="暂无交接记录" />
<div v-else class="record-list">
<article v-for="record in handoffRecords" :key="record.id" class="record-item">
<article v-for="entry in handoffRecordEntries" :key="entry.record.id" class="record-item">
<div class="record-main">
<strong>{{ formatHandoffRecordType(record.type) }}</strong>
<span>{{ formatDateTime(record.created_at) }}</span>
<strong>{{ formatHandoffRecordType(entry.record.type) }}</strong>
<span>{{ formatDateTime(entry.record.created_at) }}</span>
</div>
<p>{{ record.content }}</p>
<template v-if="entry.parts.systemLines.length">
<div v-if="entry.parts.remark" class="record-note remark">
<span class="record-note-tag">备注 · 不参与结算金额</span>
<p>{{ entry.parts.remark }}</p>
</div>
<div class="record-note system">
<span class="record-note-tag">系统结算信息 · 参与结算金额</span>
<CheckoutSystemDetail :lines="entry.parts.systemLines" />
</div>
</template>
<p v-else>{{ entry.record.content }}</p>
<div class="record-meta">
<span>发送:ID {{ record.from_user_id }}</span>
<span>接收:ID {{ record.to_user_id }}</span>
<span>租客确认:{{ formatDateTime(record.confirmed_by_renter_at, '-') }}</span>
<span>号主确认:{{ formatDateTime(record.confirmed_by_owner_at, '-') }}</span>
<span>发送:ID {{ entry.record.from_user_id }}</span>
<span>接收:ID {{ entry.record.to_user_id }}</span>
<span>租客确认:{{ formatDateTime(entry.record.confirmed_by_renter_at, '-') }}</span>
<span>号主确认:{{ formatDateTime(entry.record.confirmed_by_owner_at, '-') }}</span>
</div>
</article>
</div>