feat:优化了订单、药品这些交互

This commit is contained in:
李琦
2026-07-21 07:36:52 +08:00
parent 7656c3ac49
commit ee4490a91c
23 changed files with 692 additions and 109 deletions

View File

@@ -16,13 +16,6 @@ export async function getDeliveryWarehouseOrderDetail(id: number | string) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 按订单查物流轨迹(复用平台 express-detail 接口)
*/
export async function expressDetailByOrderId(data: Record<string, any>) {
return requestClient.post<any>(`express-detail/detail-by-order`, data);
}
/**
* 配送仓库订单发货
*/

View File

@@ -5,17 +5,13 @@ import { useVbenModal } from '@vben/common-ui';
import { Button, Descriptions, Image, Tabs, Tag, Timeline } from 'ant-design-vue';
import {
expressDetailByOrderId,
getDeliveryWarehouseOrderDetail,
} from '#/views/system/delivery-warehouse-order/api';
import { getDeliveryWarehouseOrderDetail } from '#/views/system/delivery-warehouse-order/api';
defineOptions({
name: 'DeliveryWarehouseOrderDetailModal',
});
const data = ref<Record<string, any>>({});
const expressDetail = ref<Record<string, any>>({});
const scrollToLogistics = ref(false);
const logisticsAnchorRef = ref<HTMLElement | null>(null);
/** 待发货时由列表传入,点击后关详情并打开发货弹窗 */
@@ -29,13 +25,9 @@ const canShip = computed(() => {
Number(data.value.status) === 1;
return (mineUnsent || legacy) && typeof onShipFn.value === 'function';
});
/** 仅使用仓侧详情返回的本仓 packages,避免平台物流接口带出他仓商品 */
const packageTabs = computed(() => {
const packages = Array.isArray(data.value.packages)
? data.value.packages
: Array.isArray(expressDetail.value.packages)
? expressDetail.value.packages
: [];
return packages;
return Array.isArray(data.value.packages) ? data.value.packages : [];
});
const activePkg = ref('0');
@@ -58,27 +50,17 @@ const deliveryMethodMap: Record<number, string> = {
};
/**
* 拉取订单详情;已发货时继续拉物流轨迹
* 拉取仓侧订单详情(含本仓药品 + 本仓包裹物流)
*/
async function loadDetail(id: number | string) {
data.value = (await getDeliveryWarehouseOrderDetail(id)) || {};
expressDetail.value = {};
const sent =
Number(data.value.is_send) === 1 ||
Number(data.value.status) >= 2 ||
!!data.value.express_no_id;
if (sent && data.value.id) {
try {
expressDetail.value = await expressDetailByOrderId({
order_id: data.value.id,
});
} catch {
expressDetail.value = {};
}
}
activePkg.value = '0';
if (scrollToLogistics.value) {
await nextTick();
logisticsAnchorRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start' });
logisticsAnchorRef.value?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}
@@ -119,7 +101,6 @@ const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (!isOpen) {
data.value = {};
expressDetail.value = {};
scrollToLogistics.value = false;
onShipFn.value = null;
return;
@@ -233,7 +214,11 @@ const [Modal, modalApi] = useVbenModal({
<section ref="logisticsAnchorRef">
<h3 class="section-title">物流信息</h3>
<Tabs v-if="packageTabs.length" v-model:active-key="activePkg" type="card">
<Tabs
v-if="packageTabs.length"
v-model:active-key="activePkg"
type="card"
>
<Tabs.TabPane
v-for="(pkg, idx) in packageTabs"
:key="String(idx)"
@@ -256,7 +241,9 @@ const [Modal, modalApi] = useVbenModal({
</Descriptions.Item>
</Descriptions>
<div
v-if="Array.isArray(pkg.express.detail) && pkg.express.detail.length"
v-if="
Array.isArray(pkg.express.detail) && pkg.express.detail.length
"
class="mt-4"
>
<Timeline>
@@ -264,7 +251,9 @@ const [Modal, modalApi] = useVbenModal({
v-for="(detail, dIdx) in pkg.express.detail"
:key="dIdx"
>
<Tag :color="getColor(detail.status)">{{ detail.status }}</Tag>
<Tag :color="getColor(detail.status)">{{
detail.status
}}</Tag>
<p>{{ detail.detail_at }}</p>
<p>{{ detail.detail }}</p>
</Timeline.Item>
@@ -279,8 +268,7 @@ const [Modal, modalApi] = useVbenModal({
v-else-if="
Number(data.is_send) === 1 ||
Number(data.status) >= 2 ||
expressNoRow().express_no ||
expressDetail.express_no
expressNoRow().express_no
"
>
<Descriptions
@@ -289,33 +277,12 @@ const [Modal, modalApi] = useVbenModal({
size="small"
>
<Descriptions.Item label="物流公司">
{{
expressDetail.express_company_name ||
expressNoRow().express_company_name ||
'-'
}}
{{ expressNoRow().express_company_name || '-' }}
</Descriptions.Item>
<Descriptions.Item label="运单号">
{{
expressDetail.express_no || expressNoRow().express_no || '-'
}}
</Descriptions.Item>
<Descriptions.Item label="最新状态">
{{ expressDetail.state_txt || '-' }}
{{ expressNoRow().express_no || '-' }}
</Descriptions.Item>
</Descriptions>
<div v-if="Array.isArray(expressDetail.detail) && expressDetail.detail.length" class="mt-4">
<Timeline>
<Timeline.Item
v-for="(detail, index) in expressDetail.detail"
:key="index"
>
<Tag :color="getColor(detail.status)">{{ detail.status }}</Tag>
<p>{{ detail.detail_at }}</p>
<p>{{ detail.detail }}</p>
</Timeline.Item>
</Timeline>
</div>
</template>
<div v-else class="empty-tip">暂无物流</div>
</section>

View File

@@ -48,9 +48,11 @@ const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
const { values, update } = modalApi.getData<Record<string, any>>();
const { values, update } = modalApi.getData<Record<string, any>>() || {};
isUpdate.value = !!update;
// 新建时清空表单,避免残留编辑态的 id/证照
formApi.resetForm();
if (values) {
isUpdate.value = update;
formApi.setValues(values);
}
}
@@ -60,7 +62,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}配送仓库`"
class="w-[40%]"
class="w-[720px]"
>
<Form />
</Modal>

View File

@@ -1,8 +1,10 @@
import type { VbenFormProps } from '#/adapter/form';
/**
* 配送仓库新建/编辑:仅基本信息
* 合同与银行卡走独立报备入口,不在此表单填写
* 配送仓库新建/编辑
* - 三证必填,其余证照/合同选填
* - 联系人/手机号仅新建时展示,用于开通仓管理员
* - 银行卡仍走独立报备入口
*/
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12',
@@ -41,6 +43,87 @@ export const modalFormProps: VbenFormProps = {
fieldName: 'introduce',
label: '仓库介绍',
},
{
component: 'VbenInput',
componentProps: {
placeholder: '联系人姓名(将作为仓管理员昵称)',
},
fieldName: 'contact_name',
label: '联系人',
rules: 'required',
help: '新建时用于开通配送仓库管理员账号',
dependencies: {
// 仅新建展示;编辑改账号请走「配送仓管理员」菜单
if({ id }: { id?: number | string }) {
return !id;
},
triggerFields: ['id'],
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: '手机号(仓管理员登录手机)',
},
fieldName: 'phone',
label: '手机号',
rules: 'required',
help: '默认初始密码 Xk123456@',
dependencies: {
if({ id }: { id?: number | string }) {
return !id;
},
triggerFields: ['id'],
},
},
{
component: 'Avatar',
fieldName: 'business_license_image',
label: '营业执照',
rules: 'required',
formItemClass: 'col-span-4',
},
{
component: 'Avatar',
fieldName: 'drug_license_image',
label: '药品经营许可证',
rules: 'required',
formItemClass: 'col-span-4',
},
{
component: 'Avatar',
fieldName: 'medical_device_license_image',
label: '医疗器械经营许可证',
rules: 'required',
formItemClass: 'col-span-4',
},
{
component: 'Avatar',
fieldName: 'food_license_image',
label: '食品经营许可证',
formItemClass: 'col-span-4',
},
{
component: 'Avatar',
fieldName: 'pharmacist_certificate_image',
label: '药师资格证',
formItemClass: 'col-span-4',
},
{
component: 'Avatar',
fieldName: 'internet_drug_license_image',
label: '互联网药品经营许可证',
formItemClass: 'col-span-4',
},
{
component: 'UploadOssFile',
componentProps: {
maxCount: 1,
},
fieldName: 'contract_attachment',
label: '合同附件',
formItemClass: 'col-span-12',
},
{
component: 'RadioGroup',
componentProps: {

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
/**
* 平台配送仓库列表
* 新建/编辑仅基本信息;银行卡报备对齐诊所,走独立弹窗
* 新建/编辑含证照与建仓开号;银行卡报备对齐诊所,走独立弹窗
*/
import type { VxeGridListeners } from '#/adapter/vxe-table';