feat(mes): 迁移 barcode

pull/350/head
YunaiV 2026-05-29 19:24:52 +08:00
parent d245eca60d
commit f27942c8f9
12 changed files with 2624 additions and 0 deletions

View File

@ -0,0 +1,98 @@
import type { VbenFormSchema } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
// TODO @AI这里的代码风格不对
/** 表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: { triggerFields: [''], show: () => false },
},
{
fieldName: 'format',
label: '条码格式',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
placeholder: '请选择条码格式',
},
rules: 'required',
},
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
dependencies: {
triggerFields: ['id'],
componentProps: (values) => ({
// 编辑时业务类型不允许变更 TODO @AI这种简单的注释可以考虑放到 disabled 后面;写到 style 里;
disabled: !!values.id,
}),
},
rules: 'required',
},
{
fieldName: 'contentFormat',
label: '内容格式模板',
component: 'Input',
componentProps: {
placeholder: '支持{BUSINESSCODE}占位符WH-{BUSINESSCODE}',
},
rules: 'required',
},
{
fieldName: 'contentExample',
label: '内容样例',
component: 'Input',
componentProps: {
placeholder: '如WH-WH001',
},
},
{
fieldName: 'autoGenerateFlag',
label: '自动生成',
component: 'Switch',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
},
rules: z.boolean().default(true),
},
{
fieldName: 'defaultTemplate',
label: '默认打印模板',
component: 'Input',
componentProps: {
placeholder: '请输入打印模板',
},
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}

View File

@ -0,0 +1,156 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteBarcodeConfig,
getBarcodeConfigPage,
updateBarcodeConfig,
} from '#/api/mes/wm/barcode/config';
import { $t } from '#/locales';
import { useConfigGridColumns, useConfigGridFormSchema } from '../data';
import ConfigForm from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: ConfigForm,
destroyOnClose: true,
});
/** 自动生成开关变更 */
async function handleAutoGenerateChange(
row: MesWmBarcodeConfigApi.BarcodeConfig,
): Promise<boolean> {
const text = row.autoGenerateFlag ? '启用' : '停用';
try {
await confirm(`确认要${text}自动生成吗?`);
} catch {
return false;
}
await updateBarcodeConfig(row);
message.success(`${text}成功`);
return true;
}
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建条码配置 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑条码配置 */
function handleEdit(row: MesWmBarcodeConfigApi.BarcodeConfig) {
formModalApi.setData(row).open();
}
/** 删除条码配置 */
async function handleDelete(row: MesWmBarcodeConfigApi.BarcodeConfig) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.contentFormat || `#${row.id}`]),
duration: 0,
});
try {
await deleteBarcodeConfig(row.id!);
message.success(
$t('ui.actionMessage.deleteSuccess', [row.contentFormat || `#${row.id}`]),
);
handleRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useConfigGridFormSchema(),
},
gridOptions: {
columns: useConfigGridColumns(handleAutoGenerateChange),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getBarcodeConfigPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【仓库】仓库与库区库位、条码赋码、SN码"
url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['条码配置']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:wm-barcode-config:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:wm-barcode-config:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode-config:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.contentFormat || `#${row.id}`,
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,89 @@
<script lang="ts" setup>
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createBarcodeConfig,
getBarcodeConfig,
updateBarcodeConfig,
} from '#/api/mes/wm/barcode/config';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesWmBarcodeConfigApi.BarcodeConfig>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['条码配置'])
: $t('ui.actionTitle.create', ['条码配置']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 130,
},
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 MesWmBarcodeConfigApi.BarcodeConfig;
try {
await (formData.value?.id
? updateBarcodeConfig(data)
: createBarcodeConfig(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<MesWmBarcodeConfigApi.BarcodeConfig>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getBarcodeConfig(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,599 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { markRaw } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { generateBarcodeContent } from '#/api/mes/wm/barcode';
import DvMachinerySelect from '#/views/mes/dv/machinery/components/dv-machinery-select.vue';
import MdClientSelect from '#/views/mes/md/client/components/md-client-select.vue';
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
import MdVendorSelect from '#/views/mes/md/vendor/components/md-vendor-select.vue';
import MdWorkshopSelect from '#/views/mes/md/workstation/components/md-workshop-select.vue';
import MdWorkstationSelect from '#/views/mes/md/workstation/components/md-workstation-select.vue';
import ProWorkOrderSelect from '#/views/mes/pro/workorder/components/pro-work-order-select.vue';
import TmToolSelect from '#/views/mes/tm/tool/components/tm-tool-select.vue';
import { BarcodeBizTypeEnum } from '#/views/mes/utils/constants';
import WmMaterialStockSelect from './../materialstock/components/wm-material-stock-select.vue';
import {
WmWarehouseAreaSelect,
WmWarehouseLocationSelect,
WmWarehouseSelect,
} from './../warehouse/components';
/** 业务对象选中后回填业务编码、业务名称、条码内容 */
async function applyBizSelected(formApi: VbenFormApi, item: any) {
const values = await formApi.getValues();
const bizType = values.bizType as number | undefined;
if (!item) {
await formApi.setValues({
bizCode: undefined,
bizName: undefined,
content: undefined,
});
return;
}
let bizCode: string | undefined;
let bizName: string | undefined;
if (bizType === BarcodeBizTypeEnum.STOCK) {
bizCode = item.itemCode;
bizName = item.itemName;
} else {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
}
let content: string | undefined;
if (bizType && bizCode) {
try {
content = await generateBarcodeContent(bizType, bizCode);
} catch (error) {
console.error('生成条码内容失败:', error);
}
}
await formApi.setValues({ bizCode, bizName, content });
}
/** 新增/修改条码的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
// TODO @AI是不是可以去掉 onBizChange直接搞个 handleBizTypeChange 函数?
const onBizChange = (item: any) => {
if (formApi) {
void applyBizSelected(formApi, item);
}
};
return [
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
// 业务类型变更时清空业务字段
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
locationWarehouseId: undefined,
areaWarehouseId: undefined,
areaLocationId: undefined,
}),
},
rules: 'required',
},
// ==================== 业务对象选择器(按 bizType 切换) ====================
{
fieldName: 'bizId',
label: '仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WAREHOUSE,
},
rules: 'required',
},
{
fieldName: 'locationWarehouseId',
label: '库区·仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
placeholder: '请选择仓库',
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
},
},
{
fieldName: 'bizId',
label: '库区',
component: markRaw(WmWarehouseLocationSelect),
dependencies: {
triggerFields: ['bizType', 'locationWarehouseId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
componentProps: (values) => ({
placeholder: '请选择库区',
warehouseId: values.locationWarehouseId,
onChange: onBizChange,
}),
},
rules: 'required',
},
{
fieldName: 'areaWarehouseId',
label: '库位·仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
placeholder: '请选择仓库',
onChange: () =>
formApi?.setValues({
areaLocationId: undefined,
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
},
},
{
fieldName: 'areaLocationId',
label: '库位·库区',
component: markRaw(WmWarehouseLocationSelect),
dependencies: {
triggerFields: ['bizType', 'areaWarehouseId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
componentProps: (values) => ({
placeholder: '请选择库区',
warehouseId: values.areaWarehouseId,
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
}),
},
},
{
fieldName: 'bizId',
label: '库位',
component: markRaw(WmWarehouseAreaSelect),
dependencies: {
triggerFields: ['bizType', 'areaLocationId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
componentProps: (values) => ({
placeholder: '请选择库位',
locationId: values.areaLocationId,
onChange: onBizChange,
}),
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKORDER,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '设备',
component: markRaw(DvMachinerySelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.MACHINERY,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.ITEM,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '供应商',
component: markRaw(MdVendorSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.VENDOR,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工作站',
component: markRaw(MdWorkstationSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSTATION,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '车间',
component: markRaw(MdWorkshopSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSHOP,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '客户',
component: markRaw(MdClientSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.CLIENT,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工具',
component: markRaw(TmToolSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.TOOL,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '库存',
component: markRaw(WmMaterialStockSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.STOCK,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '业务编号',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 1,
placeholder: '请输入业务编号',
},
dependencies: {
triggerFields: ['bizType'],
show: (values) =>
values.bizType !== undefined &&
![
BarcodeBizTypeEnum.AREA,
BarcodeBizTypeEnum.CLIENT,
BarcodeBizTypeEnum.ITEM,
BarcodeBizTypeEnum.LOCATION,
BarcodeBizTypeEnum.MACHINERY,
BarcodeBizTypeEnum.STOCK,
BarcodeBizTypeEnum.TOOL,
BarcodeBizTypeEnum.VENDOR,
BarcodeBizTypeEnum.WAREHOUSE,
BarcodeBizTypeEnum.WORKORDER,
BarcodeBizTypeEnum.WORKSHOP,
BarcodeBizTypeEnum.WORKSTATION,
].includes(values.bizType),
},
},
{
fieldName: 'bizCode',
label: '业务编码',
component: 'Input',
componentProps: {
disabled: true,
placeholder: '自动填充',
},
},
{
fieldName: 'bizName',
label: '业务名称',
component: 'Input',
componentProps: {
disabled: true,
placeholder: '自动填充',
},
},
{
fieldName: 'content',
label: '条码内容',
component: 'Input',
componentProps: {
placeholder: '请输入条码内容或自动生成',
},
rules: 'required',
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
},
{
fieldName: 'bizCode',
label: '业务编码',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入业务编码',
},
},
{
fieldName: 'bizName',
label: '业务名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入业务名称',
},
},
{
fieldName: 'content',
label: '条码内容',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入条码内容',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesWmBarcodeApi.Barcode>['columns'] {
return [
{
type: 'checkbox',
width: 50,
},
{
field: 'content',
title: '条码',
width: 180,
slots: { default: 'barcode' },
},
{
field: 'format',
title: '条码格式',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
},
},
{
field: 'bizType',
title: '业务类型',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
},
},
{
field: 'content',
title: '条码内容',
minWidth: 160,
},
{
field: 'bizCode',
title: '业务编码',
width: 130,
},
{
field: 'bizName',
title: '业务名称',
minWidth: 140,
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
title: '操作',
width: 200,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 配置列表的搜索表单 */
export function useConfigGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'format',
label: '条码格式',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
placeholder: '请选择条码格式',
},
},
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
},
];
}
/** 配置列表的字段 */
export function useConfigGridColumns(
onAutoGenerateChange: (
row: MesWmBarcodeConfigApi.BarcodeConfig,
) => Promise<boolean>,
): VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>['columns'] {
return [
{
field: 'id',
title: '编号',
width: 100,
},
{
field: 'format',
title: '条码格式',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
},
},
{
field: 'bizType',
title: '业务类型',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
},
},
{
field: 'contentFormat',
title: '内容格式',
minWidth: 160,
},
{
field: 'contentExample',
title: '内容样例',
minWidth: 160,
},
{
field: 'defaultTemplate',
title: '默认打印模板',
minWidth: 140,
},
{
field: 'autoGenerateFlag',
title: '自动生成',
width: 100,
cellRender: {
name: 'CellSwitch',
attrs: { beforeChange: onAutoGenerateChange },
},
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -0,0 +1,245 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteBarcode,
exportBarcode,
getBarcodePage,
} from '#/api/mes/wm/barcode';
import { $t } from '#/locales';
import { Barcode, BarcodeDetail } from './components';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const router = useRouter();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
// TODO @AI checkedIds handleRowCheckboxChange
// TODO @AI system user index
/** 已选条码 ID */
const checkedIds = ref<number[]>([]);
/** 处理勾选变化 */
function handleRowCheckboxChange({
records,
}: {
records: MesWmBarcodeApi.Barcode[];
}) {
checkedIds.value = records.map((row) => row.id!).filter(Boolean);
}
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建条码 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑条码 */
function handleEdit(row: MesWmBarcodeApi.Barcode) {
formModalApi.setData(row).open();
}
/** 删除条码 */
async function handleDelete(row: MesWmBarcodeApi.Barcode) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.bizName || row.bizCode || '']),
duration: 0,
});
try {
await deleteBarcode(row.id!);
message.success(
$t('ui.actionMessage.deleteSuccess', [row.bizName || row.bizCode || '']),
);
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除条码 */
async function handleDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [`${checkedIds.value.length}`]),
duration: 0,
});
try {
await Promise.all(checkedIds.value.map((id) => deleteBarcode(id)));
message.success($t('ui.actionMessage.deleteSuccess', ['']));
checkedIds.value = [];
handleRefresh();
} finally {
hideLoading();
}
}
/** 查看条码 */
function handleView(row: MesWmBarcodeApi.Barcode) {
barcodeDetailRef.value?.open(row);
}
/** 跳转条码配置 */
function handleConfig() {
router.push({ name: 'MesWmBarcodeConfig' });
}
/** 导出表格 */
async function handleExport() {
const data = await exportBarcode(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 getBarcodePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
height: 80,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBarcodeApi.Barcode>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【仓库】仓库与库区库位、条码赋码、SN码"
url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
/>
</template>
<FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['条码']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:wm-barcode:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode:delete'],
disabled: checkedIds.length === 0,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
`${checkedIds.length}`,
]),
confirm: handleDeleteBatch,
},
},
{
label: '条码设置',
type: 'primary',
auth: ['mes:wm-barcode-config:query'],
onClick: handleConfig,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:wm-barcode:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #barcode="{ row }">
<div v-if="row.content" class="flex justify-center">
<Barcode
:content="row.content"
:format="row.format"
:height="60"
:width="140"
/>
</div>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
auth: ['mes:wm-barcode:query'],
onClick: handleView.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['mes:wm-barcode:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.bizName || row.bizCode || '',
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,123 @@
<script lang="ts" setup>
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import {
createBarcode,
getBarcode,
updateBarcode,
} from '#/api/mes/wm/barcode';
import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
import { $t } from '#/locales';
import { BarcodeBizTypeEnum } from '#/views/mes/utils/constants';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesWmBarcodeApi.Barcode>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['条码'])
: $t('ui.actionTitle.create', ['条码']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 110,
},
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 加载级联选择器的数据(编辑时回填上级仓库/库区) */
async function loadCascadeData(data: MesWmBarcodeApi.Barcode) {
if (!data.bizType || !data.bizId) {
return;
}
try {
if (data.bizType === BarcodeBizTypeEnum.LOCATION) {
const location = await getWarehouseLocation(data.bizId);
if (location?.warehouseId) {
await formApi.setFieldValue(
'locationWarehouseId',
location.warehouseId,
);
}
} else if (data.bizType === BarcodeBizTypeEnum.AREA) {
const area = await getWarehouseArea(data.bizId);
await formApi.setValues({
areaWarehouseId: area?.warehouseId,
areaLocationId: area?.locationId,
});
}
} catch (error) {
console.error('加载级联数据失败:', error);
}
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesWmBarcodeApi.Barcode;
try {
await (formData.value?.id
? updateBarcode({ ...data, id: formData.value.id })
: createBarcode(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;
}
formApi.setState({ schema: useFormSchema(formApi) });
await formApi.setValues({ status: CommonStatusEnum.ENABLE });
//
const data = modalApi.getData<MesWmBarcodeApi.Barcode>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getBarcode(data.id);
// values
await formApi.setValues(formData.value);
await loadCascadeData(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-1/2">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,101 @@
import type { VbenFormSchema } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
/** 表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'format',
label: '条码格式',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
placeholder: '请选择条码格式',
},
rules: 'required',
},
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
dependencies: {
triggerFields: ['id'],
componentProps: (values) => ({
// 编辑时业务类型不允许变更
disabled: !!values.id,
}),
},
rules: 'required',
},
{
fieldName: 'contentFormat',
label: '内容格式模板',
component: 'Input',
componentProps: {
placeholder: '支持{BUSINESSCODE}占位符WH-{BUSINESSCODE}',
},
rules: 'required',
},
{
fieldName: 'contentExample',
label: '内容样例',
component: 'Input',
componentProps: {
placeholder: '如WH-WH001',
},
},
{
fieldName: 'autoGenerateFlag',
label: '自动生成',
component: 'Switch',
componentProps: {
activeText: '是',
inactiveText: '否',
inlinePrompt: true,
},
rules: z.boolean().default(true),
},
{
fieldName: 'defaultTemplate',
label: '默认打印模板',
component: 'Input',
componentProps: {
placeholder: '请输入打印模板',
},
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}

View File

@ -0,0 +1,160 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteBarcodeConfig,
getBarcodeConfigPage,
updateBarcodeConfig,
} from '#/api/mes/wm/barcode/config';
import { $t } from '#/locales';
import { useConfigGridColumns, useConfigGridFormSchema } from '../data';
import ConfigForm from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: ConfigForm,
destroyOnClose: true,
});
/** 自动生成开关变更 */
async function handleAutoGenerateChange(
row: MesWmBarcodeConfigApi.BarcodeConfig,
): Promise<boolean> {
const text = row.autoGenerateFlag ? '启用' : '停用';
try {
await confirm(`确认要${text}自动生成吗?`);
} catch {
return false;
}
await updateBarcodeConfig(row);
ElMessage.success(`${text}成功`);
return true;
}
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建条码配置 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑条码配置 */
function handleEdit(row: MesWmBarcodeConfigApi.BarcodeConfig) {
formModalApi.setData(row).open();
}
/** 删除条码配置 */
async function handleDelete(row: MesWmBarcodeConfigApi.BarcodeConfig) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [
row.contentFormat || `#${row.id}`,
]),
});
try {
await deleteBarcodeConfig(row.id!);
ElMessage.success(
$t('ui.actionMessage.deleteSuccess', [
row.contentFormat || `#${row.id}`,
]),
);
handleRefresh();
} finally {
loadingInstance.close();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useConfigGridFormSchema(),
},
gridOptions: {
columns: useConfigGridColumns(handleAutoGenerateChange),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getBarcodeConfigPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【仓库】仓库与库区库位、条码赋码、SN码"
url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
/>
</template>
<FormModal @success="handleRefresh" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['条码配置']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:wm-barcode-config:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:wm-barcode-config:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode-config:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.contentFormat || `#${row.id}`,
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,89 @@
<script lang="ts" setup>
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createBarcodeConfig,
getBarcodeConfig,
updateBarcodeConfig,
} from '#/api/mes/wm/barcode/config';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesWmBarcodeConfigApi.BarcodeConfig>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['条码配置'])
: $t('ui.actionTitle.create', ['条码配置']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 130,
},
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 MesWmBarcodeConfigApi.BarcodeConfig;
try {
await (formData.value?.id
? updateBarcodeConfig(data)
: createBarcodeConfig(data));
//
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
//
const data = modalApi.getData<MesWmBarcodeConfigApi.BarcodeConfig>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getBarcodeConfig(data.id);
// values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" />
</Modal>
</template>

View File

@ -0,0 +1,599 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
import { markRaw } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { generateBarcodeContent } from '#/api/mes/wm/barcode';
import DvMachinerySelect from '#/views/mes/dv/machinery/components/dv-machinery-select.vue';
import MdClientSelect from '#/views/mes/md/client/components/md-client-select.vue';
import MdItemSelect from '#/views/mes/md/item/components/md-item-select.vue';
import MdVendorSelect from '#/views/mes/md/vendor/components/md-vendor-select.vue';
import MdWorkshopSelect from '#/views/mes/md/workstation/components/md-workshop-select.vue';
import MdWorkstationSelect from '#/views/mes/md/workstation/components/md-workstation-select.vue';
import ProWorkOrderSelect from '#/views/mes/pro/workorder/components/pro-work-order-select.vue';
import TmToolSelect from '#/views/mes/tm/tool/components/tm-tool-select.vue';
import { BarcodeBizTypeEnum } from '#/views/mes/utils/constants';
import WmMaterialStockSelect from './../materialstock/components/wm-material-stock-select.vue';
import {
WmWarehouseAreaSelect,
WmWarehouseLocationSelect,
WmWarehouseSelect,
} from './../warehouse/components';
/** 业务对象选中后回填业务编码、业务名称、条码内容 */
async function applyBizSelected(formApi: VbenFormApi, item: any) {
const values = await formApi.getValues();
const bizType = values.bizType as number | undefined;
if (!item) {
await formApi.setValues({
bizCode: undefined,
bizName: undefined,
content: undefined,
});
return;
}
let bizCode: string | undefined;
let bizName: string | undefined;
if (bizType === BarcodeBizTypeEnum.STOCK) {
bizCode = item.itemCode;
bizName = item.itemName;
} else {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
}
let content: string | undefined;
if (bizType && bizCode) {
try {
content = await generateBarcodeContent(bizType, bizCode);
} catch (error) {
console.error('生成条码内容失败:', error);
}
}
await formApi.setValues({ bizCode, bizName, content });
}
/** 新增/修改条码的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
const onBizChange = (item: any) => {
if (formApi) {
void applyBizSelected(formApi, item);
}
};
return [
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
// 业务类型变更时清空业务字段
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
locationWarehouseId: undefined,
areaWarehouseId: undefined,
areaLocationId: undefined,
}),
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
rules: 'required',
},
// ==================== 业务对象选择器(按 bizType 切换) ====================
{
fieldName: 'bizId',
label: '仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WAREHOUSE,
},
rules: 'required',
},
{
fieldName: 'locationWarehouseId',
label: '库区·仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
placeholder: '请选择仓库',
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
},
},
{
fieldName: 'bizId',
label: '库区',
component: markRaw(WmWarehouseLocationSelect),
dependencies: {
triggerFields: ['bizType', 'locationWarehouseId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
componentProps: (values) => ({
onChange: onBizChange,
placeholder: '请选择库区',
warehouseId: values.locationWarehouseId,
}),
},
rules: 'required',
},
{
fieldName: 'areaWarehouseId',
label: '库位·仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
onChange: () =>
formApi?.setValues({
areaLocationId: undefined,
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
placeholder: '请选择仓库',
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
},
},
{
fieldName: 'areaLocationId',
label: '库位·库区',
component: markRaw(WmWarehouseLocationSelect),
dependencies: {
triggerFields: ['bizType', 'areaWarehouseId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
componentProps: (values) => ({
onChange: () =>
formApi?.setValues({
bizId: undefined,
bizCode: undefined,
bizName: undefined,
content: undefined,
}),
placeholder: '请选择库区',
warehouseId: values.areaWarehouseId,
}),
},
},
{
fieldName: 'bizId',
label: '库位',
component: markRaw(WmWarehouseAreaSelect),
dependencies: {
triggerFields: ['bizType', 'areaLocationId'],
show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
componentProps: (values) => ({
locationId: values.areaLocationId,
onChange: onBizChange,
placeholder: '请选择库位',
}),
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKORDER,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '设备',
component: markRaw(DvMachinerySelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.MACHINERY,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.ITEM,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '供应商',
component: markRaw(MdVendorSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.VENDOR,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工作站',
component: markRaw(MdWorkstationSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSTATION,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '车间',
component: markRaw(MdWorkshopSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSHOP,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '客户',
component: markRaw(MdClientSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.CLIENT,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '工具',
component: markRaw(TmToolSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.TOOL,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '库存',
component: markRaw(WmMaterialStockSelect),
componentProps: {
onChange: onBizChange,
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.STOCK,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '业务编号',
component: 'InputNumber',
componentProps: {
class: '!w-full',
controlsPosition: 'right',
min: 1,
placeholder: '请输入业务编号',
},
dependencies: {
triggerFields: ['bizType'],
show: (values) =>
values.bizType !== undefined &&
![
BarcodeBizTypeEnum.AREA,
BarcodeBizTypeEnum.CLIENT,
BarcodeBizTypeEnum.ITEM,
BarcodeBizTypeEnum.LOCATION,
BarcodeBizTypeEnum.MACHINERY,
BarcodeBizTypeEnum.STOCK,
BarcodeBizTypeEnum.TOOL,
BarcodeBizTypeEnum.VENDOR,
BarcodeBizTypeEnum.WAREHOUSE,
BarcodeBizTypeEnum.WORKORDER,
BarcodeBizTypeEnum.WORKSHOP,
BarcodeBizTypeEnum.WORKSTATION,
].includes(values.bizType),
},
},
{
fieldName: 'bizCode',
label: '业务编码',
component: 'Input',
componentProps: {
disabled: true,
placeholder: '自动填充',
},
},
{
fieldName: 'bizName',
label: '业务名称',
component: 'Input',
componentProps: {
disabled: true,
placeholder: '自动填充',
},
},
{
fieldName: 'content',
label: '条码内容',
component: 'Input',
componentProps: {
placeholder: '请输入条码内容或自动生成',
},
rules: 'required',
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
componentProps: {
placeholder: '请输入备注',
rows: 2,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
},
{
fieldName: 'bizCode',
label: '业务编码',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入业务编码',
},
},
{
fieldName: 'bizName',
label: '业务名称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入业务名称',
},
},
{
fieldName: 'content',
label: '条码内容',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入条码内容',
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesWmBarcodeApi.Barcode>['columns'] {
return [
{
type: 'checkbox',
width: 50,
},
{
field: 'content',
title: '条码',
width: 180,
slots: { default: 'barcode' },
},
{
field: 'format',
title: '条码格式',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
},
},
{
field: 'bizType',
title: '业务类型',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
},
},
{
field: 'content',
title: '条码内容',
minWidth: 160,
},
{
field: 'bizCode',
title: '业务编码',
width: 130,
},
{
field: 'bizName',
title: '业务名称',
minWidth: 140,
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
title: '操作',
width: 200,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 配置列表的搜索表单 */
export function useConfigGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'format',
label: '条码格式',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
placeholder: '请选择条码格式',
},
},
{
fieldName: 'bizType',
label: '业务类型',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
placeholder: '请选择业务类型',
},
},
];
}
/** 配置列表的字段 */
export function useConfigGridColumns(
onAutoGenerateChange: (
row: MesWmBarcodeConfigApi.BarcodeConfig,
) => Promise<boolean>,
): VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>['columns'] {
return [
{
field: 'id',
title: '编号',
width: 100,
},
{
field: 'format',
title: '条码格式',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
},
},
{
field: 'bizType',
title: '业务类型',
width: 110,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
},
},
{
field: 'contentFormat',
title: '内容格式',
minWidth: 160,
},
{
field: 'contentExample',
title: '内容样例',
minWidth: 160,
},
{
field: 'defaultTemplate',
title: '默认打印模板',
minWidth: 140,
},
{
field: 'autoGenerateFlag',
title: '自动生成',
width: 100,
cellRender: {
name: 'CellSwitch',
attrs: { beforeChange: onAutoGenerateChange },
},
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@ -0,0 +1,242 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteBarcode,
exportBarcode,
getBarcodePage,
} from '#/api/mes/wm/barcode';
import { $t } from '#/locales';
import { Barcode, BarcodeDetail } from './components';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const router = useRouter();
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
/** 已选条码 ID */
const checkedIds = ref<number[]>([]);
/** 处理勾选变化 */
function handleRowCheckboxChange({
records,
}: {
records: MesWmBarcodeApi.Barcode[];
}) {
checkedIds.value = records.map((row) => row.id!).filter(Boolean);
}
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 创建条码 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑条码 */
function handleEdit(row: MesWmBarcodeApi.Barcode) {
formModalApi.setData(row).open();
}
/** 删除条码 */
async function handleDelete(row: MesWmBarcodeApi.Barcode) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.bizName || row.bizCode || '']),
});
try {
await deleteBarcode(row.id!);
ElMessage.success(
$t('ui.actionMessage.deleteSuccess', [row.bizName || row.bizCode || '']),
);
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 批量删除条码 */
async function handleDeleteBatch() {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [`${checkedIds.value.length}`]),
});
try {
await Promise.all(checkedIds.value.map((id) => deleteBarcode(id)));
ElMessage.success($t('ui.actionMessage.deleteSuccess', ['']));
checkedIds.value = [];
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 查看条码 */
function handleView(row: MesWmBarcodeApi.Barcode) {
barcodeDetailRef.value?.open(row);
}
/** 跳转条码配置 */
function handleConfig() {
router.push({ name: 'MesWmBarcodeConfig' });
}
/** 导出表格 */
async function handleExport() {
const data = await exportBarcode(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 getBarcodePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
height: 80,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBarcodeApi.Barcode>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="【仓库】仓库与库区库位、条码赋码、SN码"
url="https://doc.iocoder.cn/mes/wm/warehouse-setup/"
/>
</template>
<FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
<Grid table-title="">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['条码']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['mes:wm-barcode:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'danger',
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode:delete'],
disabled: checkedIds.length === 0,
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
`${checkedIds.length}`,
]),
confirm: handleDeleteBatch,
},
},
{
label: '条码设置',
type: 'primary',
auth: ['mes:wm-barcode-config:query'],
onClick: handleConfig,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['mes:wm-barcode:export'],
onClick: handleExport,
},
]"
/>
</template>
<template #barcode="{ row }">
<div v-if="row.content" class="flex justify-center">
<Barcode
:content="row.content"
:format="row.format"
:height="60"
:width="140"
/>
</div>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'primary',
link: true,
auth: ['mes:wm-barcode:query'],
onClick: handleView.bind(null, row),
},
{
label: $t('common.edit'),
type: 'primary',
link: true,
icon: ACTION_ICON.EDIT,
auth: ['mes:wm-barcode:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mes:wm-barcode:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [
row.bizName || row.bizCode || '',
]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@ -0,0 +1,123 @@
<script lang="ts" setup>
import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import {
createBarcode,
getBarcode,
updateBarcode,
} from '#/api/mes/wm/barcode';
import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
import { $t } from '#/locales';
import { BarcodeBizTypeEnum } from '#/views/mes/utils/constants';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MesWmBarcodeApi.Barcode>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['条码'])
: $t('ui.actionTitle.create', ['条码']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 110,
},
layout: 'horizontal',
schema: [],
showDefaultActions: false,
});
/** 加载级联选择器的数据(编辑时回填上级仓库/库区) */
async function loadCascadeData(data: MesWmBarcodeApi.Barcode) {
if (!data.bizType || !data.bizId) {
return;
}
try {
if (data.bizType === BarcodeBizTypeEnum.LOCATION) {
const location = await getWarehouseLocation(data.bizId);
if (location?.warehouseId) {
await formApi.setFieldValue(
'locationWarehouseId',
location.warehouseId,
);
}
} else if (data.bizType === BarcodeBizTypeEnum.AREA) {
const area = await getWarehouseArea(data.bizId);
await formApi.setValues({
areaWarehouseId: area?.warehouseId,
areaLocationId: area?.locationId,
});
}
} catch (error) {
console.error('加载级联数据失败:', error);
}
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesWmBarcodeApi.Barcode;
try {
await (formData.value?.id
? updateBarcode({ ...data, id: formData.value.id })
: createBarcode(data));
//
await modalApi.close();
emit('success');
ElMessage.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
formApi.setState({ schema: useFormSchema(formApi) });
await formApi.setValues({ status: CommonStatusEnum.ENABLE });
//
const data = modalApi.getData<MesWmBarcodeApi.Barcode>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getBarcode(data.id);
// values
await formApi.setValues(formData.value);
await loadCascadeData(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle" class="w-1/2">
<Form class="mx-4" />
</Modal>
</template>