diff --git a/backend/app/services/order/OrderCenterServices.php b/backend/app/services/order/OrderCenterServices.php
index 9a3817be9..36add57c3 100644
--- a/backend/app/services/order/OrderCenterServices.php
+++ b/backend/app/services/order/OrderCenterServices.php
@@ -500,6 +500,17 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
// 确保 buildGroupedData 和前端引擎都能拿到正确的状态文案
$detail['status_text'] = OrderCenter::$statusMap[$detail['status_code'] ?? ''] ?? ($detail['status_code'] ?? '');
+ // 用户已删除:详情状态统一展示「已删除」(is_del 为软删标记,不改写 status_code)
+ if ((int)($detail['is_del'] ?? 0) === 1) {
+ $detail['status_text'] = '已删除';
+ $detail['business_status'] = [
+ 'code' => 'deleted',
+ 'label' => '已删除',
+ 'text' => '已删除',
+ 'type' => 'danger',
+ ];
+ }
+
// 构建操作按钮(委托给产品类型 Handler)
$detail['order_buttons'] = $this->getOrderButtonsFromHandler($detail);
// 构建订单生命周期进度条(不同订单类型节点不同,委托给产品类型 Handler)
@@ -2622,7 +2633,8 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
/**
* 构建关键词搜索闭包(后台订单列表/统计共用)
* 按 search_field 限定搜索字段:order_no=订单号前缀、contact_name=收货人、
- * contact_phone=联系电话、uid=用户ID精确匹配,all/其它=订单号+联系人+电话
+ * contact_phone=联系电话、uid=用户ID精确匹配;
+ * all/其它=用户相关(收货人+电话,纯数字兼查 UID),订单号走独立 order_no 条件
* 收货人/电话存于 extra_info JSON,走 JSON 路径匹配
* @param string $keyword 关键词(调用方需先 trim)
* @param string $searchField 搜索字段标识
@@ -2651,10 +2663,12 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
$q->where('uid', '=', (int) $keyword);
break;
default:
- // 全部字段:订单号前缀 + 收货人 + 电话
- $q->whereOr('order_no', 'LIKE', "{$escaped}%")
- ->whereOrRaw("extra_info->>'$.contact.name' LIKE ?", ["%{$escaped}%"])
+ // 用户搜索「全部」:收货人 + 电话;纯数字时兼查 UID(订单号由独立筛选项承担)
+ $q->whereRaw("extra_info->>'$.contact.name' LIKE ?", ["%{$escaped}%"])
->whereOrRaw("extra_info->>'$.contact.phone' LIKE ?", ["%{$escaped}%"]);
+ if (ctype_digit((string) $keyword)) {
+ $q->whereOr('uid', '=', (int) $keyword);
+ }
}
});
};
@@ -2764,20 +2778,25 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac
}
}
+ // 扩展表子状态(如待发货/待收货)经 _order_nos 两步筛选;不可因无 extra_conditions 而丢弃
+ $orderNos = $where['_order_nos'] ?? [];
+
if (empty($extraConditions)) {
- // 无扩展条件:直接走 DAO 方法
+ // 无闭包扩展条件:直接走 DAO 方法
$finalWhere = $searchWhere;
+ if (!empty($orderNos)) {
+ $finalWhere[] = ['order_no', 'IN', $orderNos];
+ }
if (!empty($afterSaleOrderNos)) {
$finalWhere[] = ['order_no', 'IN', $afterSaleOrderNos];
}
$list = $this->dao->selectList($finalWhere, $fields, $page, $limit, 'id DESC');
$total = $this->dao->count($finalWhere);
} else {
- // 有扩展条件:用 DAO 提供的查询构建器,避免直接 Db::table
+ // 有闭包扩展条件:用 DAO 提供的查询构建器,避免直接 Db::table
$query = $this->dao->getDb()->where($searchWhere);
// 扩展表条件用两步查询(先查 order_no,再 IN),不 JOIN
- $orderNos = $where['_order_nos'] ?? [];
if (!empty($orderNos)) {
$query->whereIn('order_no', $orderNos);
}
diff --git a/backend/modules/product/normal/controller/admin/OrderController.php b/backend/modules/product/normal/controller/admin/OrderController.php
index a1e956357..14040c837 100644
--- a/backend/modules/product/normal/controller/admin/OrderController.php
+++ b/backend/modules/product/normal/controller/admin/OrderController.php
@@ -1243,6 +1243,23 @@ class OrderController extends AuthController
]);
$where['order_category'] = 'goods';
$where['product_type'] = 'normal';
+ $where['is_deleted'] = !empty($where['is_deleted']);
+
+ // 订单类型映射:与 list 接口一致
+ $type = $where['type'] ?? '';
+ if (!empty($type)) {
+ $activityType = $this->mapOrderType($type);
+ if ($activityType !== '') {
+ $where['activity_type'] = $activityType;
+ }
+ }
+ unset($where['type']);
+
+ // 业务状态(待发货/待收货等)与列表同口径翻译
+ $businessStatus = (string)$this->request->get('business_status', '');
+ if ($businessStatus !== '') {
+ $this->translateBusinessStatus($businessStatus, $where);
+ }
try {
$result = $centerServices->getAdminList($where, (int)$where['page'], (int)$where['limit']);
@@ -1259,12 +1276,18 @@ class OrderController extends AuthController
$exportData = [];
foreach ($list as $item) {
- $items = $item['items'] ?? [];
- $storeName = '';
- if (!empty($items)) {
- $first = $items[0];
- $storeName = $first['store_name'] ?? $first['title'] ?? '';
+ // 列表接口无顶层 items,商品名在中台 title 或 extra_info.items[].store_name
+ $goodsItems = $item['extra_info']['items'] ?? ($item['items'] ?? []);
+ $goodsNames = [];
+ foreach ((array)$goodsItems as $goods) {
+ $name = trim((string)($goods['store_name'] ?? $goods['title'] ?? $goods['name'] ?? ''));
+ if ($name !== '') {
+ $goodsNames[] = $name;
+ }
}
+ $storeName = $goodsNames
+ ? implode(' / ', $goodsNames)
+ : (string)($item['title'] ?? $item['subtitle'] ?? '');
$exportData[] = [
'order_no' => $item['order_no'] ?? '',
diff --git a/frontend/admin/src/pages/order/orderCenter/detail.vue b/frontend/admin/src/pages/order/orderCenter/detail.vue
index e89acd9d1..a82578f8a 100644
--- a/frontend/admin/src/pages/order/orderCenter/detail.vue
+++ b/frontend/admin/src/pages/order/orderCenter/detail.vue
@@ -14,10 +14,11 @@
订单状态
- {{
- getStatusLabel(detailData.status_code)
- }}
-
+ {{ getStatusLabel(detailData) }}
+
退款中
@@ -114,7 +115,11 @@ function getLogisticsData() {
});
}
-function getStatusLabel(code) {
+/** 详情状态:用户已删除优先展示「已删除」,否则按 status_code / status_text */
+function getStatusLabel(detail) {
+ if (detail && Number(detail.is_del) === 1) return '已删除';
+ if (detail?.status_text) return detail.status_text;
+ const code = typeof detail === 'string' ? detail : detail?.status_code;
const map = {
pending_payment: '待支付',
processing: '待处理',
@@ -124,12 +129,15 @@ function getStatusLabel(code) {
after_sale: '售后中',
refunding: '退款中',
refunded: '已退款',
+ deleted: '已删除',
};
return map[code] || code || '--';
}
/* 与列表 getStatusClass 同口径 */
-function getStatusClass(code) {
+function getStatusClass(detail) {
+ if (detail && Number(detail.is_del) === 1) return 'danger';
+ const code = typeof detail === 'string' ? detail : detail?.status_code;
const map = {
pending_payment: 'warning',
processing: 'primary',
@@ -139,6 +147,7 @@ function getStatusClass(code) {
after_sale: 'warning',
refunding: 'warning',
refunded: 'info',
+ deleted: 'danger',
};
return map[code] || 'info';
}
diff --git a/frontend/admin/src/pages/order/orderCenter/index.vue b/frontend/admin/src/pages/order/orderCenter/index.vue
index 48c94f608..33387fd9a 100644
--- a/frontend/admin/src/pages/order/orderCenter/index.vue
+++ b/frontend/admin/src/pages/order/orderCenter/index.vue
@@ -204,8 +204,8 @@
- {{ getStatusLabel(scope.row.status_code) }}
-
+
{{ getStatusLabel(scope.row) }}
+
{{ scope.row.business_status.label }}
@@ -469,12 +469,18 @@ function getProductTypeLabel(type) {
return item ? item.label : type || '--';
}
-function getStatusLabel(code) {
+function getStatusLabel(rowOrCode) {
+ const row = typeof rowOrCode === 'object' && rowOrCode ? rowOrCode : null;
+ if (row && Number(row.is_del) === 1) return '已删除';
+ const code = row ? row.status_code : rowOrCode;
const item = statusCodeOptions.find((opt) => opt.key === code);
return item ? item.label : code || '--';
}
-function getStatusClass(code) {
+function getStatusClass(rowOrCode) {
+ const row = typeof rowOrCode === 'object' && rowOrCode ? rowOrCode : null;
+ if (row && Number(row.is_del) === 1) return 'danger';
+ const code = row ? row.status_code : rowOrCode;
const map = {
pending_payment: 'warning',
processing: 'primary',
@@ -484,6 +490,7 @@ function getStatusClass(code) {
after_sale: 'warning',
refunding: 'warning',
refunded: 'info',
+ deleted: 'danger',
};
return map[code] || 'info';
}
diff --git a/frontend/admin/src/pages/plugin/normal/normalOrder.js b/frontend/admin/src/pages/plugin/normal/normalOrder.js
index 206258177..b70e51a69 100644
--- a/frontend/admin/src/pages/plugin/normal/normalOrder.js
+++ b/frontend/admin/src/pages/plugin/normal/normalOrder.js
@@ -189,7 +189,8 @@ export function getOrderExpress(orderNo) {
* 供详情弹窗等场景复用(列表/详情状态展示保持一致)
*/
export function getOrderBusinessStatus(row = {}) {
- if (row.is_del === 1) {
+ // is_del 可能为数字 1 或字符串 "1",与列表口径一致按真值判断
+ if (Number(row.is_del) === 1) {
return { code: 'deleted', text: '已删除', type: 'danger' };
}
if (row.status_code === 'pending_payment') {
diff --git a/frontend/admin/src/pages/plugin/normal/order/List.vue b/frontend/admin/src/pages/plugin/normal/order/List.vue
index 625b9cc04..26a569f28 100644
--- a/frontend/admin/src/pages/plugin/normal/order/List.vue
+++ b/frontend/admin/src/pages/plugin/normal/order/List.vue
@@ -122,46 +122,73 @@
-
-
-
请上传 Excel 文件(xls/xlsx),格式如下:
-
-
- CP1234567890
-
-
- 顺丰速运
-
-
- SF1234567890
-
-
-
- 第一行为表头,从第二行开始为数据
-
+
+
+
+ 请按下方格式编辑 Excel(xls / xlsx)后上传,系统将按行完成发货。
+
+
+
+
- 选取文件上传
+
+
+
+ 正在上传并处理,请稍候…
+ 将文件拖到此处,或点击上传
+
+
仅支持 .xls、.xlsx
+
-
-
@@ -197,7 +224,9 @@ import {
getPrintPickList,
getOrderExpress,
exportOrderList as exportNormalOrder,
+ batchShipment,
} from '../normalOrder';
+import { getCookies } from '@/libs/util';
import OrderStatistics from './components/OrderStatistics';
import OrderSearch from './components/OrderSearch';
@@ -276,17 +305,24 @@ const editOrderData = ref(null);
const batchShipmentModal = ref(false);
const batchResult = ref(null);
const showBatchErrors = ref(false);
-const templateData = [{}];
+const batchUploading = ref(false);
+const batchUploadRef = ref(null);
+const templateData = [
+ {
+ order_no: 'CP1234567890',
+ delivery_name: '顺丰速运',
+ delivery_id: 'SF1234567890',
+ },
+];
// 批量删除
const selectedIds = ref([]);
-const adminApi = computed(() => {
- return import.meta.env.VITE_APP_API_URL + '/adminapi/';
-});
-const headers = computed(() => {
- return { 'Authori-zation': 'Bearer ' + localStorage.getItem('token') };
-});
+// 先上传到文件服务拿到路径,再调批量发货接口(与商品导入同口径)
+const batchUploadUrl = computed(() => setting.apiBaseURL + '/file/upload/1');
+const batchUploadHeaders = computed(() => ({
+ 'Authori-zation': 'Bearer ' + (getCookies('token') || ''),
+}));
/* ===== 数据加载 ===== */
@@ -616,29 +652,61 @@ function openRecord(row) {
function openBatchShipment() {
batchResult.value = null;
showBatchErrors.value = false;
+ batchUploading.value = false;
batchShipmentModal.value = true;
}
-function onBatchUploadSuccess(res) {
- batchResult.value = res;
- if (res.code === 200) {
- ElMessage.success(res.msg || '批量发货完成');
- getList();
- } else {
- ElMessage.error(res.msg || '批量发货失败');
+function onBatchDialogClosed() {
+ batchResult.value = null;
+ showBatchErrors.value = false;
+ batchUploading.value = false;
+}
+
+/**
+ * 文件上传成功后,用返回路径调用批量发货接口
+ */
+function onBatchFileUploadSuccess(res) {
+ if (res.status !== 200 || !res.data || !res.data.src) {
+ batchUploading.value = false;
+ ElMessage.error(res.msg || '文件上传失败');
+ return;
}
+ batchShipment({ file: res.data.src })
+ .then((result) => {
+ batchResult.value = result;
+ ElMessage.success(result.msg || '批量发货完成');
+ getList();
+ getStatistics();
+ })
+ .catch((err) => {
+ ElMessage.error((err && err.msg) || '批量发货失败');
+ })
+ .finally(() => {
+ batchUploading.value = false;
+ });
}
function onBatchUploadError(err) {
- ElMessage.error('批量发货失败:' + (err.msg || '文件上传异常'));
+ batchUploading.value = false;
+ let msg = '文件上传失败';
+ try {
+ const res = JSON.parse(err?.message || '');
+ if (res && res.msg) msg = res.msg;
+ } catch (e) {
+ // 非 JSON 响应,保留默认提示
+ }
+ ElMessage.error(msg);
}
function onBeforeUpload(file) {
- var suffix = file.name.split('.').pop().toLowerCase();
+ const suffix = (file.name.split('.').pop() || '').toLowerCase();
if (!['xls', 'xlsx'].includes(suffix)) {
- ElMessage.error('文件格式不正确,请上传xls或xlsx格式');
+ ElMessage.error('文件格式不正确,请上传 xls 或 xlsx 格式');
return false;
}
+ batchResult.value = null;
+ showBatchErrors.value = false;
+ batchUploading.value = true;
return true;
}
@@ -750,9 +818,12 @@ function handleExport() {
page: 1,
limit: 200,
...searchParams.value,
- status_code: statusTab.value === 'deleted' ? '' : statusTab.value,
is_deleted: statusTab.value === 'deleted' ? 1 : '',
};
+ // 与列表同口径:业务子状态走 business_status,勿把 pending_shipped 等塞进 status_code
+ if (statusTab.value && statusTab.value !== 'deleted') {
+ params.business_status = statusTab.value;
+ }
doExport(params);
})
.catch(() => {});
@@ -805,13 +876,6 @@ onMounted(() => {
margin-top: 10px;
}
- .batch-result {
- padding: 10px;
- background: #f5f7fa;
- border-radius: 4px;
- font-size: 14px;
- }
-
.logistics-box {
.logistics-header {
display: flex;
@@ -843,3 +907,136 @@ onMounted(() => {
}
}
+
+
+
diff --git a/frontend/admin/src/pages/plugin/normal/order/components/OrderSearch.vue b/frontend/admin/src/pages/plugin/normal/order/components/OrderSearch.vue
index 6c4365a3d..67517e14c 100644
--- a/frontend/admin/src/pages/plugin/normal/order/components/OrderSearch.vue
+++ b/frontend/admin/src/pages/plugin/normal/order/components/OrderSearch.vue
@@ -33,18 +33,17 @@
value-format="YYYY-MM-DD"
>
-
+
-