feat: 客服订单列表/详情展示自动结算倒计时,配置页文案改为结算率

平台单与商家单新增「待自动结算」筛选与自动结算列;详情状态旁展示预计时间。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 02:12:48 +08:00
parent 5af0ee5f09
commit aeb1e20c06
4 changed files with 108 additions and 7 deletions

View File

@@ -103,6 +103,10 @@
{{ getStatusText(orderData.zhuangtai) }} {{ getStatusText(orderData.zhuangtai) }}
</el-tag> </el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item v-if="orderData.auto_settle_eligible" label="自动结算">
<span class="auto-settle-tip">{{ autoSettleText }}</span>
<span v-if="orderData.auto_expire_at" class="auto-settle-at">{{ orderData.auto_expire_at }}</span>
</el-descriptions-item>
<el-descriptions-item label="发单平台"> <el-descriptions-item label="发单平台">
{{ orderData.fadanpingtai === 1 ? '平台发单' : orderData.fadanpingtai === 2 ? '商家发单' : '未知' }} {{ orderData.fadanpingtai === 1 ? '平台发单' : orderData.fadanpingtai === 2 ? '商家发单' : '未知' }}
</el-descriptions-item> </el-descriptions-item>
@@ -403,6 +407,26 @@ const getFullImageUrl = (url) => {
const formatPrice = (val) => parseFloat(val || 0).toFixed(2) const formatPrice = (val) => parseFloat(val || 0).toFixed(2)
const formatRemainText = (sec) => {
const s = Math.max(0, Number(sec) || 0)
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
if (h >= 24) return `${Math.floor(h / 24)}${h % 24}小时`
if (h > 0) return `${h}小时${m}`
if (m > 0) return `${m}分钟`
return s > 0 ? `${s}` : '即将结算'
}
const autoSettleText = computed(() => {
const d = orderData.value
if (!d || !d.auto_settle_eligible) return ''
const remain = d.auto_settle_remain_seconds
if (remain == null || remain === '') {
return d.auto_expire_at ? `预计 ${d.auto_expire_at} 自动结算` : ''
}
return Number(remain) <= 0 ? '即将自动结算' : `预计自动结算 ${formatRemainText(remain)}`
})
const statusMap = { const statusMap = {
1: '下单中', 7: '指定中', 2: '进行中', 8: '结算中', 1: '下单中', 7: '指定中', 2: '进行中', 8: '结算中',
3: '已完成', 4: '退款审核', 5: '已退款', 6: '退款失败' 3: '已完成', 4: '退款审核', 5: '已退款', 6: '退款失败'
@@ -775,6 +799,15 @@ onMounted(() => {
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
} }
.auto-settle-tip {
color: #c62828;
font-weight: 600;
}
.auto-settle-at {
color: #8d6e63;
margin-left: 6px;
font-size: 13px;
}
.card-header { .card-header {
font-weight: 700; font-weight: 700;
color: #1a2634; color: #1a2634;

View File

@@ -114,6 +114,14 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="自动结算" min-width="160">
<template #default="{ row }">
<span v-if="row.auto_settle_eligible" class="auto-settle-cell">
{{ formatAutoSettle(row) }}
</span>
<span v-else class="muted">--</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right"> <el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click.stop="goToDetail(row.dingdan_id)">详情</el-button> <el-button type="primary" link @click.stop="goToDetail(row.dingdan_id)">详情</el-button>
@@ -179,6 +187,7 @@ const statusList = ref([
{ label: '已下单', value: '1,7', count: 0 }, { label: '已下单', value: '1,7', count: 0 },
{ label: '进行中', value: '2', count: 0 }, { label: '进行中', value: '2', count: 0 },
{ label: '待结算', value: '8', count: 0 }, { label: '待结算', value: '8', count: 0 },
{ label: '待自动结算', value: 'auto_settle', count: 0 },
{ label: '已完成', value: '3', count: 0 }, { label: '已完成', value: '3', count: 0 },
{ label: '退款审核', value: '4', count: 0 }, { label: '退款审核', value: '4', count: 0 },
{ label: '已退款', value: '5', count: 0 }, { label: '已退款', value: '5', count: 0 },
@@ -225,7 +234,6 @@ const fetchOrders = async () => {
username: phone, username: phone,
page: pageNum.value, page: pageNum.value,
pageSize: pageSize.value, pageSize: pageSize.value,
zhuangtai: currentStatus.value === 'all' ? undefined : currentStatus.value,
dingdan_id: search.ordId || undefined, dingdan_id: search.ordId || undefined,
shangjia_id: search.shangjiaId || undefined, shangjia_id: search.shangjiaId || undefined,
dashou_id: search.dashouId || undefined, dashou_id: search.dashouId || undefined,
@@ -235,6 +243,12 @@ const fetchOrders = async () => {
clkf: search.clkf || undefined, clkf: search.clkf || undefined,
shangjia_nicheng: search.shangjiaNicheng || undefined shangjia_nicheng: search.shangjiaNicheng || undefined
} }
if (currentStatus.value === 'auto_settle') {
params.zhuangtai = '8'
params.auto_settle_pending = 1
} else if (currentStatus.value !== 'all') {
params.zhuangtai = currentStatus.value
}
if (currentType.value !== '' && currentType.value !== null && currentType.value !== undefined) { if (currentType.value !== '' && currentType.value !== null && currentType.value !== undefined) {
params.leixing = currentType.value params.leixing = currentType.value
} }
@@ -344,6 +358,24 @@ const handlePageChange = (val) => { pageNum.value = val; fetchOrders(); updateQu
const goToDetail = (dingdanId) => router.push(`/order/detail/${dingdanId}`) const goToDetail = (dingdanId) => router.push(`/order/detail/${dingdanId}`)
const formatPrice = (price) => parseFloat(price || 0).toFixed(2) const formatPrice = (price) => parseFloat(price || 0).toFixed(2)
const formatRemainText = (sec) => {
const s = Math.max(0, Number(sec) || 0)
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
if (h >= 24) return `${Math.floor(h / 24)}${h % 24}小时`
if (h > 0) return `${h}小时${m}`
if (m > 0) return `${m}分钟`
return s > 0 ? `${s}` : '即将结算'
}
const formatAutoSettle = (row) => {
if (!row || !row.auto_settle_eligible) return ''
const remain = row.auto_settle_remain_seconds
if (remain == null || remain === '') {
return row.auto_expire_at ? `预计 ${row.auto_expire_at}` : ''
}
const text = formatRemainText(remain)
return Number(remain) <= 0 ? '即将自动结算' : `预计自动结算 ${text}`
}
const getStatusText = (code) => { const getStatusText = (code) => {
const map = { '1':'下单中','7':'指定中','2':'进行中','8':'待结算','3':'已完成','4':'退款审核','5':'已退款','6':'退款失败' } const map = { '1':'下单中','7':'指定中','2':'进行中','8':'待结算','3':'已完成','4':'退款审核','5':'已退款','6':'退款失败' }
return map[code] || '未知' return map[code] || '未知'
@@ -398,6 +430,8 @@ onMounted(async () => {
.stat-label { font-size: 13px; color: #6b7a88; margin-bottom: 8px; } .stat-label { font-size: 13px; color: #6b7a88; margin-bottom: 8px; }
.stat-value { font-size: 24px; font-weight: 600; color: #1a2634; } .stat-value { font-size: 24px; font-weight: 600; color: #1a2634; }
.order-table { width: 100%; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.02); margin-bottom: 20px; } .order-table { width: 100%; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.02); margin-bottom: 20px; }
.auto-settle-cell { color: #c62828; font-size: 13px; }
.muted { color: #9aa5b1; }
.pagination { display: flex; justify-content: flex-end; } .pagination { display: flex; justify-content: flex-end; }
.no-permission { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; } .no-permission { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; }
.no-permission-icon { font-size: 80px; margin-bottom: 20px; opacity: 0.6; } .no-permission-icon { font-size: 80px; margin-bottom: 20px; opacity: 0.6; }

View File

@@ -2,7 +2,7 @@
<div class="page" v-loading="loading"> <div class="page" v-loading="loading">
<ClubScopeHint /> <ClubScopeHint />
<div class="header"> <div class="header">
<h2>成交指标与自动结算</h2> <h2>结算率指标与自动结算</h2>
<el-button @click="load">刷新</el-button> <el-button @click="load">刷新</el-button>
</div> </div>
<el-alert <el-alert
@@ -33,7 +33,7 @@
</el-card> </el-card>
<el-card shadow="never" class="card"> <el-card shadow="never" class="card">
<template #header>成交 / 罚款指标统计</template> <template #header>结算率 / 罚款指标统计</template>
<el-form label-width="160px"> <el-form label-width="160px">
<el-form-item label="启用指标统计"> <el-form-item label="启用指标统计">
<el-switch v-model="form.stats_enabled" /> <el-switch v-model="form.stats_enabled" />
@@ -52,7 +52,7 @@
<el-table :data="merchantList" size="small" max-height="360"> <el-table :data="merchantList" size="small" max-height="360">
<el-table-column prop="merchant_uid" label="商家UID" width="120" /> <el-table-column prop="merchant_uid" label="商家UID" width="120" />
<el-table-column prop="order_count" label="订单量" width="90" /> <el-table-column prop="order_count" label="订单量" width="90" />
<el-table-column prop="deal_rate" label="成交率" width="100"> <el-table-column prop="deal_rate" label="结算率" width="100">
<template #default="{ row }">{{ pct(row.deal_rate) }}</template> <template #default="{ row }">{{ pct(row.deal_rate) }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="fine_rate" label="罚款率" width="100"> <el-table-column prop="fine_rate" label="罚款率" width="100">
@@ -61,7 +61,7 @@
<el-table-column prop="refund_rate" label="退款率" width="100"> <el-table-column prop="refund_rate" label="退款率" width="100">
<template #default="{ row }">{{ pct(row.refund_rate) }}</template> <template #default="{ row }">{{ pct(row.refund_rate) }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="avg_deal_hours" label="平均成交时长(h)" min-width="120" /> <el-table-column prop="avg_deal_hours" label="平均结算时长(h)" min-width="120" />
</el-table> </el-table>
</el-card> </el-card>
@@ -70,7 +70,7 @@
<el-table :data="dashouList" size="small" max-height="360"> <el-table :data="dashouList" size="small" max-height="360">
<el-table-column prop="dashou_uid" label="打手UID" width="120" /> <el-table-column prop="dashou_uid" label="打手UID" width="120" />
<el-table-column prop="order_count" label="接单量" width="90" /> <el-table-column prop="order_count" label="接单量" width="90" />
<el-table-column prop="deal_rate" label="成交率" width="100"> <el-table-column prop="deal_rate" label="结算率" width="100">
<template #default="{ row }">{{ pct(row.deal_rate) }}</template> <template #default="{ row }">{{ pct(row.deal_rate) }}</template>
</el-table-column> </el-table-column>
<el-table-column prop="fined_rate" label="被罚款率" width="100"> <el-table-column prop="fined_rate" label="被罚款率" width="100">

View File

@@ -118,6 +118,14 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="自动结算" min-width="160">
<template #default="{ row }">
<span v-if="row.auto_settle_eligible" class="auto-settle-cell">
{{ formatAutoSettle(row) }}
</span>
<span v-else class="muted">--</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right"> <el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click.stop="goToDetail(row.dingdan_id)">详情</el-button> <el-button type="primary" link @click.stop="goToDetail(row.dingdan_id)">详情</el-button>
@@ -183,6 +191,7 @@ const statusList = ref([
{ label: '已下单', value: '1,7', count: 0 }, { label: '已下单', value: '1,7', count: 0 },
{ label: '进行中', value: '2', count: 0 }, { label: '进行中', value: '2', count: 0 },
{ label: '待结算', value: '8', count: 0 }, { label: '待结算', value: '8', count: 0 },
{ label: '待自动结算', value: 'auto_settle', count: 0 },
{ label: '已完成', value: '3', count: 0 }, { label: '已完成', value: '3', count: 0 },
{ label: '退款审核', value: '4', count: 0 }, { label: '退款审核', value: '4', count: 0 },
{ label: '已退款', value: '5', count: 0 }, { label: '已退款', value: '5', count: 0 },
@@ -228,7 +237,6 @@ const fetchOrders = async () => {
phone: localStorage.getItem('username'), phone: localStorage.getItem('username'),
page: pageNum.value, page: pageNum.value,
pageSize: pageSize.value, pageSize: pageSize.value,
zhuangtai: currentStatus.value === 'all' ? undefined : currentStatus.value,
dingdan_id: search.ordId || undefined, dingdan_id: search.ordId || undefined,
laoban_id: search.laobanId || undefined, laoban_id: search.laobanId || undefined,
dashou_id: search.dashouId || undefined, dashou_id: search.dashouId || undefined,
@@ -237,6 +245,12 @@ const fetchOrders = async () => {
jieshao: search.jieshao || undefined, jieshao: search.jieshao || undefined,
clkf: search.clkf || undefined clkf: search.clkf || undefined
} }
if (currentStatus.value === 'auto_settle') {
params.zhuangtai = '8'
params.auto_settle_pending = 1
} else if (currentStatus.value !== 'all') {
params.zhuangtai = currentStatus.value
}
if (currentType.value !== '' && currentType.value !== null && currentType.value !== undefined) { if (currentType.value !== '' && currentType.value !== null && currentType.value !== undefined) {
params.leixing = currentType.value params.leixing = currentType.value
} }
@@ -344,6 +358,24 @@ const handlePageChange = (val) => { pageNum.value = val; fetchOrders(); updateQu
const goToDetail = (dingdanId) => router.push(`/order/detail/${dingdanId}`) const goToDetail = (dingdanId) => router.push(`/order/detail/${dingdanId}`)
const formatPrice = (price) => parseFloat(price || 0).toFixed(2) const formatPrice = (price) => parseFloat(price || 0).toFixed(2)
const formatRemainText = (sec) => {
const s = Math.max(0, Number(sec) || 0)
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
if (h >= 24) return `${Math.floor(h / 24)}${h % 24}小时`
if (h > 0) return `${h}小时${m}`
if (m > 0) return `${m}分钟`
return s > 0 ? `${s}` : '即将结算'
}
const formatAutoSettle = (row) => {
if (!row || !row.auto_settle_eligible) return ''
const remain = row.auto_settle_remain_seconds
if (remain == null || remain === '') {
return row.auto_expire_at ? `预计 ${row.auto_expire_at}` : ''
}
const text = formatRemainText(remain)
return Number(remain) <= 0 ? '即将自动结算' : `预计自动结算 ${text}`
}
const getStatusText = (code) => { const getStatusText = (code) => {
const map = { '1':'下单中','7':'指定中','2':'进行中','8':'待结算','3':'已完成','4':'退款审核','5':'已退款','6':'退款失败' } const map = { '1':'下单中','7':'指定中','2':'进行中','8':'待结算','3':'已完成','4':'退款审核','5':'已退款','6':'退款失败' }
return map[code] || '未知' return map[code] || '未知'
@@ -394,6 +426,8 @@ onMounted(() => {
.stat-label { font-size: 13px; color: #6b7a88; margin-bottom: 8px; } .stat-label { font-size: 13px; color: #6b7a88; margin-bottom: 8px; }
.stat-value { font-size: 24px; font-weight: 600; color: #1a2634; } .stat-value { font-size: 24px; font-weight: 600; color: #1a2634; }
.order-table { width: 100%; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.02); margin-bottom: 20px; } .order-table { width: 100%; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.02); margin-bottom: 20px; }
.auto-settle-cell { color: #c62828; font-size: 13px; }
.muted { color: #9aa5b1; }
.pagination { display: flex; justify-content: flex-end; } .pagination { display: flex; justify-content: flex-end; }
.no-permission { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; } .no-permission { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60vh; }
.no-permission-icon { font-size: 80px; margin-bottom: 20px; opacity: 0.6; } .no-permission-icon { font-size: 80px; margin-bottom: 20px; opacity: 0.6; }