From 339c8f508ca67038992cdb88909852f2190197c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=AF=E4=B9=90=E5=8A=A0=E5=86=B0?= <11869599+addie66@user.noreply.gitee.com> Date: Fri, 2 Oct 2026 15:39:03 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(split-order):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=88=86=E8=B4=A6=E8=AE=A2=E5=8D=95=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加订单关联关系,支持查询原始订单信息 - 实现分账订单列表的搜索过滤功能,支持买家、订单号、状态等多维度筛选 - 优化前端页面展示,添加分页、样式美化和交互提示 - 增加重试分账和查询状态的操作确认提示 - 格式化显示买家信息、金额和时间等数据字段 - 支持汇付和微信两种分账通道的状态显示 ``` --- addons/shopro/model/split/SplitOrder.php | 5 + .../admin/controller/shopro/split/Order.php | 145 ++++++++++++++- .../admin/view/shopro/split/order/index.html | 165 ++++++++++++++++-- .../assets/js/backend/shopro/split/order.js | 102 ++++++++++- 4 files changed, 392 insertions(+), 25 deletions(-) diff --git a/addons/shopro/model/split/SplitOrder.php b/addons/shopro/model/split/SplitOrder.php index f07bc02..1c3aeef 100644 --- a/addons/shopro/model/split/SplitOrder.php +++ b/addons/shopro/model/split/SplitOrder.php @@ -15,4 +15,9 @@ class SplitOrder extends Model { return $this->hasMany(SplitOrderReceiver::class, 'split_order_id', 'id'); } + + public function order() + { + return $this->belongsTo(\app\admin\model\shopro\order\Order::class, 'order_id', 'id'); + } } diff --git a/application/admin/controller/shopro/split/Order.php b/application/admin/controller/shopro/split/Order.php index 8706f4d..8daf25e 100644 --- a/application/admin/controller/shopro/split/Order.php +++ b/application/admin/controller/shopro/split/Order.php @@ -6,11 +6,21 @@ use app\admin\controller\shopro\Base; use addons\shopro\library\split\Config as SplitConfig; use addons\shopro\library\split\SplitExecutor; use addons\shopro\library\split\ProfitSharingService; +use app\admin\model\shopro\order\Order as ShopOrder; +use app\admin\model\shopro\user\User as ShopUser; class Order extends Base { protected $model = null; + protected $statusMap = [ + 0 => '待分账', + 1 => '分账中', + 2 => '成功', + 3 => '部分成功', + 4 => '失败', + ]; + public function _initialize() { parent::_initialize(); @@ -21,14 +31,143 @@ class Order extends Base { $this->request->filter(['strip_tags']); if ($this->request->isAjax()) { - list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(); - $total = $this->model->where($where)->count(); - $list = $this->model->with('receivers')->where($where)->order($sort, $order)->limit($offset, $limit)->select(); + $nobuildfields = ['buyer_keyword', 'order_sn', 'status', 'channel', 'createtime']; + list($where, $sort, $order, $offset, $limit) = $this->custombuildparams(null, $nobuildfields); + + $query = $this->buildSearch(); + $total = $query->where($where)->count(); + $list = $this->buildSearch() + ->with([ + 'receivers', + 'order' => function ($q) { + $q->field('id,order_sn,user_id,pay_fee,paytime,status'); + }, + 'order.user' => function ($q) { + $q->field('id,nickname,mobile'); + }, + ]) + ->where($where) + ->order($sort, $order) + ->limit($offset, $limit) + ->select(); + + $list = $this->formatSplitOrderList($list); + return $this->success('操作成功', null, ['total' => $total, 'rows' => $list]); } + $this->assignconfig('splitHuifuOnly', (new SplitConfig())->isHuifuMode()); return $this->view->fetch(); } + protected function buildSearch() + { + $filter = (array) json_decode($this->request->get('filter', ''), true); + $query = $this->model; + + if (!empty($filter['order_sn'])) { + $orderIds = ShopOrder::where('order_sn', 'like', '%' . trim($filter['order_sn']) . '%')->column('id'); + $query = $query->where('order_id', 'in', $orderIds ?: [0]); + } + if (!empty($filter['buyer_keyword'])) { + $kw = trim($filter['buyer_keyword']); + $userIds = ShopUser::where('nickname|mobile', 'like', '%' . $kw . '%')->column('id'); + $orderIds = $userIds + ? ShopOrder::where('user_id', 'in', $userIds)->column('id') + : []; + $query = $query->where('order_id', 'in', $orderIds ?: [0]); + } + if (isset($filter['status']) && $filter['status'] !== '') { + $query = $query->where('status', intval($filter['status'])); + } + if (!empty($filter['channel'])) { + $query = $query->where('channel', $filter['channel']); + } + if (!empty($filter['createtime'])) { + $parts = explode(' - ', $filter['createtime']); + if (count($parts) === 2) { + $start = strtotime($parts[0]); + $end = strtotime($parts[1] . ' 23:59:59'); + if ($start && $end) { + $query = $query->where('createtime', 'between', [$start, $end]); + } + } + } + + return $query; + } + + protected function formatSplitOrderList($list) + { + $rows = []; + foreach ($list as $item) { + $data = is_array($item) ? $item : $item->toArray(); + + $order = $data['order'] ?? null; + $user = is_array($order) ? ($order['user'] ?? null) : null; + + $status = intval($data['status'] ?? 0); + $channel = $data['channel'] ?? 'wechat'; + $buyerNickname = $user['nickname'] ?? ''; + $buyerMobile = $user['mobile'] ?? ''; + + $receivers = $data['receivers'] ?? []; + $lines = []; + foreach ($receivers as $recv) { + $r = is_array($recv) ? $recv : (method_exists($recv, 'toArray') ? $recv->toArray() : []); + $lines[] = sprintf( + '%s ¥%s', + $this->roleTypeLabel($r['role_type'] ?? ''), + number_format(floatval($r['amount'] ?? 0), 2, '.', '') + ); + } + + $data['buyer_nickname'] = $buyerNickname; + $data['buyer_mobile'] = $buyerMobile; + $data['buyer_display'] = $this->formatBuyerDisplay( + $buyerNickname, + $buyerMobile, + is_array($order) ? intval($order['user_id'] ?? 0) : 0 + ); + $data['status_text'] = $this->statusMap[$status] ?? '-'; + $data['channel_text'] = $channel === 'huifu' ? '汇付' : '微信'; + $data['createtime_text'] = !empty($data['createtime']) + ? date('Y-m-d H:i:s', $data['createtime']) + : '-'; + $data['order_paytime_text'] = (is_array($order) && !empty($order['paytime'])) + ? date('Y-m-d H:i:s', $order['paytime']) + : '-'; + $data['receiver_summary'] = $lines ? implode(';', $lines) : '—'; + + unset($data['order']); + $rows[] = $data; + } + + return $rows; + } + + protected function formatBuyerDisplay($nickname, $mobile, $userId) + { + $nickname = trim((string) $nickname); + $mobile = trim((string) $mobile); + if ($nickname !== '') { + return $mobile !== '' ? ($nickname . '(' . $mobile . ')') : $nickname; + } + if ($mobile !== '') { + return $mobile; + } + return $userId ? ('用户#' . $userId) : '—'; + } + + protected function roleTypeLabel($type) + { + $map = [ + 'platform' => '平台', + 'promoter' => '推广员', + 'regional_agent' => '区域公司', + ]; + return $map[$type] ?? ($type ?: '接收方'); + } + public function retry($ids = null) { $id = $ids ?: $this->request->post('id'); diff --git a/application/admin/view/shopro/split/order/index.html b/application/admin/view/shopro/split/order/index.html index 8a28c8e..f89c967 100644 --- a/application/admin/view/shopro/split/order/index.html +++ b/application/admin/view/shopro/split/order/index.html @@ -1,19 +1,154 @@ -
- - - - - - - - - - - + + +
+
+

+ 分账记录展示向支付通道(微信/汇付)发起的网关分账结果,与「分销佣金」「区域差价」钱包入账是不同链路。 + 仅已支付且触发分账的订单会出现记录;列表为空时请先确认分账已开启并完成一笔测试支付。 +

+
+
+
+ + + + + + + + + + + + + + + 搜索 + 重置 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + +
+

+ 「重试分账」仅对失败记录有效,会向通道重新发起分账;「同步通道状态」只拉取最新结果并更新本页,不会重复扣款。 +

+
diff --git a/public/assets/js/backend/shopro/split/order.js b/public/assets/js/backend/shopro/split/order.js index 4fa86c2..1ec914a 100644 --- a/public/assets/js/backend/shopro/split/order.js +++ b/public/assets/js/backend/shopro/split/order.js @@ -3,20 +3,108 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe index: function () { new Vue({ el: '#splitOrderIndex', - data: { listData: [], loading: false }, - mounted() { this.getList(); }, + data() { + return { + listData: [], + loading: false, + currentPage: 1, + limit: 20, + total: 0, + dateRange: [], + filters: { + buyer_keyword: '', + order_sn: '', + status: '', + channel: '' + }, + splitHuifuOnly: !!(typeof Config !== 'undefined' && Config.splitHuifuOnly) + }; + }, + mounted() { + this.getList(); + }, methods: { + buildFilter() { + var filter = Object.assign({}, this.filters); + if (this.dateRange && this.dateRange.length === 2) { + filter.createtime = this.dateRange[0] + ' - ' + this.dateRange[1]; + } + return filter; + }, + search() { + this.currentPage = 1; + this.getList(); + }, + resetFilters() { + this.filters = { buyer_keyword: '', order_sn: '', status: '', channel: '' }; + this.dateRange = []; + this.currentPage = 1; + this.getList(); + }, + onSizeChange() { + this.currentPage = 1; + this.getList(); + }, getList() { this.loading = true; - Fast.api.ajax({ url: 'shopro/split/order/index', type: 'GET', data: { limit: 50, sort: 'id', order: 'desc' } }, (ret, res) => { - this.listData = (res.data && res.data.rows) || []; + Fast.api.ajax({ + url: 'shopro/split/order/index', + type: 'GET', + data: { + offset: (this.currentPage - 1) * this.limit, + limit: this.limit, + sort: 'id', + order: 'desc', + filter: JSON.stringify(this.buildFilter()), + op: JSON.stringify({}) + } + }, (ret, res) => { + const data = res.data || {}; + this.listData = data.rows || []; + this.total = data.total || 0; this.loading = false; return false; }, () => { this.loading = false; }); }, - retry(id) { Fast.api.ajax({ url: 'shopro/split/order/retry/ids/' + id, type: 'POST' }, () => this.getList()); }, - query(id) { Fast.api.ajax({ url: 'shopro/split/order/query/ids/' + id, type: 'POST' }, () => this.getList()); }, - statusText(s) { return { 0: '待分账', 1: '分账中', 2: '成功', 3: '部分成功', 4: '失败' }[s] || '-'; } + formatMoney(v) { + var n = parseFloat(v); + return isNaN(n) ? '0.00' : n.toFixed(2); + }, + openOrder(orderId) { + if (!orderId) return; + Fast.api.open('shopro/order/order/detail?id=' + orderId, '订单详情'); + }, + retry(row) { + var tip = this.splitHuifuOnly + ? '将向汇付重新发起该笔分账。若订单资金仍被冻结,请先发货/确认收货或按文档处理 FREEZE。' + : '将向微信/汇付重新发起该笔分账,请确认该订单尚未分账成功。'; + Layer.confirm( + '订单 ' + (row.order_sn || '') + '(' + (row.buyer_display || '买家') + ')
' + tip, + { title: '确认重试分账', btn: ['重试', '取消'] }, + () => { + Fast.api.ajax({ url: 'shopro/split/order/retry/ids/' + row.id, type: 'POST' }, () => { + this.getList(); + }); + } + ); + }, + query(id) { + Fast.api.ajax({ url: 'shopro/split/order/query/ids/' + id, type: 'POST' }, (ret, res) => { + var hint = '已同步通道状态并更新记录'; + if (res.data) { + try { + var d = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; + var parts = [d.resp_desc, d.resp_code, d.bank_message, d.status].filter(Boolean); + if (parts.length) { + hint += ':' + parts.join(' '); + } + } catch (e) { /* ignore */ } + } + Layer.msg(hint, { time: 4000 }); + this.getList(); + return false; + }); + } } }); }