feat(shopOrder): 增加发货门店与配送店员选择功能
- 在发货类型非“无需发货”时新增发货门店下拉选择,支持加载门店列表 - 选择门店后自动回填发货地址及快递配送发货人信息 - 商家送货时新增配送店员下拉筛选,支持根据门店联动加载配送人员 - 配送店员选择后自动回填发货人姓名和电话 - 表单校验新增发货门店必填及配送店员在商家送货时必填规则 - 更新提交数据结构,包含发货门店ID与配送店员ID字段 - 订单详情页增加发货门店及配送店员展示字段 - 调整配送方式单选按钮顺序,快递配送与商家送货前置,无需发货后置
This commit is contained in:
24
.workbuddy/memory/2026-07-17.md
Normal file
24
.workbuddy/memory/2026-07-17.md
Normal 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 值不变。
|
||||||
@@ -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;
|
||||||
// 优惠说明
|
// 优惠说明
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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' }"
|
||||||
|
|||||||
Reference in New Issue
Block a user