From 7071ec408e842698accaba90e10a16571f21046e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 17 Jul 2026 03:49:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(store):=20=E9=97=A8=E5=BA=97=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E9=A1=B5=E6=96=B0=E5=A2=9E=E5=8F=91=E8=B4=A7=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E5=8F=8A=E9=80=89=E6=8B=A9=E5=8F=91=E8=B4=A7=E4=BA=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 物流页发货信息依赖 shopOrderDelivery,之前门店确认完成不创建发货单导致显示为空 - 在门店订单页确认收款后增加发货按钮,允许选择门店店员作为发货人并创建发货单 - 新增接口 saveShopOrderDelivery 调用 POST /shop/shop-order-delivery 创建发货单 - 补全 shopStoreUser 模型字段,支持店员姓名、电话、角色等信息 - 订单列表支持发货操作按钮显示及样式,增加对应操作逻辑及UI弹窗选择发货人员 - 发货后订单状态置为已发货(deliveryStatus=20),同步更新发货时间和物流页显示 - 用户可选择门店全部店员(含经理)作为发货人员,确认完成按钮可与发货按钮共存,行为保持兼容 - 完成相关接口调用错误提示与加载状态处理,确保用户体验 - taro 构建通过,无类型错误 --- .workbuddy/memory/2026-07-17.md | 18 +++ src/api/shop/shopOrderDelivery/index.ts | 18 +++ src/api/shop/shopStoreUser/model/index.ts | 10 ++ src/pages/store/orders/index.tsx | 160 +++++++++++++++++++++- 4 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 src/api/shop/shopOrderDelivery/index.ts diff --git a/.workbuddy/memory/2026-07-17.md b/.workbuddy/memory/2026-07-17.md index 48294ad..998ad1a 100644 --- a/.workbuddy/memory/2026-07-17.md +++ b/.workbuddy/memory/2026-07-17.md @@ -74,5 +74,23 @@ - **字段确认**:用户选择「送达时间」用 `order.deliveryTime`,且**保留**现有「发货时间」行 → `deliveryTime` 在两行各显示一次(用户明知并接受) - **改动**:第408-413行 `{order.expirationTime && ...过期时间}` 改为 `{order.deliveryTime && ...送达时间}`,数据用 `formatTime(order.deliveryTime)` +## 门店订单页「发货」按钮 + 选发货人员写 shopOrderDelivery(解决物流页发货信息为空) +- **根因**:物流页 `pages/order/logistics.tsx` 的「发货信息」卡片依赖 `shopOrderDelivery` 发货单;门店「确认完成」只改订单状态、不建发货单 → `delivery` 为 null → 卡片不渲染(空白) +- **方案**:在「确认收款」之后加「发货」按钮,选门店店员作为发货人,写入 `shopOrderDelivery`,从源头让物流页有数据 +- **后端可行性(已确认)**:`POST /shop/shop-order-delivery`(save) 已存在,实体字段全可空最少传 orderId;`listShopStoreUser({storeId})` 已存在;请求层自动注入 `TenantId` 请求头(无需手动传租户);`ShopOrderDelivery` 实体有 orderId/deliveryMethod/sendName/sendPhone/sendAddress +- **前端改动**: + - 新增 `src/api/shop/shopOrderDelivery/index.ts`:`saveShopOrderDelivery(data)` 封装 `POST /shop/shop-order-delivery` + - 补全 `src/api/shop/shopStoreUser/model` 的 `ShopStoreUser` 类型缺的 `name`/`phone`/`roleType`/`storeName`/`image`(前端模型此前不完整,后端实体有) + - `src/pages/store/orders/index.tsx`: + - `OpType` 增加 `'ship'`;`OP_LABEL`/`OP_DESC` 补 ship + - `getOrderActions`:已付款 且 `deliveryStatus<20`(或未发货)且 `orderStatus!==1` 时显示「发货」(紫色 `bg-purple-500`) + - 新增状态 `showShipModal`/`clerkList`/`selectedClerkId`/`loadingClerks`/`shipping` + - 新增 `openShipModal(order)`(拉 `listShopStoreUser({storeId: order.storeId})`)与 `handleConfirmShip()`(先 `saveShopOrderDelivery({orderId, deliveryMethod:20, sendName:clerk.name, sendPhone:clerk.phone, sendAddress:order.storeName})`,再 `updateShopOrder({orderId, deliveryStatus:20, deliveryTime})`) + - 渲染「选择发货人员」底部弹层(店员列表可点选,确认发货按钮置灰直到选中) +- **设计决策(用户确认)**:① 发货即把订单置「已发货」(deliveryStatus=20);② 发货人可选门店全部店员(经理+店员) +- **物流页**:无需改动,`shopOrderDelivery` 存在即自动渲染发货人 +- **验证**:tsc 仅剩预存 `@tarojs/taro` 类型缺失环境报错,本文件无新错误 +- ⚠️ 行为说明:已付款未发货时「发货」与「确认完成」两个按钮同时显示;店员可跳过发货直接确认完成(该订单仍无发货单,物流页空白,与旧行为一致);如需强制「先发货才能确认完成」,可隐藏未发货订单的「确认完成」按钮(一行判断改动),待用户决定 + ### 验证 - `npx taro build --type weapp` 构建成功 diff --git a/src/api/shop/shopOrderDelivery/index.ts b/src/api/shop/shopOrderDelivery/index.ts new file mode 100644 index 0000000..64e78de --- /dev/null +++ b/src/api/shop/shopOrderDelivery/index.ts @@ -0,0 +1,18 @@ +import request from '@/utils/request' +import type { ApiResult } from '@/api' +import type { ShopOrderDelivery } from '@/api/shop/shopOrder/model' + +/** + * 创建发货单(门店发货:记录发货人员) + * 后端 POST /shop/shop-order-delivery(save) + */ +export async function saveShopOrderDelivery(data: ShopOrderDelivery) { + const res = await request.post>( + '/shop/shop-order-delivery', + data + ) + if (res.code === 0) { + return res.message + } + return Promise.reject(new Error(res.message)) +} diff --git a/src/api/shop/shopStoreUser/model/index.ts b/src/api/shop/shopStoreUser/model/index.ts index 46151f5..4cb65e4 100644 --- a/src/api/shop/shopStoreUser/model/index.ts +++ b/src/api/shop/shopStoreUser/model/index.ts @@ -10,6 +10,16 @@ export interface ShopStoreUser { storeId?: number; // 用户ID userId?: number; + // 姓名 + name?: string; + // 手机号 + phone?: string; + // 头像 + image?: string; + // 角色类型: 1-门店经理, 2-店员 + roleType?: number; + // 门店名称(关联字段) + storeName?: string; // 备注 comments?: string; // 排序号 diff --git a/src/pages/store/orders/index.tsx b/src/pages/store/orders/index.tsx index 7280cac..9a54bae 100644 --- a/src/pages/store/orders/index.tsx +++ b/src/pages/store/orders/index.tsx @@ -3,10 +3,12 @@ import {View, Text, Image, ScrollView, Input, Textarea} from '@tarojs/components import Taro, {useDidShow} from '@tarojs/taro' import {pageShopOrder, updateShopOrder, confirmOfflinePayment} from '@/api/shop/shopOrder' import type {ShopOrder, ShopOrderParam} from '@/api/shop/shopOrder/model' +import {saveShopOrderDelivery} from '@/api/shop/shopOrderDelivery' import {TenantId} from '@/config/app' import {useNewOrderDetector} from '@/hooks/useNewOrderDetector' import { getCompressedImageUrl } from '@/utils/image' -import { getMyClerk } from '@/api/shop/shopStoreUser' +import { getMyClerk, listShopStoreUser } from '@/api/shop/shopStoreUser' +import type {ShopStoreUser} from '@/api/shop/shopStoreUser/model' import { ensurePrivacyAuthorized } from '@/api/system/file' definePageConfig({ @@ -26,18 +28,20 @@ const TABS: { key: TabKey; label: string; params: Partial[] }[] ] // ─── 操作类型 ───────────────────────────────────────────────────── -type OpType = 'confirmPay' | 'complete' | 'editPrice' +type OpType = 'confirmPay' | 'complete' | 'editPrice' | 'ship' const OP_LABEL: Record = { confirmPay: '收款', complete: '已完成', editPrice: '金额', + ship: '发货', } const OP_DESC: Record = { confirmPay: '确认已收到线下付款,订单将进入待发货状态', complete: '同时变更支付状态为已付款、收货状态为已收货、订单状态为已完成', editPrice: '修改订单实付金额', + ship: '选择发货人员并生成发货单,订单将进入已发货状态', } // ─── 图片上传 ───────────────────────────────────────────────────── @@ -135,6 +139,10 @@ function getOrderActions(order: ShopOrder): { label: string; type: OpType }[] { if (order.payStatus) { actions.push({label: '确认完成', type: 'complete'}) } + // 发货:确认收款后、且尚未发货(deliveryStatus<20)时显示 + if (order.payStatus && (order.deliveryStatus == null || order.deliveryStatus < 20) && order.orderStatus !== 1) { + actions.push({label: '发货', type: 'ship'}) + } return actions } @@ -161,6 +169,13 @@ export default function StoreOrdersPage() { const [editPayPrice, setEditPayPrice] = useState('') const [editReason, setEditReason] = useState('') + // 发货弹窗状态 + const [showShipModal, setShowShipModal] = useState(false) + const [clerkList, setClerkList] = useState([]) + const [selectedClerkId, setSelectedClerkId] = useState(null) + const [loadingClerks, setLoadingClerks] = useState(false) + const [shipping, setShipping] = useState(false) + // 搜索:searchInput 受控输入框,searchKeyword 为已提交的搜索词 const [searchInput, setSearchInput] = useState('') const [searchKeyword, setSearchKeyword] = useState('') @@ -439,6 +454,58 @@ export default function StoreOrdersPage() { } } + /** 打开发货弹窗:拉取门店店员列表供选择发货人员 */ + const openShipModal = async (order: ShopOrder) => { + setCurrentOrder(order) + setSelectedClerkId(null) + setClerkList([]) + setShowShipModal(true) + setLoadingClerks(true) + try { + const res = await listShopStoreUser({storeId: order.storeId}) + setClerkList(res || []) + } catch (e) { + Taro.showToast({title: '加载店员失败', icon: 'none'}) + } finally { + setLoadingClerks(false) + } + } + + /** 确认发货:创建发货单并记录发货人员,订单置为已发货 */ + const handleConfirmShip = async () => { + if (!currentOrder) return + const clerk = clerkList.find(c => c.id === selectedClerkId) + if (!clerk) { + Taro.showToast({title: '请选择发货人员', icon: 'none'}) + return + } + setShipping(true) + try { + // 1) 创建发货单,记录发货人信息 + await saveShopOrderDelivery({ + orderId: currentOrder.orderId, + deliveryMethod: 20, // 20=无需物流(门店自送/自提,无快递单号) + sendName: clerk.name, + sendPhone: clerk.phone, + // 发货地址取门店名称(店员实体无地址字段) + sendAddress: currentOrder.storeName || '', + }) + // 2) 订单置为已发货(deliveryStatus=20),物流页与列表状态同步更新 + await updateShopOrder({ + orderId: currentOrder.orderId, + deliveryStatus: 20, + deliveryTime: formatDateTime(new Date()), + }) + Taro.showToast({title: '发货成功', icon: 'success'}) + setShowShipModal(false) + loadOrders(activeTab, 1) // 刷新列表(发货按钮随之隐藏) + } catch (e: any) { + Taro.showToast({title: e.message || '发货失败', icon: 'none'}) + } finally { + setShipping(false) + } + } + /** 关闭订单 */ const handleCloseOrder = (order: ShopOrder) => { Taro.showModal({ @@ -600,12 +667,18 @@ export default function StoreOrdersPage() { ? 'bg-blue-500' : act.type === 'editPrice' ? 'bg-orange-500' + : act.type === 'ship' + ? 'bg-purple-500' : 'border border-blue-500' }`} - onClick={() => act.type === 'editPrice' ? openEditPriceModal(order) : openModal(order, act.type)} + onClick={() => act.type === 'editPrice' + ? openEditPriceModal(order) + : act.type === 'ship' + ? openShipModal(order) + : openModal(order, act.type)} > {act.label} ))} @@ -867,6 +940,85 @@ export default function StoreOrdersPage() { )} + + {/* 发货弹窗:选择发货人员 */} + {showShipModal && currentOrder && ( + + setShowShipModal(false)}/> + + + 选择发货人员 + + + {/* 订单信息 */} + + 订单号:{currentOrder.orderNo} + + 实付金额:¥{currentOrder.payPrice || currentOrder.totalPrice} + + + + {/* 店员列表 */} + + {loadingClerks ? ( + + 加载中... + + ) : clerkList.length === 0 ? ( + + 暂无可选择的店员 + + ) : ( + clerkList.map(clerk => { + const selected = clerk.id === selectedClerkId + return ( + setSelectedClerkId(clerk.id ?? null)} + > + + + {clerk.name} + + {clerk.roleType === 1 ? '经理' : '店员'} + + + {clerk.phone} + + {selected && ( + + )} + + ) + }) + )} + + + {/* 操作按钮 */} + + setShowShipModal(false)}> + 取消 + + + {shipping ? ( + 发货中... + ) : ( + 确认发货 + )} + + + + + )} ) }