feat(shop): 配送员管理功能新增门店选择功能

- 在ShopStoreRider模型中增加storeId和storeName字段
- 为配送员编辑表单添加门店选择组件和验证规则
- 实现门店选择的Modal组件和数据表格展示
- 添加门店搜索和筛选功能
- 优化表单回显逻辑支持门店名称显示
- 更新表单提交时的门店ID关联逻辑
This commit is contained in:
2026-02-10 15:15:07 +08:00
parent 353916a081
commit 74068508c0
4 changed files with 229 additions and 0 deletions

View File

@@ -6,6 +6,10 @@ import type { PageParam } from '@/api';
export interface ShopStoreRider {
// 主键ID
id?: string;
// 门店IDshop_store.id
storeId?: number;
// 门店名称(后端联表返回,提交时可不传)
storeName?: string;
// 配送点IDshop_dealer.id
dealerId?: number;
// 骑手编号(可选)

View File

@@ -0,0 +1,106 @@
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:title="title"
:footer="null"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
>
<ele-pro-table
ref="tableRef"
row-key="id"
:datasource="datasource"
:columns="columns"
:pagination="false"
>
<template #toolbar>
<a-space>
<a-input-search
allow-clear
v-model:value="where.keywords"
placeholder="请输入门店关键词"
style="width: 240px"
@search="reload"
@pressEnter="reload"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-button type="primary" @click="done(record)">选择</a-button>
</a-space>
</template>
</template>
</ele-pro-table>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import type {
ColumnItem,
DatasourceFunction
} from 'ele-admin-pro/es/ele-pro-table/types';
import { EleProTable } from 'ele-admin-pro';
import useSearch from '@/utils/use-search';
import { pageShopStore } from '@/api/shop/shopStore';
import type { ShopStoreParam } from '@/api/shop/shopStore/model';
import type { ShopStore } from '@/api/shop/shopStore/model';
defineProps<{
visible: boolean;
title?: string;
}>();
const emit = defineEmits<{
(e: 'done', data: ShopStore): void;
(e: 'update:visible', visible: boolean): void;
}>();
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const { where } = useSearch<ShopStoreParam>({
id: undefined,
keywords: ''
});
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
const columns = ref<ColumnItem[]>([
{ title: 'ID', dataIndex: 'id', width: 90 },
{ title: '门店名称', dataIndex: 'name' },
{ title: '电话', dataIndex: 'phone', width: 160 },
{ title: '地址', dataIndex: 'address' },
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
hideInSetting: true
}
]);
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
return pageShopStore({
...where,
...orders,
page,
limit
});
};
const reload = () => {
tableRef?.value?.reload({ page: 1, where: where as ShopStoreParam });
};
const done = (record: ShopStore) => {
updateVisible(false);
emit('done', record);
};
</script>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="content"
:placeholder="placeholder"
/>
<a-button @click="openEdit">
<template #icon><BulbOutlined class="ele-text-warning" /></template>
</a-button>
</a-input-group>
<!-- 选择弹窗 -->
<SelectData
v-model:visible="showEdit"
:title="placeholder"
@done="onChange"
/>
</div>
</template>
<script lang="ts" setup>
import { BulbOutlined } from '@ant-design/icons-vue';
import { ref, watch } from 'vue';
import SelectData from './components/select-data.vue';
import type { ShopStore } from '@/api/shop/shopStore/model';
const props = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
}>(),
{
placeholder: '选择门店'
}
);
const emit = defineEmits<{
(e: 'done', data?: ShopStore): void;
(e: 'clear'): void;
}>();
const showEdit = ref(false);
const content = ref<string>(props.value ?? '');
const openEdit = () => {
showEdit.value = true;
};
const onChange = (row?: ShopStore) => {
showEdit.value = false;
emit('done', row);
};
watch(
() => props.value,
(v) => {
content.value = v ?? '';
}
);
</script>

View File

@@ -5,6 +5,7 @@
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:confirm-loading="loading"
:title="isUpdate ? '编辑配送员' : '添加配送员'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@@ -28,6 +29,14 @@
@done="onChooseUser"
/>
</a-form-item>
<a-form-item label="所属门店" name="storeId">
<SelectShopStore
:key="String(form.storeId ?? '') + selectedStoreText"
:value="selectedStoreText"
:placeholder="`选择门店`"
@done="onChooseStore"
/>
</a-form-item>
<a-form-item label="配送点(小区)" name="dealerId">
<SelectCommunity
:key="String(form.dealerId ?? '') + selectedCommunityText"
@@ -165,6 +174,10 @@
import { ShopStoreRider } from '@/api/shop/shopStoreRider/model';
import { getUser } from '@/api/system/user';
import { getShopCommunity } from '@/api/shop/shopCommunity';
import { getShopStore } from '@/api/shop/shopStore';
import SelectUser from '@/components/SelectUser/index.vue';
import SelectCommunity from '@/components/SelectCommunity/index.vue';
import SelectShopStore from '@/components/SelectShopStore/index.vue';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
@@ -172,6 +185,7 @@
import { FileRecord } from '@/api/system/file/model';
import type { User } from '@/api/system/user/model';
import type { ShopCommunity } from '@/api/shop/shopCommunity/model';
import type { ShopStore } from '@/api/shop/shopStore/model';
// 是否是修改
const isUpdate = ref(false);
@@ -201,10 +215,12 @@
const images = ref<ItemType[]>([]);
const selectedUserText = ref('');
const selectedCommunityText = ref('');
const selectedStoreText = ref('');
// 用户信息
const form = reactive<ShopStoreRider>({
id: undefined,
storeId: undefined,
dealerId: undefined,
riderNo: undefined,
realName: undefined,
@@ -236,6 +252,17 @@
// 表单验证规则
const rules = reactive({
storeId: [
{
validator: (_rule: unknown, value: number | undefined) => {
if (!value) {
return Promise.reject(new Error('请选择门店'));
}
return Promise.resolve();
},
trigger: 'change'
}
]
// userId: [
// {
// required: true,
@@ -317,6 +344,16 @@
selectedCommunityText.value = community.name ?? String(community.id ?? '');
};
const onChooseStore = (store?: ShopStore) => {
if (!store) {
selectedStoreText.value = '';
form.storeId = undefined;
return;
}
form.storeId = store.id;
selectedStoreText.value = store.name ?? String(store.id ?? '');
};
const { resetFields } = useForm(form, rules);
/* 保存编辑 */
@@ -356,6 +393,7 @@
images.value = [];
selectedUserText.value = '';
selectedCommunityText.value = '';
selectedStoreText.value = '';
if (props.data) {
assignObject(form, props.data);
if (props.data.avatar) {
@@ -382,6 +420,24 @@
selectedUserText.value = String(form.userId ?? '');
});
}
if (form.storeId) {
// 优先使用列表接口返回的 storeName 回显,避免额外请求
if ((props.data as any)?.storeName) {
selectedStoreText.value = String((props.data as any).storeName);
} else {
const sid = form.storeId;
getShopStore(form.storeId)
.then((store) => {
if (form.storeId !== sid) return;
selectedStoreText.value =
store.name ?? String(store.id ?? '');
})
.catch(() => {
if (form.storeId !== sid) return;
selectedStoreText.value = String(form.storeId ?? '');
});
}
}
if (form.dealerId) {
const dealerId = form.dealerId;
getShopCommunity(form.dealerId)