feat(shopOrder): 增加发货门店与配送店员选择功能

- 在发货类型非“无需发货”时新增发货门店下拉选择,支持加载门店列表
- 选择门店后自动回填发货地址及快递配送发货人信息
- 商家送货时新增配送店员下拉筛选,支持根据门店联动加载配送人员
- 配送店员选择后自动回填发货人姓名和电话
- 表单校验新增发货门店必填及配送店员在商家送货时必填规则
- 更新提交数据结构,包含发货门店ID与配送店员ID字段
- 订单详情页增加发货门店及配送店员展示字段
- 调整配送方式单选按钮顺序,快递配送与商家送货前置,无需发货后置
This commit is contained in:
2026-07-17 04:11:45 +08:00
parent 6b57dd2fed
commit e9249c910f
4 changed files with 186 additions and 2 deletions

View File

@@ -0,0 +1,24 @@
# 2026-07-17 工作日志
## 订单发货功能改进shopOrder / deliveryModal
分析了订单发货弹窗(`src/views/shop/shopOrder/components/deliveryModal.vue`)三种配送方式,按用户确认的改进方案实施了前端改动:
### 改动文件
1. `deliveryModal.vue`(核心)
- 新增「发货门店」下拉(复用 `listShopStore`),选中后自动回填:快递配送→发货人=门店名/电话=门店电话/地址=门店地址;商家送货→地址=门店地址。
- 商家送货(`deliveryType===2`)新增「配送店员」二级联动下拉(复用 `listShopStoreUser({storeId})`),选中店员自动回填发货人=店员名/联系方式=店员电话。
- 校验:发货门店必填(`deliveryType!==1`);配送店员必填(仅 `deliveryType===2`)。
- 提交时附带 `sendStoreId` / `deliveryUserId`
2. `api/shop/shopOrder/model/index.ts`:新增 `sendStoreId``deliveryUserId` 字段(为后端落库铺路)。
3. `orderInfo.vue`:发货信息卡片增加「发货门店」「配送店员」展示(可选链,后端返回 `storeName`/`deliveryUserName` 后自动生效,否则显示「未填写」)。
### 关键结论 / 待后端配合
- 门店、店员 list API 已存在,前端改动零后端依赖即可落地交互。
- **后端缺口(需补充)**`shop_order` 表需新增 `send_store_id``delivery_user_id` 字段存储门店/店员关联;否则下拉选了也存不进去。
- 登录用户 `User` 模型无 `storeId`,无法默认锁定当前门店,门店下拉需用户手动选择。
- 原「发货地址模板」需求因无现成后端接口,改用「门店作为发货地址模板」方案满足(复用门店 name/phone/address
## 配送方式排序调整
- 修改 `deliveryModal.vue` 配送方式 radio 按钮顺序:快递配送→商家送货→无需发货(原:快递配送→无需发货→商家送货),仅调换模板中 radio 排列顺序value 值不变。

View File

@@ -111,6 +111,14 @@ export interface ShopOrder {
sendPhone?: string; sendPhone?: string;
// 发货地址 // 发货地址
sendAddress?: string; sendAddress?: string;
// 发货门店id快递配送=发货仓库,商家送货=配送门店)
sendStoreId?: number;
// 配送店员id商家送货时记录具体配送人员
deliveryUserId?: number;
// 发货门店名称(后端联表返回)
sendStoreName?: string;
// 配送店员名称(后端联表返回)
deliveryUserName?: string;
// 优惠类型0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡 // 优惠类型0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡
couponType?: number; couponType?: number;
// 优惠说明 // 优惠说明

View File

@@ -22,8 +22,8 @@
<a-radio :value="0"> <a-radio :value="0">
<span style="color: #1890ff">快递配送</span> <span style="color: #1890ff">快递配送</span>
</a-radio> </a-radio>
<a-radio :value="1">无需发货</a-radio>
<a-radio :value="2">商家送货</a-radio> <a-radio :value="2">商家送货</a-radio>
<a-radio :value="1">无需发货</a-radio>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
@@ -87,6 +87,49 @@
</a-form-item> </a-form-item>
<template v-if="form.deliveryType !== 1"> <template v-if="form.deliveryType !== 1">
<!-- 发货门店快递配送=发货仓库商家送货=配送门店 -->
<a-form-item label="发货门店" name="sendStoreId">
<a-select
v-model:value="form.sendStoreId"
placeholder="请选择门店"
@change="onStoreChange"
>
<a-select-option
v-for="store in storeList"
:key="store.id"
:value="store.id"
>
{{ store.name }}
</a-select-option>
</a-select>
</a-form-item>
<!-- 商家送货配送店员依赖门店二级联动 -->
<a-form-item
label="配送店员"
name="deliveryUserId"
v-if="form.deliveryType === 2"
>
<a-select
v-model:value="form.deliveryUserId"
placeholder="请先选择门店"
:disabled="!form.sendStoreId"
@change="onUserChange"
>
<a-select-option
v-for="user in userList"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</a-select-option>
</a-select>
</a-form-item>
<div style="color: #999; font-size: 12px; margin: 4px 0 12px">
选择门店后将自动回填下方发货人信息可手动修改
</div>
<a-form-item label="发货人" name="sendName"> <a-form-item label="发货人" name="sendName">
<a-input <a-input
v-model:value="form.sendName" v-model:value="form.sendName"
@@ -156,6 +199,10 @@
import { updateShopOrder } from '@/api/shop/shopOrder'; import { updateShopOrder } from '@/api/shop/shopOrder';
import { listShopExpress } from '@/api/shop/shopExpress'; import { listShopExpress } from '@/api/shop/shopExpress';
import { ShopExpress } from '@/api/shop/shopExpress/model'; import { ShopExpress } from '@/api/shop/shopExpress/model';
import { listShopStore } from '@/api/shop/shopStore';
import { ShopStore } from '@/api/shop/shopStore/model';
import { listShopStoreUser } from '@/api/shop/shopStoreUser';
import { ShopStoreUser } from '@/api/shop/shopStoreUser/model';
import { toDateString } from 'ele-admin-pro'; import { toDateString } from 'ele-admin-pro';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import ExpressSettingModal from './expressSettingModal.vue'; import ExpressSettingModal from './expressSettingModal.vue';
@@ -184,6 +231,8 @@
sendName: '', sendName: '',
sendPhone: '', sendPhone: '',
sendAddress: '', sendAddress: '',
sendStoreId: undefined as number | undefined, // 发货门店/配送门店
deliveryUserId: undefined as number | undefined, // 配送店员
deliveryTime: dayjs() as Dayjs deliveryTime: dayjs() as Dayjs
}); });
@@ -250,6 +299,26 @@
return Promise.resolve(); return Promise.resolve();
} }
} }
],
sendStoreId: [
{
validator: (_: any, value: any) => {
if (form.deliveryType !== 1 && !value) {
return Promise.reject('请选择发货门店');
}
return Promise.resolve();
}
}
],
deliveryUserId: [
{
validator: (_: any, value: any) => {
if (form.deliveryType === 2 && !value) {
return Promise.reject('请选择配送店员');
}
return Promise.resolve();
}
}
] ]
}; };
@@ -259,6 +328,8 @@
// 状态 // 状态
const loading = ref(false); const loading = ref(false);
const expressList = ref<ShopExpress[]>([]); const expressList = ref<ShopExpress[]>([]);
const storeList = ref<ShopStore[]>([]);
const userList = ref<ShopStoreUser[]>([]);
const expressModalVisible = ref(false); const expressModalVisible = ref(false);
// 加载快递公司列表 // 加载快递公司列表
@@ -271,6 +342,58 @@
} }
}; };
// 加载门店列表
const loadStoreList = async () => {
try {
const data = await listShopStore({});
storeList.value = data || [];
} catch (error) {
console.error('加载门店失败:', error);
}
};
// 加载指定门店下的店员
const loadUserList = async (storeId?: number) => {
if (!storeId) {
userList.value = [];
return;
}
try {
const data = await listShopStoreUser({ storeId });
userList.value = data || [];
} catch (error) {
console.error('加载店员失败:', error);
}
};
// 门店选择变化
const onStoreChange = (storeId: number) => {
const store = storeList.value.find((item) => item.id === storeId);
if (store) {
// 发货地址统一回填写门店地址
form.sendAddress = store.address || '';
if (form.deliveryType === 0) {
// 快递配送:门店作为发货方,直接回填发货人与联系方式
form.sendName = store.name || '';
form.sendPhone = store.phone || '';
}
if (form.deliveryType === 2) {
// 商家送货:切换门店后清空已选店员并重新加载
form.deliveryUserId = undefined;
loadUserList(storeId);
}
}
};
// 店员选择变化
const onUserChange = (userId: number) => {
const user = userList.value.find((item) => item.id === userId);
if (user) {
form.sendName = user.name || '';
form.sendPhone = user.phone || '';
}
};
// 快递公司筛选 // 快递公司筛选
const filterExpressOption = (input: string, option: any) => { const filterExpressOption = (input: string, option: any) => {
return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0; return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0;
@@ -303,6 +426,9 @@
form.sendName = ''; form.sendName = '';
form.sendPhone = ''; form.sendPhone = '';
form.sendAddress = ''; form.sendAddress = '';
form.sendStoreId = undefined;
form.deliveryUserId = undefined;
userList.value = [];
} }
} }
); );
@@ -345,6 +471,8 @@
updateData.sendName = form.sendName; updateData.sendName = form.sendName;
updateData.sendPhone = form.sendPhone; updateData.sendPhone = form.sendPhone;
updateData.sendAddress = form.sendAddress; updateData.sendAddress = form.sendAddress;
updateData.sendStoreId = form.sendStoreId;
updateData.deliveryUserId = undefined;
updateData.expressName = form.expressName || express?.expressName; updateData.expressName = form.expressName || express?.expressName;
updateData.expressNo = form.expressNo; updateData.expressNo = form.expressNo;
} else if (form.deliveryType === 2) { } else if (form.deliveryType === 2) {
@@ -352,6 +480,8 @@
updateData.sendName = form.sendName; updateData.sendName = form.sendName;
updateData.sendPhone = form.sendPhone; updateData.sendPhone = form.sendPhone;
updateData.sendAddress = form.sendAddress; updateData.sendAddress = form.sendAddress;
updateData.sendStoreId = form.sendStoreId;
updateData.deliveryUserId = form.deliveryUserId;
updateData.expressId = undefined; updateData.expressId = undefined;
updateData.expressName = undefined; updateData.expressName = undefined;
updateData.expressNo = undefined; updateData.expressNo = undefined;
@@ -399,10 +529,14 @@
form.sendName = ''; form.sendName = '';
form.sendPhone = ''; form.sendPhone = '';
form.sendAddress = ''; form.sendAddress = '';
form.sendStoreId = undefined;
form.deliveryUserId = undefined;
userList.value = [];
form.deliveryTime = dayjs(); form.deliveryTime = dayjs();
// 加载快递公司列表 // 加载快递公司列表与门店列表
loadExpressList(); loadExpressList();
loadStoreList();
} else { } else {
resetFields(); resetFields();
} }

View File

@@ -362,6 +362,24 @@
> >
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<a-descriptions :column="2"> <a-descriptions :column="2">
<a-descriptions-item
label="发货门店"
v-if="form.deliveryType === 0 || form.deliveryType === 2"
:labelStyle="{ width: '90px', color: '#808080' }"
>
{{
form.sendStoreName ||
form.expressMerchantName ||
'未填写'
}}
</a-descriptions-item>
<a-descriptions-item
label="配送店员"
v-if="form.deliveryType === 2"
:labelStyle="{ width: '90px', color: '#808080' }"
>
{{ form.deliveryUserName || '未填写' }}
</a-descriptions-item>
<a-descriptions-item <a-descriptions-item
label="发货人" label="发货人"
:labelStyle="{ width: '90px', color: '#808080' }" :labelStyle="{ width: '90px', color: '#808080' }"