feat(split): 优化分账订单页面UI和提示信息

- 简化错误提示文案,提升用户体验
- 重构分账订单列表页面样式,优化表格布局和响应式显示
- 添加分账来源标签和诊断详情弹窗功能
- 改进长文本内容的显示方式,支持水平滚动查看
- 调整列宽和文字说明,使界面更加清晰易读
```
这个提交包含在:
可乐加冰
2026-10-05 14:11:25 +08:00
父节点 43b36159b4
当前提交 764c9de5d8
共有 2 个文件被更改,包括 142 次插入 和 176 次删除

查看文件

@@ -82,7 +82,7 @@ class SplitCalculator
$payFee = floatval($order['pay_fee']);
$commissionRewards = CommissionReward::where('order_id', $orderId)->where('status', '>=', 0)->select();
if ($this->isEmptyResult($commissionRewards)) {
$issues[] = '该订单无分销佣金记录(未产生推广关系或未参与分销)';
$issues[] = '无分销佣金记录';
}
foreach ($this->toResultList($commissionRewards) as $reward) {
@@ -98,7 +98,7 @@ class SplitCalculator
$receiver = $this->getReceiver('promoter', $userId);
if (!$receiver) {
$issues[] = sprintf(
'推广员#%d 佣金¥%s 无汇付接收方(分账接收方 role_id 须等于该 user_id)',
'推广员#%d 佣金¥%s:未配置汇付接收方(role_id 须等于该 user_id)',
$userId,
number_format($amount, 2, '.', '')
);
@@ -131,7 +131,7 @@ class SplitCalculator
} elseif (!empty($ruleItems) && ($amountSource === 'rule' || ($amountSource === 'mixed' && empty($commissionItems)))) {
$finalSource = 'rule';
if ($amountSource === 'mixed' && !empty($issues)) {
$issues[] = '已回退到分账规则(如推广员 5%),金额与佣金不一致';
$issues[] = '已回退分账规则(如 5%),与佣金金额不一致';
}
}

查看文件

@@ -6,6 +6,11 @@
background: #f5f7fa;
min-height: 100%;
}
#splitOrderIndex .el-table .cell {
overflow: hidden;
line-height: 1.5;
vertical-align: top;
}
.page-card {
background: #fff;
border-radius: 8px;
@@ -26,176 +31,142 @@
gap: 10px;
align-items: center;
}
.cell-plain {
font-size: 13px;
color: #303133;
line-height: 1.5;
white-space: nowrap;
}
.cell-sub-plain {
font-size: 12px;
color: #909399;
line-height: 1.5;
margin-top: 4px;
white-space: nowrap;
}
/* 默认截断;悬浮后横向滚动查看完整内容 */
.scroll-cell {
max-width: 100%;
overflow-x: hidden;
overflow-x: auto;
overflow-y: hidden;
cursor: default;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
.scroll-cell:hover {
overflow-x: auto;
cursor: ew-resize;
}
.scroll-cell::-webkit-scrollbar {
height: 4px;
}
.scroll-cell::-webkit-scrollbar { height: 3px; }
.scroll-cell::-webkit-scrollbar-thumb {
background: rgba(116, 56, 213, 0.35);
background: rgba(116, 56, 213, 0.3);
border-radius: 2px;
}
.scroll-cell-inner {
display: inline-block;
white-space: nowrap;
min-width: min-content;
}
.scroll-hint {
display: none;
font-size: 10px;
color: #c0c4cc;
margin-top: 2px;
white-space: nowrap;
}
.scroll-cell:hover + .scroll-hint,
.scroll-cell-wrap:hover .scroll-hint {
display: block;
}
.scroll-cell-wrap {
max-width: 100%;
}
.amount-cell {
text-align: right;
white-space: nowrap;
}
.amount-cell .money-line {
font-size: 13px;
color: #303133;
line-height: 1.6;
}
.amount-cell .money-line.split-line {
font-size: 12px;
color: #909399;
}
.mono-text {
font-family: Consolas, Monaco, "Courier New", monospace;
letter-spacing: 0.2px;
}
.link-text {
color: #7438D5;
cursor: pointer;
font-size: 12px;
}
.link-text { color: #7438D5; cursor: pointer; }
.link-text:hover { opacity: 0.85; }
.money-text { color: #e1212b; font-weight: 600; }
.channel-tag {
display: inline-block;
margin-left: 6px;
padding: 0 6px;
margin-left: 4px;
padding: 0 5px;
font-size: 11px;
line-height: 18px;
border-radius: 4px;
border-radius: 3px;
background: #f4f4f5;
color: #606266;
vertical-align: middle;
}
.status-tag {
display: inline-block;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
white-space: nowrap;
}
.status-tag.s0 { background: #f4f4f5; color: #909399; }
.status-tag.s1 { background: #ecf5ff; color: #409eff; }
.status-tag.s2 { background: #f0f9eb; color: #67c23a; }
.status-tag.s3 { background: #fdf6ec; color: #e6a23c; }
.status-tag.s4 { background: #fef0f0; color: #f56c6c; }
.recv-list { display: flex; flex-direction: column; gap: 6px; }
.recv-row {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 6px;
font-size: 12px;
line-height: 1.5;
.amount-block {
text-align: right;
line-height: 1.65;
font-size: 13px;
color: #303133;
}
.recv-scroll {
flex: 1;
min-width: 0;
max-width: calc(100% - 120px);
}
.recv-role {
display: inline-block;
padding: 0 6px;
border-radius: 4px;
background: #f0ebfa;
color: #7438D5;
white-space: nowrap;
}
.recv-name { color: #303133; }
.recv-account {
color: #909399;
font-family: Consolas, Monaco, "Courier New", monospace;
font-size: 11px;
}
.recv-amt { color: #e1212b; font-weight: 600; margin-left: auto; white-space: nowrap; }
.recv-preview-hint {
font-size: 11px;
color: #c0c4cc;
margin-top: 2px;
}
.fail-reason {
font-size: 12px;
color: #f56c6c;
line-height: 1.5;
.amount-block .sub { font-size: 12px; color: #909399; }
.source-line {
margin-top: 6px;
text-align: right;
white-space: nowrap;
}
.source-tag {
display: inline-block;
padding: 0 6px;
border-radius: 4px;
border-radius: 3px;
font-size: 11px;
line-height: 18px;
white-space: nowrap;
margin-top: 4px;
}
.source-tag.commission { background: #ecf5ff; color: #409eff; }
.source-tag.rule { background: #fdf6ec; color: #e6a23c; }
.diagnose-hint {
.source-detail-btn {
font-size: 11px;
color: #e6a23c;
line-height: 1.5;
margin-top: 4px;
white-space: normal;
}
.op-hint {
font-size: 12px;
color: #909399;
margin-left: 4px;
cursor: pointer;
}
#splitOrderIndex .el-table .cell {
.source-detail-btn:hover { color: #7438D5; }
.recv-block {
padding: 6px 0;
border-bottom: 1px dashed #ebeef5;
}
.recv-block:last-child { border-bottom: none; padding-bottom: 0; }
.recv-line1 {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
min-width: 0;
}
.recv-role {
flex-shrink: 0;
padding: 0 5px;
border-radius: 3px;
background: #f0ebfa;
color: #7438D5;
font-size: 11px;
line-height: 18px;
}
.recv-name {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #303133;
}
.recv-amt {
flex-shrink: 0;
color: #e1212b;
font-weight: 600;
}
.recv-line2 {
margin-top: 3px;
font-size: 11px;
color: #909399;
}
.fail-text {
font-size: 12px;
color: #f56c6c;
line-height: 1.5;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.diag-pop {
font-size: 12px;
color: #606266;
line-height: 1.6;
max-height: 220px;
overflow-y: auto;
}
.diag-pop li { margin-bottom: 6px; }
.diag-pop li:last-child { margin-bottom: 0; }
.op-hint { font-size: 12px; color: #909399; margin-top: 10px; }
</style>
<div id="splitOrderIndex" v-cloak>
<div class="page-card">
<p class="page-intro">
<strong>分账记录</strong>展示向支付通道(微信/汇付)发起的<strong>网关分账</strong>结果,与「分销佣金」「区域差价」钱包入账是不同链路。
仅<strong>已支付且触发分账</strong>的订单会出现记录;列表为空时请先确认分账已开启并完成一笔测试支付。
<strong>分账记录</strong>为支付通道网关分账结果,与分销佣金钱包入账不同链路。
长订单号可在单元格内<strong>左右滑动</strong>查看;「规则兜底」时点 <strong>详情</strong> 可看未走佣金分账的原因。
</p>
</div>
<div class="page-card">
@@ -214,92 +185,89 @@
<el-option label="微信" value="wechat"></el-option>
</el-select>
<el-date-picker v-model="dateRange" type="daterange" size="small" range-separator="至"
start-placeholder="记录开始" end-placeholder="记录结束" value-format="yyyy-MM-dd"></el-date-picker>
start-placeholder="开始" end-placeholder="结束" value-format="yyyy-MM-dd"></el-date-picker>
<el-button type="primary" size="small" @click="search">搜索</el-button>
<el-button size="small" @click="resetFilters">重置</el-button>
</div>
</div>
<div class="page-card" style="padding-bottom: 8px;">
<el-table :data="listData" border stripe v-loading="loading"
empty-text="暂无分账记录。请确认已开启分账,并完成支付/确认收货后再查看。">
<el-table-column label="记录时间" prop="createtime_text" width="156"></el-table-column>
<el-table-column label="买家" min-width="130">
empty-text="暂无分账记录">
<el-table-column label="时间" prop="createtime_text" width="152"></el-table-column>
<el-table-column label="买家" width="128" show-overflow-tooltip>
<template slot-scope="s">
<div class="scroll-cell-wrap">
<div class="scroll-cell" @wheel="scrollCellWheel">
<div class="scroll-cell-inner cell-plain">{{ s.row.buyer_display || '—' }}</div>
<span>{{ s.row.buyer_display || '—' }}</span>
</template>
</el-table-column>
<el-table-column label="订单号" min-width="168">
<template slot-scope="s">
<div class="scroll-cell" @wheel="scrollCellWheel">
<div class="scroll-cell-inner">
<span class="link-text mono-text" @click="openOrder(s.row.order_id)">{{ s.row.order_sn }}</span>
<span class="channel-tag">{{ s.row.channel_text }}</span>
</div>
</div>
<div class="scroll-cell" @wheel="scrollCellWheel" v-if="s.row.out_order_no" style="margin-top:4px;">
<div class="scroll-cell-inner mono-text" style="color:#909399;font-size:11px;">{{ s.row.out_order_no }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="订单号" min-width="200">
<el-table-column label="金额 / 来源" width="132" align="right">
<template slot-scope="s">
<div class="scroll-cell-wrap">
<div class="scroll-cell" @wheel="scrollCellWheel">
<div class="scroll-cell-inner cell-plain">
<span class="link-text mono-text" @click="openOrder(s.row.order_id)">{{ s.row.order_sn || '—' }}</span>
<span class="channel-tag">{{ s.row.channel_text }}</span>
</div>
</div>
<div class="scroll-hint">悬浮可左右滑动</div>
<div class="scroll-cell" @wheel="scrollCellWheel" v-if="s.row.out_order_no" style="margin-top:4px;">
<div class="scroll-cell-inner cell-sub-plain mono-text">分账 {{ s.row.out_order_no }}</div>
</div>
<div class="amount-block">
<div>订单 <span class="money-text">¥{{ formatMoney(s.row.total_amount) }}</span></div>
<div class="sub">分账 <span class="money-text">¥{{ formatMoney(s.row.split_amount) }}</span></div>
</div>
</template>
</el-table-column>
<el-table-column label="金额" width="138" align="right">
<template slot-scope="s">
<div class="amount-cell">
<div class="money-line">订单 <span class="money-text">¥{{ formatMoney(s.row.total_amount) }}</span></div>
<div class="money-line split-line">分账 <span class="money-text">¥{{ formatMoney(s.row.split_amount) }}</span></div>
<span v-if="s.row.split_source_text" :class="'source-tag ' + (s.row.split_source === 'commission' ? 'commission' : 'rule')">
{{ s.row.split_source_text }}
<div class="source-line" v-if="s.row.split_source">
<span :class="'source-tag ' + (s.row.split_source === 'commission' ? 'commission' : 'rule')">
{{ s.row.split_source === 'commission' ? '按佣金' : '规则兜底' }}
</span>
<el-popover
v-if="s.row.split_source === 'rule' && s.row.split_diagnose && s.row.split_diagnose.length"
placement="left"
width="300"
trigger="click">
<ul class="diag-pop">
<li v-for="(t, i) in s.row.split_diagnose" :key="i">{{ t }}</li>
</ul>
<span slot="reference" class="source-detail-btn">详情</span>
</el-popover>
</div>
</template>
</el-table-column>
<el-table-column label="分账接收方" min-width="260">
<el-table-column label="分账给谁" min-width="168">
<template slot-scope="s">
<div v-if="s.row.receiver_details && s.row.receiver_details.length" class="recv-list">
<div v-for="(r, idx) in s.row.receiver_details" :key="idx" class="recv-row">
<span class="recv-role">{{ r.role_label }}</span>
<div class="scroll-cell recv-scroll" @wheel="scrollCellWheel" v-if="r.display_name || r.account">
<div class="scroll-cell-inner">
<span class="recv-name">{{ r.display_name }}</span>
<span class="recv-account" v-if="r.account"> {{ r.account }}</span>
</div>
<template v-if="s.row.receiver_details && s.row.receiver_details.length">
<div v-for="(r, idx) in s.row.receiver_details" :key="idx" class="recv-block">
<div class="recv-line1">
<span class="recv-role">{{ r.role_label }}</span>
<span class="recv-name" :title="r.display_name">{{ r.display_name }}</span>
<span class="recv-amt">¥{{ formatMoney(r.amount) }}</span>
</div>
<div class="recv-line2 scroll-cell" @wheel="scrollCellWheel" v-if="r.account">
<span class="scroll-cell-inner mono-text">{{ r.account }}</span>
</div>
<span class="recv-amt">¥{{ formatMoney(r.amount) }}</span>
</div>
<div v-if="s.row.receiver_preview" class="recv-preview-hint">按当前规则预估(历史失败单未落库明细)</div>
<div v-if="s.row.split_diagnose && s.row.split_diagnose.length && s.row.split_source === 'rule'" class="diagnose-hint">
{{ s.row.split_diagnose.join(';') }}
</div>
</div>
<span v-else class="cell-sub-plain">—</span>
<div v-if="s.row.receiver_preview" style="font-size:11px;color:#c0c4cc;margin-top:4px;">预估明细</div>
</template>
<span v-else style="color:#909399;">—</span>
</template>
</el-table-column>
<el-table-column label="状态" width="88" align="center">
<el-table-column label="状态" width="76" align="center">
<template slot-scope="s">
<span :class="'status-tag s' + s.row.status">{{ s.row.status_text }}</span>
</template>
</el-table-column>
<el-table-column label="失败原因" min-width="140">
<el-table-column label="失败原因" min-width="120">
<template slot-scope="s">
<div v-if="s.row.fail_reason" class="scroll-cell-wrap">
<div class="scroll-cell" @wheel="scrollCellWheel">
<div class="scroll-cell-inner fail-reason">{{ s.row.fail_reason }}</div>
</div>
</div>
<span v-else class="cell-sub-plain">—</span>
<div v-if="s.row.fail_reason" class="fail-text" :title="s.row.fail_reason">{{ s.row.fail_reason }}</div>
<span v-else style="color:#909399;">—</span>
</template>
</el-table-column>
<el-table-column label="重试" width="56" align="center" prop="retry_count"></el-table-column>
<el-table-column label="操作" width="156" fixed="right">
<el-table-column label="操作" width="128" fixed="right">
<template slot-scope="s">
<el-button type="text" v-if="s.row.status == 4" @click="retry(s.row)">重试分账</el-button>
<el-button type="text" @click="query(s.row.id)">同步状态</el-button>
<el-button type="text" size="small" v-if="s.row.status == 4" @click="retry(s.row)">重试</el-button>
<el-button type="text" size="small" @click="query(s.row.id)">同步</el-button>
</template>
</el-table-column>
</el-table>
@@ -315,9 +283,7 @@
@size-change="onSizeChange">
</el-pagination>
</div>
<p class="op-hint" style="margin-top:10px;">
订单号、分账单号等长字段<strong>鼠标悬浮后可左右滑动</strong>查看完整内容;「重试分账」仅对失败记录有效;「同步状态」只拉取通道结果,不会重复扣款。
</p>
<p class="op-hint">「重试」仅对失败记录向通道重新发起;「同步」只更新状态,不会重复扣款。</p>
</div>
</div>
<script src="__CDN__/assets/addons/shopro/libs/vue.js"></script>