feat:仓库、库区、仓位基础功能

This commit is contained in:
zhouz
2026-07-20 15:01:50 +08:00
parent d60f33022c
commit 949609246e
46 changed files with 3179 additions and 40 deletions

View File

@@ -80,3 +80,10 @@ export function deleteBsrealStorAttrList(ids: string[]) {
export function exportBsrealStorAttr(params: any) {
return requestClient.download('/wms/bsreal-stor-attr/export-excel', { params });
}
/** 获得启用的实物库属性精简列表 */
export function getBsrealStorAttrSimpleList() {
return requestClient.get<{ storId: string; storName: string }[]>(
'/wms/bsreal-stor-attr/simple-list',
);
}

View File

@@ -0,0 +1,83 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace WmsSectAttrApi {
/** 库区属性信息 */
export interface SectAttr {
sectId: string; // 库区标识
sectCode?: string; // 库区编码
sectName?: string; // 库区名称
simpleName: string; // 库区简称
sectTypeAttr?: string; // 库区类型
storId?: string; // 仓库标识
storType: string; // 仓库类型
capacity: number; // 容量
width: number; // 宽度
height: number; // 高度
zdepth: number; // 深度
xqty: number; // 起始X坐标
yqty: number; // 起始Y坐标
zqty: number; // 起始Z坐标
sectManagerName: string; // 负责人
mobileNo: string; // 负责人电话
remark: string; // 备注
backGroundColor: string; // 背景色
frontGroundColor: string; // 前景色
backGroundPic: string; // 背景图片
fontDirectionScode: string; // 字体显示方向
floorNo: number; // 所在楼层
isUsed?: string; // 是否启用
extId: string; // 外部标识
}
}
/** 查询库区属性分页 */
export function getSectAttrPage(params: PageParam) {
return requestClient.get<PageResult<WmsSectAttrApi.SectAttr>>(
'/wms/sect-attr/page',
{ params },
);
}
/** 查询库区属性详情 */
export function getSectAttr(id: string) {
return requestClient.get<WmsSectAttrApi.SectAttr>(
`/wms/sect-attr/get?id=${id}`,
);
}
/** 新增库区属性 */
export function createSectAttr(data: WmsSectAttrApi.SectAttr) {
return requestClient.post('/wms/sect-attr/create', data);
}
/** 修改库区属性 */
export function updateSectAttr(data: WmsSectAttrApi.SectAttr) {
return requestClient.put('/wms/sect-attr/update', data);
}
/** 删除库区属性 */
export function deleteSectAttr(id: string) {
return requestClient.delete(`/wms/sect-attr/delete?id=${id}`);
}
/** 批量删除库区属性 */
export function deleteSectAttrList(ids: string[]) {
return requestClient.delete(
`/wms/sect-attr/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出库区属性 */
export function exportSectAttr(params: any) {
return requestClient.download('/wms/sect-attr/export-excel', { params });
}
/** 获得启用的库区属性精简列表(可按仓库筛选) */
export function getSectAttrSimpleList(storId?: string) {
return requestClient.get<{ sectId: string; sectName: string; storId: string }[]>(
'/wms/sect-attr/simple-list',
{ params: storId ? { storId } : {} },
);
}

View File

@@ -0,0 +1,88 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace WmsStrucAttrApi {
/** 仓位属性信息 */
export interface StrucAttr {
structId: string; // 仓位标识
structCode?: string; // 仓位编码
structName?: string; // 仓位名称
simpleName: string; // 仓位简称
sectId?: string; // 库区标识
sectCode?: string; // 库区编码
sectName?: string; // 库区名称
storId?: string; // 仓库标识
storCode?: string; // 仓库编码
storName?: string; // 仓库名称
storType: string; // 仓库类型
capacity: number; // 容量
width: number; // 宽度
height: number; // 高度
zdepth: number; // 深度
weight: number; // 承受重量
xqty: number; // 起始X坐标
yqty: number; // 起始Y坐标
zqty: number; // 起始Z坐标
isTempstruct?: string; // 是否临时仓位
rowNum: number; // 排
colNum: number; // 列
layerNum: number; // 层
blockNum: number; // 块
placementType: string; // 放置类型
isUsed?: boolean; // 是否启用
isZdepth: string; // 是否判断高度
storagevehicleCode: string; // 存储载具号
storagevehicleType: string; // 载具类型
storagevehicleQty: number; // 载具数量
lockType?: string; // 锁定类型
taskCode: string; // 锁定任务编码
invType: string; // 锁定单据类型
invId: string; // 锁定单据标识
invCode: string; // 锁定单据编码
extId: string; // 外部标识
remark: string; // 备注
}
}
/** 查询仓位属性分页 */
export function getStrucAttrPage(params: PageParam) {
return requestClient.get<PageResult<WmsStrucAttrApi.StrucAttr>>(
'/wms/struc-attr/page',
{ params },
);
}
/** 查询仓位属性详情 */
export function getStrucAttr(id: number) {
return requestClient.get<WmsStrucAttrApi.StrucAttr>(
`/wms/struc-attr/get?id=${id}`,
);
}
/** 新增仓位属性 */
export function createStrucAttr(data: WmsStrucAttrApi.StrucAttr) {
return requestClient.post('/wms/struc-attr/create', data);
}
/** 修改仓位属性 */
export function updateStrucAttr(data: WmsStrucAttrApi.StrucAttr) {
return requestClient.put('/wms/struc-attr/update', data);
}
/** 删除仓位属性 */
export function deleteStrucAttr(id: number) {
return requestClient.delete(`/wms/struc-attr/delete?id=${id}`);
}
/** 批量删除仓位属性 */
export function deleteStrucAttrList(ids: number[]) {
return requestClient.delete(
`/wms/struc-attr/delete-list?ids=${ids.join(',')}`,
);
}
/** 导出仓位属性 */
export function exportStrucAttr(params: any) {
return requestClient.download('/wms/struc-attr/export-excel', { params });
}

View File

@@ -0,0 +1,377 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsSectAttrApi } from '#/api/wms/sectattr';
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
import { getRangePickerDefaultProps } from '#/utils';
/** 是/否 选项 */
const BOOLEAN_OPTIONS = [
{ label: '是', value: '1' },
{ label: '否', value: '0' },
];
// ========== 仓库名称映射表storId → storName ==========
const warehouseNameMap: Record<string, string> = {};
// ========== 启用的仓库下拉 ==========
async function loadWarehouseList() {
const data = await getBsrealStorAttrSimpleList();
// 填充映射表
if (data) {
data.forEach((item) => {
warehouseNameMap[item.storId] = item.storName;
});
}
return data || [];
}
/** 仓库标识 → 仓库名称 格式化 */
function warehouseFormatter({ cellValue }: { cellValue: string }) {
return warehouseNameMap[cellValue] || cellValue;
}
/** 是/否 格式化('1' → '是''0' → '否' */
function boolFormatter({ cellValue }: { cellValue: string }) {
return cellValue === '1' ? '是' : '否';
}
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'sectId',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'sectCode',
label: '库区编码',
rules: 'required',
component: 'Input',
componentProps: {
placeholder: '请输入库区编码',
},
},
{
fieldName: 'sectName',
label: '库区名称',
rules: 'required',
component: 'Input',
componentProps: {
placeholder: '请输入库区名称',
},
},
{
fieldName: 'simpleName',
label: '库区简称',
component: 'Input',
componentProps: {
placeholder: '请输入库区简称',
},
},
{
fieldName: 'sectTypeAttr',
label: '库区类型',
rules: 'required',
component: 'Input',
componentProps: {
placeholder: '请输入库区类型',
},
},
{
fieldName: 'storId',
label: '仓库标识',
rules: 'required',
component: 'ApiSelect',
componentProps: {
api: loadWarehouseList,
labelField: 'storName',
valueField: 'storId',
placeholder: '请选择仓库',
allowClear: true,
},
},
{
fieldName: 'storType',
label: '仓库类型',
component: 'Select',
componentProps: {
options: [],
placeholder: '请选择仓库类型',
},
},
{
fieldName: 'capacity',
label: '容量',
component: 'Input',
componentProps: {
placeholder: '请输入容量',
},
},
{
fieldName: 'width',
label: '宽度',
component: 'Input',
componentProps: {
placeholder: '请输入宽度',
},
},
{
fieldName: 'height',
label: '高度',
component: 'Input',
componentProps: {
placeholder: '请输入高度',
},
},
{
fieldName: 'zdepth',
label: '深度',
component: 'Input',
componentProps: {
placeholder: '请输入深度',
},
},
{
fieldName: 'xqty',
label: '起始X坐标',
component: 'Input',
componentProps: {
placeholder: '请输入起始X坐标',
},
},
{
fieldName: 'yqty',
label: '起始Y坐标',
component: 'Input',
componentProps: {
placeholder: '请输入起始Y坐标',
},
},
{
fieldName: 'zqty',
label: '起始Z坐标',
component: 'Input',
componentProps: {
placeholder: '请输入起始Z坐标',
},
},
{
fieldName: 'sectManagerName',
label: '负责人',
component: 'Input',
componentProps: {
placeholder: '请输入负责人',
},
},
{
fieldName: 'mobileNo',
label: '负责人电话',
component: 'Input',
componentProps: {
placeholder: '请输入负责人电话',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
componentProps: {
placeholder: '请输入备注',
},
},
{
fieldName: 'backGroundColor',
label: '背景色',
component: 'Input',
componentProps: {
placeholder: '请输入背景色',
},
},
{
fieldName: 'frontGroundColor',
label: '前景色',
component: 'Input',
componentProps: {
placeholder: '请输入前景色',
},
},
{
fieldName: 'backGroundPic',
label: '背景图片',
component: 'Input',
componentProps: {
placeholder: '请输入背景图片',
},
},
{
fieldName: 'fontDirectionScode',
label: '字体显示方向',
component: 'Input',
componentProps: {
placeholder: '请输入字体显示方向',
},
},
{
fieldName: 'floorNo',
label: '所在楼层',
component: 'Input',
componentProps: {
placeholder: '请输入所在楼层',
},
},
{
fieldName: 'isUsed',
label: '是否启用',
rules: 'required',
component: 'RadioGroup',
componentProps: {
options: BOOLEAN_OPTIONS,
buttonStyle: 'solid',
optionType: 'button',
},
},
{
fieldName: 'extId',
label: '外部标识',
component: 'Input',
componentProps: {
placeholder: '请输入外部标识',
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'sectCode',
label: '库区编码',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入库区编码',
},
},
{
fieldName: 'sectName',
label: '库区名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入库区名称',
},
},
{
fieldName: 'sectTypeAttr',
label: '库区类型',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入库区类型',
},
},
{
fieldName: 'storId',
label: '仓库标识',
component: 'ApiSelect',
componentProps: {
api: loadWarehouseList,
labelField: 'storName',
valueField: 'storId',
placeholder: '请选择仓库',
allowClear: true,
},
},
{
fieldName: 'createTime',
label: '创建时间',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
allowClear: true,
},
},
{
fieldName: 'isUsed',
label: '是否启用',
component: 'Select',
componentProps: {
allowClear: true,
options: BOOLEAN_OPTIONS,
placeholder: '请选择是否启用',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<WmsSectAttrApi.SectAttr>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'sectId',
title: '库区标识',
visible: false,
},
{
field: 'sectCode',
title: '库区编码',
minWidth: 120,
},
{
field: 'sectName',
title: '库区名称',
minWidth: 120,
},
{
field: 'simpleName',
title: '库区简称',
minWidth: 120,
},
{
field: 'sectTypeAttr',
title: '库区类型',
minWidth: 120,
},
{
field: 'storId',
title: '仓库名称',
minWidth: 120,
formatter: warehouseFormatter,
},
{
field: 'remark',
title: '备注',
minWidth: 120,
},
{
field: 'createTime',
title: '创建时间',
minWidth: 120,
formatter: 'formatDateTime',
},
{
field: 'isUsed',
title: '是否启用',
minWidth: 120,
formatter: boolFormatter,
},
{
field: 'extId',
title: '外部标识',
minWidth: 120,
},
{
title: '操作',
width: 200,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@@ -0,0 +1,186 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsSectAttrApi } from '#/api/wms/sectattr';
import { ref } from 'vue';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from 'antdv-next';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteSectAttr,
deleteSectAttrList,
exportSectAttr,
getSectAttrPage,
} from '#/api/wms/sectattr';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建库区属性 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑库区属性 */
function handleEdit(row: WmsSectAttrApi.SectAttr) {
formModalApi.setData(row).open();
}
/** 删除库区属性 */
async function handleDelete(row: WmsSectAttrApi.SectAttr) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.sectId]),
duration: 0,
});
try {
await deleteSectAttr(row.sectId!);
message.success($t('ui.actionMessage.deleteSuccess', [row.sectId]));
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除库区属性 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
});
try {
await deleteSectAttrList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading();
}
}
const checkedIds = ref<string[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: WmsSectAttrApi.SectAttr[];
}) {
checkedIds.value = records.map((item) => item.sectId! as string);
}
/** 导出表格 */
async function handleExport() {
const data = await exportSectAttr(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '库区属性.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSectAttrPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'sectId',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<WmsSectAttrApi.SectAttr>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<Grid table-title="库区属性列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['库区属性']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['wms:sect-attr:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['wms:sect-attr:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['wms:sect-attr:delete'],
disabled: isEmpty(checkedIds),
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['wms:sect-attr:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['wms:sect-attr:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.sectId]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,82 @@
<script lang="ts" setup>
import type { WmsSectAttrApi } from '#/api/wms/sectattr';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';
import { createSectAttr, getSectAttr, updateSectAttr } from '#/api/wms/sectattr';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<WmsSectAttrApi.SectAttr>();
const getTitle = computed(() => {
return formData.value?.sectId
? $t('ui.actionTitle.edit', ['库区属性'])
: $t('ui.actionTitle.create', ['库区属性']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as WmsSectAttrApi.SectAttr;
try {
await (formData.value?.sectId ? updateSectAttr(data) : createSectAttr(data));
// 关闭并提示
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
// 加载数据
const data = modalApi.getData<WmsSectAttrApi.SectAttr>();
if (!data || !data.sectId) {
return;
}
modalApi.lock();
try {
formData.value = await getSectAttr(data.sectId);
// 设置到 values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>

View File

@@ -0,0 +1,401 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsStrucAttrApi } from '#/api/wms/structAttr';
import { ref } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { getBsrealStorAttrSimpleList } from '#/api/wms/bsrealstorattr';
import { getSectAttrSimpleList } from '#/api/wms/sectattr';
import { getRangePickerDefaultProps } from '#/utils';
/** 是/否 选项 */
const BOOLEAN_OPTIONS = [
{ label: '是', value: '1' },
{ label: '否', value: '0' },
];
const BOOLEAN_TRUE_FALSE_OPTIONS = [
{ label: '是', value: true },
{ label: '否', value: false },
];
function boolFormatter({ cellValue }: { cellValue: boolean | string }) {
if (cellValue === true || cellValue === 'true' || cellValue === '1') return '是';
return '否';
}
// ========== 名称映射表 ==========
const warehouseNameMap: Record<string, string> = {};
const warehouseCodeMap: Record<string, string> = {};
const sectNameMap: Record<string, string> = {};
const sectCodeMap: Record<string, string> = {};
function warehouseFormatter({ cellValue }: { cellValue: string }) {
return warehouseNameMap[cellValue] || cellValue;
}
function sectFormatter({ cellValue }: { cellValue: string }) {
return sectNameMap[cellValue] || cellValue;
}
// ========== 仓库-库区级联树 ==========
const warehouseSectTree = ref<any[]>([]);
export async function loadWarehouseSectTree() {
if (warehouseSectTree.value.length > 0) return;
const warehouses = await getBsrealStorAttrSimpleList();
if (!warehouses) return;
const tree = await Promise.all(
warehouses.map(async (w: any) => {
warehouseNameMap[w.storId] = w.storName;
warehouseCodeMap[w.storId] = w.storCode || '';
const sects = await getSectAttrSimpleList(w.storId);
return {
label: w.storName,
value: w.storId,
children: (sects || []).map((s: any) => {
sectNameMap[s.sectId] = s.sectName;
sectCodeMap[s.sectId] = s.sectCode || '';
return { label: s.sectName, value: s.sectId };
}),
};
}),
);
warehouseSectTree.value = tree;
}
/** 导出映射,供 form.vue 使用 */
export function getWarehouseCode(id: string) { return warehouseCodeMap[id] || ''; }
export function getWarehouseName(id: string) { return warehouseNameMap[id] || ''; }
export function getSectCode(id: string) { return sectCodeMap[id] || ''; }
export function getSectName(id: string) { return sectNameMap[id] || ''; }
// 立即预加载
loadWarehouseSectTree();
/** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'structId',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'structCode',
label: '仓位编码',
rules: 'required',
component: 'Input',
componentProps: { placeholder: '请输入仓位编码' },
},
{
fieldName: 'structName',
label: '仓位名称',
rules: 'required',
component: 'Input',
componentProps: { placeholder: '请输入仓位名称' },
},
{
fieldName: 'simpleName',
label: '仓位简称',
component: 'Input',
componentProps: { placeholder: '请输入仓位简称' },
},
{
fieldName: 'warehouseSect',
label: '仓库 / 库区',
rules: 'required',
component: 'Cascader',
componentProps: {
options: warehouseSectTree,
placeholder: '请选择仓库和库区',
allowClear: true,
changeOnSelect: true,
},
},
{
fieldName: 'storType',
label: '仓库类型',
component: 'Select',
componentProps: { options: [], placeholder: '请选择仓库类型' },
},
{
fieldName: 'capacity',
label: '容量',
component: 'Input',
componentProps: { placeholder: '请输入容量' },
},
{
fieldName: 'width',
label: '宽度',
component: 'Input',
componentProps: { placeholder: '请输入宽度' },
},
{
fieldName: 'height',
label: '高度',
component: 'Input',
componentProps: { placeholder: '请输入高度' },
},
{
fieldName: 'zdepth',
label: '深度',
component: 'Input',
componentProps: { placeholder: '请输入深度' },
},
{
fieldName: 'weight',
label: '承受重量',
component: 'Input',
componentProps: { placeholder: '请输入承受重量' },
},
{
fieldName: 'xqty',
label: '起始X坐标',
component: 'Input',
componentProps: { placeholder: '请输入起始X坐标' },
},
{
fieldName: 'yqty',
label: '起始Y坐标',
component: 'Input',
componentProps: { placeholder: '请输入起始Y坐标' },
},
{
fieldName: 'zqty',
label: '起始Z坐标',
component: 'Input',
componentProps: { placeholder: '请输入起始Z坐标' },
},
{
fieldName: 'isTempstruct',
label: '是否临时仓位',
rules: 'required',
component: 'RadioGroup',
componentProps: {
options: BOOLEAN_OPTIONS,
buttonStyle: 'solid',
optionType: 'button',
},
},
{
fieldName: 'rowNum',
label: '排',
component: 'Input',
componentProps: { placeholder: '请输入排' },
},
{
fieldName: 'colNum',
label: '列',
component: 'Input',
componentProps: { placeholder: '请输入列' },
},
{
fieldName: 'layerNum',
label: '层',
component: 'Input',
componentProps: { placeholder: '请输入层' },
},
{
fieldName: 'blockNum',
label: '块',
component: 'Input',
componentProps: { placeholder: '请输入块' },
},
{
fieldName: 'placementType',
label: '放置类型',
component: 'Select',
componentProps: { options: [], placeholder: '请选择放置类型' },
},
{
fieldName: 'isUsed',
label: '是否启用',
rules: 'required',
component: 'RadioGroup',
componentProps: {
options: BOOLEAN_TRUE_FALSE_OPTIONS,
buttonStyle: 'solid',
optionType: 'button',
},
},
{
fieldName: 'isZdepth',
label: '是否判断高度',
component: 'RadioGroup',
componentProps: {
options: BOOLEAN_OPTIONS,
buttonStyle: 'solid',
optionType: 'button',
},
},
{
fieldName: 'storagevehicleCode',
label: '存储载具号',
component: 'Input',
componentProps: { placeholder: '请输入存储载具号' },
},
{
fieldName: 'storagevehicleType',
label: '载具类型',
component: 'Select',
componentProps: { options: [], placeholder: '请选择载具类型' },
},
{
fieldName: 'storagevehicleQty',
label: '载具数量',
component: 'Input',
componentProps: { placeholder: '请输入载具数量' },
},
{
fieldName: 'lockType',
label: '锁定类型',
rules: 'required',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.WMS_LOCK_TYPE, 'string'),
placeholder: '请选择锁定类型',
},
},
{
fieldName: 'taskCode',
label: '锁定任务编码',
component: 'Input',
componentProps: { placeholder: '请输入锁定任务编码' },
},
{
fieldName: 'invType',
label: '锁定单据类型',
component: 'Select',
componentProps: { options: [], placeholder: '请选择锁定单据类型' },
},
{
fieldName: 'invId',
label: '锁定单据标识',
component: 'Input',
componentProps: { placeholder: '请输入锁定单据标识' },
},
{
fieldName: 'invCode',
label: '锁定单据编码',
component: 'Input',
componentProps: { placeholder: '请输入锁定单据编码' },
},
{
fieldName: 'extId',
label: '外部标识',
component: 'Input',
componentProps: { placeholder: '请输入外部标识' },
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
componentProps: { placeholder: '请输入备注' },
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'structCode',
label: '仓位编码',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入仓位编码' },
},
{
fieldName: 'structName',
label: '仓位名称',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入仓位名称' },
},
{
fieldName: 'warehouseSect',
label: '仓库 / 库区',
component: 'Cascader',
componentProps: {
options: warehouseSectTree,
placeholder: '请选择仓库和库区',
allowClear: true,
changeOnSelect: true,
},
},
{
fieldName: 'createTime',
label: '创建时间',
component: 'RangePicker',
componentProps: { ...getRangePickerDefaultProps(), allowClear: true },
},
{
fieldName: 'isUsed',
label: '是否启用',
component: 'Select',
componentProps: {
allowClear: true,
options: BOOLEAN_TRUE_FALSE_OPTIONS,
placeholder: '请选择是否启用',
},
},
{
fieldName: 'storagevehicleCode',
label: '存储载具号',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入存储载具号' },
},
{
fieldName: 'lockType',
label: '锁定类型',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.WMS_LOCK_TYPE, 'string'),
placeholder: '请选择锁定类型',
},
},
{
fieldName: 'taskCode',
label: '锁定任务编码',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入锁定任务编码' },
},
{
fieldName: 'invCode',
label: '锁定单据编码',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入锁定单据编码' },
},
{
fieldName: 'extId',
label: '外部标识',
component: 'Input',
componentProps: { allowClear: true, placeholder: '请输入外部标识' },
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<WmsStrucAttrApi.StrucAttr>['columns'] {
return [
{ type: 'checkbox', width: 40 },
{ field: 'structId', title: '仓位标识', minWidth: 120 },
{ field: 'structCode', title: '仓位编码', minWidth: 120 },
{ field: 'structName', title: '仓位名称', minWidth: 120 },
{ field: 'simpleName', title: '仓位简称', minWidth: 120 },
{ field: 'storId', title: '仓库名称', minWidth: 120, formatter: warehouseFormatter },
{ field: 'sectId', title: '库区名称', minWidth: 120, formatter: sectFormatter },
{ field: 'height', title: '高度', minWidth: 120 },
{ field: 'rowNum', title: '排', minWidth: 120 },
{ field: 'colNum', title: '列', minWidth: 120 },
{ field: 'layerNum', title: '层', minWidth: 120 },
{ field: 'blockNum', title: '块', minWidth: 120 },
{ field: 'isTempstruct', title: '是否临时仓位', minWidth: 120, formatter: boolFormatter },
{ field: 'isUsed', title: '是否启用', minWidth: 120, formatter: boolFormatter },
{ field: 'isZdepth', title: '是否判断高度', minWidth: 120, formatter: boolFormatter },
{ field: 'createTime', title: '创建时间', minWidth: 120, formatter: 'formatDateTime' },
{ field: 'remark', title: '备注', minWidth: 120 },
{ title: '操作', width: 200, fixed: 'right', slots: { default: 'actions' } },
];
}

View File

@@ -0,0 +1,190 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsStrucAttrApi } from '#/api/wms/structAttr';
import { ref } from 'vue';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from 'antdv-next';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteStrucAttr,
deleteStrucAttrList,
exportStrucAttr,
getStrucAttrPage,
} from '#/api/wms/structAttr';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建仓位属性 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑仓位属性 */
function handleEdit(row: WmsStrucAttrApi.StrucAttr) {
formModalApi.setData(row).open();
}
/** 删除仓位属性 */
async function handleDelete(row: WmsStrucAttrApi.StrucAttr) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.structId]),
duration: 0,
});
try {
await deleteStrucAttr(row.structId!);
message.success($t('ui.actionMessage.deleteSuccess', [row.structId]));
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除仓位属性 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
});
try {
await deleteStrucAttrList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading();
}
}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: WmsStrucAttrApi.StrucAttr[];
}) {
checkedIds.value = records.map((item) => item.structId!);
}
/** 导出表格 */
async function handleExport() {
const data = await exportStrucAttr(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '仓位属性.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
// Cascader 值映射warehouseSect[0]=storId, warehouseSect[1]=sectId
const { warehouseSect, ...rest } = formValues as any;
if (warehouseSect?.length >= 1) rest.storId = warehouseSect[0];
if (warehouseSect?.length >= 2) rest.sectId = warehouseSect[1];
return await getStrucAttrPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...rest,
});
},
},
},
rowConfig: {
keyField: 'structId',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<WmsStrucAttrApi.StrucAttr>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
<Grid table-title="仓位属性列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['仓位属性']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['wms:struc-attr:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['wms:struc-attr:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['wms:struc-attr:delete'],
disabled: isEmpty(checkedIds),
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['wms:struc-attr:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['wms:struc-attr:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.structId]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,108 @@
<script lang="ts" setup>
import type { WmsStrucAttrApi } from '#/api/wms/structAttr';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'antdv-next';
import { useVbenForm } from '#/adapter/form';
import { createStrucAttr, getStrucAttr, updateStrucAttr } from '#/api/wms/structAttr';
import { $t } from '#/locales';
import {
getSectCode,
getSectName,
getWarehouseCode,
getWarehouseName,
loadWarehouseSectTree,
useFormSchema,
} from '../data';
const emit = defineEmits(['success']);
const formData = ref<WmsStrucAttrApi.StrucAttr>();
const getTitle = computed(() => {
return formData.value?.structId
? $t('ui.actionTitle.edit', ['仓位属性'])
: $t('ui.actionTitle.create', ['仓位属性']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: { class: 'w-full' },
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) return;
modalApi.lock();
const data = (await formApi.getValues()) as WmsStrucAttrApi.StrucAttr & {
warehouseSect?: string[];
};
// Cascader 值映射warehouseSect[0]=storId, warehouseSect[1]=sectId
if (data.warehouseSect) {
const storId = data.warehouseSect[0];
const sectId = data.warehouseSect[1] || '';
data.storId = storId;
data.sectId = sectId;
data.storCode = getWarehouseCode(storId);
data.storName = getWarehouseName(storId);
data.sectCode = getSectCode(sectId);
data.sectName = getSectName(sectId);
delete data.warehouseSect;
}
try {
await (formData.value?.structId ? updateStrucAttr(data) : createStrucAttr(data));
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
const data = modalApi.getData<WmsStrucAttrApi.StrucAttr>();
if (!data || !data.structId) return;
modalApi.lock();
try {
// 预加载级联树
await loadWarehouseSectTree();
formData.value = await getStrucAttr(data.structId);
// 将 storId + sectId 合并为 Cascader 值
const cascadeValue = [formData.value.storId];
if (formData.value.sectId) {
cascadeValue.push(formData.value.sectId);
}
formData.value = { ...formData.value, warehouseSect: cascadeValue } as any;
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>

View File

@@ -279,6 +279,7 @@ const WMS_DICT = {
WMS_ORDER_STATUS: 'wms_order_status', // WMS 单据状态
WMS_RECEIPT_ORDER_TYPE: 'wms_receipt_order_type', // WMS 入库单类型
WMS_SHIPMENT_ORDER_TYPE: 'wms_shipment_order_type', // WMS 出库单类型
WMS_LOCK_TYPE: 'wms_lock_type', // WMS 锁定类型
} as const;
/** ========== TASK - 任务管理模块 ========== */