```
feat(shopro_wechat_trade): 增加微信发货/确认收货同步页面功能 增加统计信息显示、筛选搜索功能和详细信息弹窗,优化用户体验 - 添加buildQuery方法用于构建查询条件,支持状态、同步类型和订单号筛选 - 添加getStats方法获取各状态统计数据并在页面显示统计栏 - 在index页面增加类型筛选、订单号搜索、重置和仅看失败按钮 - 实现detail方法用于获取同步记录详情 - 增加详细的CSS样式和前端交互逻辑 - 添加防止重复重试成功记录的验证 - 格式化JSON数据显示和错误信息展示 ```
这个提交包含在:
@@ -8,25 +8,68 @@ use think\Db;
|
||||
|
||||
class Sync extends Backend
|
||||
{
|
||||
protected function buildQuery($status = '', $syncType = '', $orderSn = '')
|
||||
{
|
||||
$query = Db::name('shopro_wechat_trade_sync');
|
||||
if ($status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
if ($syncType !== '') {
|
||||
$query->where('sync_type', $syncType);
|
||||
}
|
||||
if ($orderSn !== '') {
|
||||
$query->where('order_sn', 'like', '%' . $orderSn . '%');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getStats()
|
||||
{
|
||||
$rows = Db::name('shopro_wechat_trade_sync')
|
||||
->field('status, COUNT(*) AS cnt')
|
||||
->group('status')
|
||||
->select();
|
||||
$stats = ['success' => 0, 'pending' => 0, 'failed' => 0, 'total' => 0];
|
||||
foreach ($rows as $row) {
|
||||
$key = $row['status'] ?? '';
|
||||
if (isset($stats[$key])) {
|
||||
$stats[$key] = intval($row['cnt']);
|
||||
}
|
||||
$stats['total'] += intval($row['cnt']);
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$page = max(1, intval($this->request->get('page', 1)));
|
||||
$limit = max(1, min(100, intval($this->request->get('limit', 20))));
|
||||
$status = trim($this->request->get('status', ''));
|
||||
$syncType = trim($this->request->get('sync_type', ''));
|
||||
$orderSn = trim($this->request->get('order_sn', ''));
|
||||
|
||||
$query = Db::name('shopro_wechat_trade_sync')->order('id', 'desc');
|
||||
if ($status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
$total = $query->count();
|
||||
$query = $this->buildQuery($status, $syncType, $orderSn)->order('id', 'desc');
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
return json(['total' => $total, 'rows' => $list]);
|
||||
}
|
||||
|
||||
$this->view->assign('stats', $this->getStats());
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$id = intval($this->request->get('id'));
|
||||
$row = Db::name('shopro_wechat_trade_sync')->where('id', $id)->find();
|
||||
if (!$row) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
$this->success('', null, $row);
|
||||
}
|
||||
|
||||
public function retry()
|
||||
{
|
||||
$id = intval($this->request->post('id'));
|
||||
@@ -34,6 +77,9 @@ class Sync extends Backend
|
||||
if (!$row) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
if ($row['status'] === 'success') {
|
||||
$this->error('该记录已同步成功,无需重试');
|
||||
}
|
||||
try {
|
||||
$service = new WxShippingService();
|
||||
if ($row['sync_type'] === 'confirm') {
|
||||
|
||||
@@ -1,14 +1,122 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
<div class="panel-heading">微信发货/确认收货同步日志</div>
|
||||
<style>
|
||||
.wx-sync-page .page-tip {
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 14px;
|
||||
background: #f4f8ff;
|
||||
border: 1px solid #d9e8ff;
|
||||
border-radius: 4px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.wx-sync-page .page-tip strong { color: #303133; }
|
||||
.wx-sync-page .stats-bar {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.wx-sync-page .stat-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
}
|
||||
.wx-sync-page .stat-item .num {
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.wx-sync-page .stat-success { background: #f0f9eb; color: #67c23a; }
|
||||
.wx-sync-page .stat-pending { background: #fdf6ec; color: #e6a23c; }
|
||||
.wx-sync-page .stat-failed { background: #fef0f0; color: #f56c6c; }
|
||||
.wx-sync-page .toolbar-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.wx-sync-page .toolbar-row .form-control { width: auto; min-width: 140px; }
|
||||
.wx-sync-page .toolbar-row .order-sn-input { min-width: 220px; }
|
||||
.wx-sync-detail pre {
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.wx-sync-detail .detail-row {
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.wx-sync-detail .detail-label {
|
||||
color: #909399;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
<div class="panel panel-default panel-intro wx-sync-page">
|
||||
<div class="panel-heading">
|
||||
微信发货/确认收货同步日志
|
||||
<span class="text-muted" style="font-size:12px;margin-left:10px;">记录向微信平台同步的操作结果,供排查与手动重试</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="toolbar" class="toolbar">
|
||||
<select id="status-filter" class="form-control" style="width:160px;display:inline-block;">
|
||||
<div class="page-tip">
|
||||
<strong>说明:</strong>本页展示商城向微信「发货信息管理服务」同步的记录,包括
|
||||
<strong>发货同步</strong>(upload_shipping_info)与<strong>确认收货同步</strong>(notify_confirm_receive)。
|
||||
「同步成功」仅表示微信接口返回成功,<strong>不代表</strong>商城订单状态;订单实际状态请到「订单管理」查看。
|
||||
失败记录会按配置自动重试,也可在此手动重试。
|
||||
</div>
|
||||
|
||||
<div class="stats-bar">
|
||||
<span class="stat-item">全部<span class="num">{$stats.total|default=0}</span></span>
|
||||
<span class="stat-item stat-success">成功<span class="num">{$stats.success|default=0}</span></span>
|
||||
<span class="stat-item stat-pending">待重试/处理中<span class="num">{$stats.pending|default=0}</span></span>
|
||||
<span class="stat-item stat-failed">失败<span class="num">{$stats.failed|default=0}</span></span>
|
||||
</div>
|
||||
|
||||
<div id="toolbar" class="toolbar toolbar-row">
|
||||
<select id="status-filter" class="form-control">
|
||||
<option value="">全部状态</option>
|
||||
<option value="pending">pending</option>
|
||||
<option value="success">success</option>
|
||||
<option value="failed">failed</option>
|
||||
<option value="success">成功</option>
|
||||
<option value="pending">待重试/处理中</option>
|
||||
<option value="failed">失败</option>
|
||||
</select>
|
||||
<select id="type-filter" class="form-control">
|
||||
<option value="">全部类型</option>
|
||||
<option value="shipping">发货同步</option>
|
||||
<option value="confirm">确认收货同步</option>
|
||||
</select>
|
||||
<input type="text" id="order-sn-filter" class="form-control order-sn-input" placeholder="搜索订单号">
|
||||
<button type="button" class="btn btn-primary btn-search"><i class="fa fa-search"></i> 搜索</button>
|
||||
<button type="button" class="btn btn-default btn-reset">重置</button>
|
||||
<button type="button" class="btn btn-warning btn-filter-failed">仅看失败</button>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover" width="100%"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sync-detail-template" class="hide wx-sync-detail">
|
||||
<div style="padding:16px;">
|
||||
<div class="detail-row"><span class="detail-label">记录ID:</span><span class="detail-id"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">订单号:</span><span class="detail-order-sn"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">同步类型:</span><span class="detail-sync-type"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">同步状态:</span><span class="detail-status"></span></div>
|
||||
<div class="detail-row detail-delivery-mode-row"><span class="detail-label">发货模式:</span><span class="detail-delivery-mode"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">重试次数:</span><span class="detail-retry"></span></div>
|
||||
<div class="detail-row detail-error-row"><span class="detail-label">错误信息:</span><span class="detail-error"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">创建时间:</span><span class="detail-create"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">更新时间:</span><span class="detail-update"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">请求内容:</span></div>
|
||||
<pre class="detail-request"></pre>
|
||||
<div class="detail-row"><span class="detail-label">响应内容:</span></div>
|
||||
<pre class="detail-response"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,84 @@
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
|
||||
var SYNC_TYPE_MAP = {
|
||||
shipping: '发货同步',
|
||||
confirm: '确认收货同步'
|
||||
};
|
||||
|
||||
var DELIVERY_MODE_MAP = {
|
||||
1: '统一发货',
|
||||
2: '分拆发货'
|
||||
};
|
||||
|
||||
function escapeHtml(text) {
|
||||
return $('<div/>').text(text == null ? '' : text).html();
|
||||
}
|
||||
|
||||
function formatSyncType(value) {
|
||||
var text = SYNC_TYPE_MAP[value] || value || '-';
|
||||
if (value === 'shipping') {
|
||||
return '<span class="label label-info">' + text + '</span>';
|
||||
}
|
||||
if (value === 'confirm') {
|
||||
return '<span class="label label-primary">' + text + '</span>';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function formatStatus(row) {
|
||||
var status = row.status;
|
||||
var retryCount = parseInt(row.retry_count, 10) || 0;
|
||||
if (status === 'success') {
|
||||
return '<span class="label label-success">成功</span>';
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return '<span class="label label-danger">失败</span>';
|
||||
}
|
||||
if (status === 'pending') {
|
||||
if (retryCount > 0) {
|
||||
return '<span class="label label-warning">待重试</span>';
|
||||
}
|
||||
return '<span class="label label-default">处理中</span>';
|
||||
}
|
||||
return status || '-';
|
||||
}
|
||||
|
||||
function formatDeliveryMode(value, row) {
|
||||
if (row.sync_type !== 'shipping') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
var text = DELIVERY_MODE_MAP[value] || ('模式' + value);
|
||||
return text;
|
||||
}
|
||||
|
||||
function formatErrorInfo(value, row) {
|
||||
if (row.status === 'success') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
var parts = [];
|
||||
if (row.errcode !== null && row.errcode !== undefined && row.errcode !== '') {
|
||||
parts.push('[' + row.errcode + ']');
|
||||
}
|
||||
if (row.errmsg) {
|
||||
parts.push(row.errmsg);
|
||||
}
|
||||
return parts.length ? escapeHtml(parts.join(' ')) : '<span class="text-muted">-</span>';
|
||||
}
|
||||
|
||||
function formatJsonText(raw) {
|
||||
if (!raw) {
|
||||
return '(无)';
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(raw), null, 2);
|
||||
} catch (e) {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDatetime(value) {
|
||||
return value ? Table.api.formatter.datetime(value) : '-';
|
||||
}
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
Table.api.init({
|
||||
@@ -9,55 +89,186 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
sortOrder: 'desc',
|
||||
search: false,
|
||||
commonSearch: false,
|
||||
showExport: false,
|
||||
queryParams: function (params) {
|
||||
params.status = $('#status-filter').val();
|
||||
params.sync_type = $('#type-filter').val();
|
||||
params.order_sn = $.trim($('#order-sn-filter').val());
|
||||
return params;
|
||||
},
|
||||
columns: [
|
||||
{ field: 'id', title: 'ID' },
|
||||
{ field: 'order_sn', title: '订单号' },
|
||||
{ field: 'sync_type', title: '类型' },
|
||||
{ field: 'status', title: '状态' },
|
||||
{ field: 'delivery_mode', title: '发货模式' },
|
||||
{ field: 'errcode', title: 'errcode' },
|
||||
{ field: 'errmsg', title: 'errmsg' },
|
||||
{ field: 'retry_count', title: '重试次数' },
|
||||
{ field: 'id', title: 'ID', width: 70 },
|
||||
{
|
||||
field: 'order_sn',
|
||||
title: '订单号',
|
||||
formatter: function (value, row) {
|
||||
if (!value) {
|
||||
return '-';
|
||||
}
|
||||
if (row.order_id) {
|
||||
return '<a href="javascript:;" class="btn-link btn-order" data-id="' + row.order_id + '">' + escapeHtml(value) + '</a>';
|
||||
}
|
||||
return escapeHtml(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'sync_type',
|
||||
title: '同步类型',
|
||||
formatter: function (value) {
|
||||
return formatSyncType(value);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '同步状态',
|
||||
formatter: function (value, row) {
|
||||
return formatStatus(row);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'delivery_mode',
|
||||
title: '发货模式',
|
||||
formatter: function (value, row) {
|
||||
return formatDeliveryMode(value, row);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'errmsg',
|
||||
title: '错误信息',
|
||||
formatter: function (value, row) {
|
||||
return formatErrorInfo(value, row);
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'retry_count',
|
||||
title: '重试次数',
|
||||
width: 90,
|
||||
formatter: function (value, row) {
|
||||
if (row.status === 'success') {
|
||||
return '<span class="text-muted">-</span>';
|
||||
}
|
||||
return value || 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'createtime',
|
||||
title: '创建时间',
|
||||
formatter: function (value) {
|
||||
return value ? Table.api.formatter.datetime(value) : '';
|
||||
}
|
||||
formatter: formatDatetime
|
||||
},
|
||||
{
|
||||
field: 'operate',
|
||||
title: '操作',
|
||||
width: 140,
|
||||
table: table,
|
||||
events: Table.api.events.operate,
|
||||
formatter: function (value, row) {
|
||||
if (row.status === 'success') {
|
||||
return '-';
|
||||
var html = [];
|
||||
html.push('<a href="javascript:;" class="btn btn-xs btn-info btn-detail" data-id="' + row.id + '">详情</a>');
|
||||
if (row.status !== 'success') {
|
||||
html.push(' <a href="javascript:;" class="btn btn-xs btn-warning btn-retry" data-id="' + row.id + '">重试</a>');
|
||||
}
|
||||
return '<a href="javascript:;" class="btn btn-xs btn-warning btn-retry" data-id="' + row.id + '">重试</a>';
|
||||
return html.join('');
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
$('#status-filter').on('change', function () {
|
||||
function refreshTable() {
|
||||
table.bootstrapTable('refresh');
|
||||
}
|
||||
|
||||
$('#status-filter, #type-filter').on('change', refreshTable);
|
||||
|
||||
$('.btn-search').on('click', refreshTable);
|
||||
|
||||
$('#order-sn-filter').on('keypress', function (e) {
|
||||
if (e.which === 13) {
|
||||
refreshTable();
|
||||
}
|
||||
});
|
||||
|
||||
$('.btn-reset').on('click', function () {
|
||||
$('#status-filter').val('');
|
||||
$('#type-filter').val('');
|
||||
$('#order-sn-filter').val('');
|
||||
refreshTable();
|
||||
});
|
||||
|
||||
$('.btn-filter-failed').on('click', function () {
|
||||
$('#status-filter').val('failed');
|
||||
$('#type-filter').val('');
|
||||
refreshTable();
|
||||
});
|
||||
|
||||
table.on('click', '.btn-order', function () {
|
||||
var orderId = $(this).data('id');
|
||||
Fast.api.open('shopro/order/order/detail?id=' + orderId, '订单详情');
|
||||
});
|
||||
|
||||
table.on('click', '.btn-retry', function () {
|
||||
var id = $(this).data('id');
|
||||
$.post('shopro_wechat_trade/sync/retry', { id: id }, function (ret) {
|
||||
Backend.api.toastr[ret.code === 1 ? 'success' : 'error'](ret.msg);
|
||||
table.bootstrapTable('refresh');
|
||||
Layer.confirm('将重新向微信发起同步请求,是否继续?', { icon: 3, title: '确认重试' }, function (index) {
|
||||
Layer.close(index);
|
||||
$.post('shopro_wechat_trade/sync/retry', { id: id }, function (ret) {
|
||||
Backend.api.toastr[ret.code === 1 ? 'success' : 'error'](ret.msg);
|
||||
refreshTable();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
table.on('click', '.btn-detail', function () {
|
||||
var id = $(this).data('id');
|
||||
$.get('shopro_wechat_trade/sync/detail', { id: id }, function (ret) {
|
||||
if (ret.code !== 1) {
|
||||
Backend.api.toastr.error(ret.msg || '加载失败');
|
||||
return;
|
||||
}
|
||||
var row = ret.data || {};
|
||||
var $tpl = $('#sync-detail-template').clone().removeAttr('id').removeClass('hide');
|
||||
$tpl.find('.detail-id').text(row.id || '-');
|
||||
$tpl.find('.detail-order-sn').text(row.order_sn || '-');
|
||||
$tpl.find('.detail-sync-type').html(formatSyncType(row.sync_type));
|
||||
$tpl.find('.detail-status').html(formatStatus(row));
|
||||
if (row.sync_type === 'shipping') {
|
||||
$tpl.find('.detail-delivery-mode').text(DELIVERY_MODE_MAP[row.delivery_mode] || ('模式' + row.delivery_mode));
|
||||
$tpl.find('.detail-delivery-mode-row').show();
|
||||
} else {
|
||||
$tpl.find('.detail-delivery-mode-row').hide();
|
||||
}
|
||||
$tpl.find('.detail-retry').text(row.retry_count || 0);
|
||||
if (row.status === 'success') {
|
||||
$tpl.find('.detail-error-row').hide();
|
||||
} else {
|
||||
var errParts = [];
|
||||
if (row.errcode !== null && row.errcode !== undefined && row.errcode !== '') {
|
||||
errParts.push('[' + row.errcode + ']');
|
||||
}
|
||||
if (row.errmsg) {
|
||||
errParts.push(row.errmsg);
|
||||
}
|
||||
$tpl.find('.detail-error').text(errParts.join(' ') || '-');
|
||||
$tpl.find('.detail-error-row').show();
|
||||
}
|
||||
$tpl.find('.detail-create').text(formatDatetime(row.createtime));
|
||||
$tpl.find('.detail-update').text(formatDatetime(row.updatetime));
|
||||
$tpl.find('.detail-request').text(formatJsonText(row.request_payload));
|
||||
$tpl.find('.detail-response').text(formatJsonText(row.response_payload));
|
||||
|
||||
Layer.open({
|
||||
type: 1,
|
||||
title: '同步记录详情 #' + row.id,
|
||||
area: ['720px', '80%'],
|
||||
shadeClose: true,
|
||||
content: $tpl.prop('outerHTML')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -8,25 +8,68 @@ use think\Db;
|
||||
|
||||
class Sync extends Backend
|
||||
{
|
||||
protected function buildQuery($status = '', $syncType = '', $orderSn = '')
|
||||
{
|
||||
$query = Db::name('shopro_wechat_trade_sync');
|
||||
if ($status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
if ($syncType !== '') {
|
||||
$query->where('sync_type', $syncType);
|
||||
}
|
||||
if ($orderSn !== '') {
|
||||
$query->where('order_sn', 'like', '%' . $orderSn . '%');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function getStats()
|
||||
{
|
||||
$rows = Db::name('shopro_wechat_trade_sync')
|
||||
->field('status, COUNT(*) AS cnt')
|
||||
->group('status')
|
||||
->select();
|
||||
$stats = ['success' => 0, 'pending' => 0, 'failed' => 0, 'total' => 0];
|
||||
foreach ($rows as $row) {
|
||||
$key = $row['status'] ?? '';
|
||||
if (isset($stats[$key])) {
|
||||
$stats[$key] = intval($row['cnt']);
|
||||
}
|
||||
$stats['total'] += intval($row['cnt']);
|
||||
}
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$page = max(1, intval($this->request->get('page', 1)));
|
||||
$limit = max(1, min(100, intval($this->request->get('limit', 20))));
|
||||
$status = trim($this->request->get('status', ''));
|
||||
$syncType = trim($this->request->get('sync_type', ''));
|
||||
$orderSn = trim($this->request->get('order_sn', ''));
|
||||
|
||||
$query = Db::name('shopro_wechat_trade_sync')->order('id', 'desc');
|
||||
if ($status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
$total = $query->count();
|
||||
$query = $this->buildQuery($status, $syncType, $orderSn)->order('id', 'desc');
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
return json(['total' => $total, 'rows' => $list]);
|
||||
}
|
||||
|
||||
$this->view->assign('stats', $this->getStats());
|
||||
return $this->view->fetch();
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$id = intval($this->request->get('id'));
|
||||
$row = Db::name('shopro_wechat_trade_sync')->where('id', $id)->find();
|
||||
if (!$row) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
$this->success('', null, $row);
|
||||
}
|
||||
|
||||
public function retry()
|
||||
{
|
||||
$id = intval($this->request->post('id'));
|
||||
@@ -34,6 +77,9 @@ class Sync extends Backend
|
||||
if (!$row) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
if ($row['status'] === 'success') {
|
||||
$this->error('该记录已同步成功,无需重试');
|
||||
}
|
||||
try {
|
||||
$service = new WxShippingService();
|
||||
if ($row['sync_type'] === 'confirm') {
|
||||
|
||||
@@ -1,14 +1,122 @@
|
||||
<div class="panel panel-default panel-intro">
|
||||
<div class="panel-heading">微信发货/确认收货同步日志</div>
|
||||
<style>
|
||||
.wx-sync-page .page-tip {
|
||||
margin-bottom: 14px;
|
||||
padding: 12px 14px;
|
||||
background: #f4f8ff;
|
||||
border: 1px solid #d9e8ff;
|
||||
border-radius: 4px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.wx-sync-page .page-tip strong { color: #303133; }
|
||||
.wx-sync-page .stats-bar {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.wx-sync-page .stat-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
background: #f5f7fa;
|
||||
color: #606266;
|
||||
}
|
||||
.wx-sync-page .stat-item .num {
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.wx-sync-page .stat-success { background: #f0f9eb; color: #67c23a; }
|
||||
.wx-sync-page .stat-pending { background: #fdf6ec; color: #e6a23c; }
|
||||
.wx-sync-page .stat-failed { background: #fef0f0; color: #f56c6c; }
|
||||
.wx-sync-page .toolbar-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.wx-sync-page .toolbar-row .form-control { width: auto; min-width: 140px; }
|
||||
.wx-sync-page .toolbar-row .order-sn-input { min-width: 220px; }
|
||||
.wx-sync-detail pre {
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.wx-sync-detail .detail-row {
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.wx-sync-detail .detail-label {
|
||||
color: #909399;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
<div class="panel panel-default panel-intro wx-sync-page">
|
||||
<div class="panel-heading">
|
||||
微信发货/确认收货同步日志
|
||||
<span class="text-muted" style="font-size:12px;margin-left:10px;">记录向微信平台同步的操作结果,供排查与手动重试</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="toolbar" class="toolbar">
|
||||
<select id="status-filter" class="form-control" style="width:160px;display:inline-block;">
|
||||
<div class="page-tip">
|
||||
<strong>说明:</strong>本页展示商城向微信「发货信息管理服务」同步的记录,包括
|
||||
<strong>发货同步</strong>(upload_shipping_info)与<strong>确认收货同步</strong>(notify_confirm_receive)。
|
||||
「同步成功」仅表示微信接口返回成功,<strong>不代表</strong>商城订单状态;订单实际状态请到「订单管理」查看。
|
||||
失败记录会按配置自动重试,也可在此手动重试。
|
||||
</div>
|
||||
|
||||
<div class="stats-bar">
|
||||
<span class="stat-item">全部<span class="num">{$stats.total|default=0}</span></span>
|
||||
<span class="stat-item stat-success">成功<span class="num">{$stats.success|default=0}</span></span>
|
||||
<span class="stat-item stat-pending">待重试/处理中<span class="num">{$stats.pending|default=0}</span></span>
|
||||
<span class="stat-item stat-failed">失败<span class="num">{$stats.failed|default=0}</span></span>
|
||||
</div>
|
||||
|
||||
<div id="toolbar" class="toolbar toolbar-row">
|
||||
<select id="status-filter" class="form-control">
|
||||
<option value="">全部状态</option>
|
||||
<option value="pending">pending</option>
|
||||
<option value="success">success</option>
|
||||
<option value="failed">failed</option>
|
||||
<option value="success">成功</option>
|
||||
<option value="pending">待重试/处理中</option>
|
||||
<option value="failed">失败</option>
|
||||
</select>
|
||||
<select id="type-filter" class="form-control">
|
||||
<option value="">全部类型</option>
|
||||
<option value="shipping">发货同步</option>
|
||||
<option value="confirm">确认收货同步</option>
|
||||
</select>
|
||||
<input type="text" id="order-sn-filter" class="form-control order-sn-input" placeholder="搜索订单号">
|
||||
<button type="button" class="btn btn-primary btn-search"><i class="fa fa-search"></i> 搜索</button>
|
||||
<button type="button" class="btn btn-default btn-reset">重置</button>
|
||||
<button type="button" class="btn btn-warning btn-filter-failed">仅看失败</button>
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover" width="100%"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sync-detail-template" class="hide wx-sync-detail">
|
||||
<div style="padding:16px;">
|
||||
<div class="detail-row"><span class="detail-label">记录ID:</span><span class="detail-id"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">订单号:</span><span class="detail-order-sn"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">同步类型:</span><span class="detail-sync-type"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">同步状态:</span><span class="detail-status"></span></div>
|
||||
<div class="detail-row detail-delivery-mode-row"><span class="detail-label">发货模式:</span><span class="detail-delivery-mode"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">重试次数:</span><span class="detail-retry"></span></div>
|
||||
<div class="detail-row detail-error-row"><span class="detail-label">错误信息:</span><span class="detail-error"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">创建时间:</span><span class="detail-create"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">更新时间:</span><span class="detail-update"></span></div>
|
||||
<div class="detail-row"><span class="detail-label">请求内容:</span></div>
|
||||
<pre class="detail-request"></pre>
|
||||
<div class="detail-row"><span class="detail-label">响应内容:</span></div>
|
||||
<pre class="detail-response"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
在新工单中引用
屏蔽一个用户