feat(mes): 完善条码管理迁移并补齐业务选择器

pull/350/head
YunaiV 2026-05-30 00:51:38 +08:00
parent 7ee42b9888
commit 6b6d45132f
53 changed files with 4768 additions and 89 deletions

View File

@ -21,6 +21,8 @@ export namespace MesProWorkOrderApi {
routeName?: string;
clientId?: number;
clientName?: string;
vendorId?: number; // 供应商编号
vendorName?: string; // 供应商名称
planStartTime?: number | string;
planEndTime?: number | string;
actualStartTime?: number | string;

View File

@ -1,3 +1,5 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmItemReceiptApi {
@ -19,16 +21,66 @@ export namespace MesWmItemReceiptApi {
locationName?: string; // 库区名称
areaId?: number; // 库位编号
areaName?: string; // 库位名称
receiptDate?: Date | number | string; // 入库日期
receiptDate?: number; // 入库日期
status?: number; // 状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询采购入库单分页 */
export function getItemReceiptPage(params: PageParam) {
return requestClient.get<PageResult<MesWmItemReceiptApi.ItemReceipt>>(
'/mes/wm/item-receipt/page',
{ params },
);
}
/** 查询采购入库单详情 */
export function getItemReceipt(id: number) {
return requestClient.get<MesWmItemReceiptApi.ItemReceipt>(
`/mes/wm/item-receipt/get?id=${id}`,
);
}
/** 新增采购入库单 */
export function createItemReceipt(data: MesWmItemReceiptApi.ItemReceipt) {
return requestClient.post<number>('/mes/wm/item-receipt/create', data);
}
/** 修改采购入库单 */
export function updateItemReceipt(data: MesWmItemReceiptApi.ItemReceipt) {
return requestClient.put('/mes/wm/item-receipt/update', data);
}
/** 删除采购入库单 */
export function deleteItemReceipt(id: number) {
return requestClient.delete(`/mes/wm/item-receipt/delete?id=${id}`);
}
/** 提交采购入库单 */
export function submitItemReceipt(id: number) {
return requestClient.put(`/mes/wm/item-receipt/submit?id=${id}`);
}
/** 执行上架 */
export function stockItemReceipt(id: number) {
return requestClient.put(`/mes/wm/item-receipt/stock?id=${id}`);
}
/** 执行入库 */
export function finishItemReceipt(id: number) {
return requestClient.put(`/mes/wm/item-receipt/finish?id=${id}`);
}
/** 取消采购入库单 */
export function cancelItemReceipt(id: number) {
return requestClient.put(`/mes/wm/item-receipt/cancel?id=${id}`);
}
/** 导出采购入库单 */
export function exportItemReceipt(params: any) {
return requestClient.download('/mes/wm/item-receipt/export-excel', {
params,
});
}

View File

@ -3,11 +3,12 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmItemReceiptLineApi {
/** MES 物料接收单行 */
/** MES 采购入库单行 */
export interface ItemReceiptLine {
id?: number; // 行编号
receiptId?: number; // 入库单编号
receiptCode?: string; // 入库单编码
arrivalNoticeLineId?: number; // 到货通知单行编号
purchaseOrderCode?: string; // 采购订单号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
@ -15,14 +16,48 @@ export namespace MesWmItemReceiptLineApi {
specification?: string; // 规格型号
unitMeasureName?: string; // 单位
receivedQuantity?: number; // 入库数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
productionDate?: number; // 生产日期
expireDate?: number; // 有效期
lotNumber?: string; // 生产批号
iqcCheckFlag?: boolean; // 是否检验
iqcId?: number; // 来料检验单编号
iqcCode?: string; // 来料检验单编码
remark?: string; // 备注
}
}
/** 查询物料接收单行分页 */
/** 查询采购入库单行分页 */
export function getItemReceiptLinePage(params: PageParam) {
return requestClient.get<PageResult<MesWmItemReceiptLineApi.ItemReceiptLine>>(
'/mes/wm/item-receipt-line/page',
{ params },
);
}
/** 查询采购入库单行详情 */
export function getItemReceiptLine(id: number) {
return requestClient.get<MesWmItemReceiptLineApi.ItemReceiptLine>(
`/mes/wm/item-receipt-line/get?id=${id}`,
);
}
/** 新增采购入库单行 */
export function createItemReceiptLine(
data: MesWmItemReceiptLineApi.ItemReceiptLine,
) {
return requestClient.post<number>('/mes/wm/item-receipt-line/create', data);
}
/** 修改采购入库单行 */
export function updateItemReceiptLine(
data: MesWmItemReceiptLineApi.ItemReceiptLine,
) {
return requestClient.put('/mes/wm/item-receipt-line/update', data);
}
/** 删除采购入库单行 */
export function deleteItemReceiptLine(id: number) {
return requestClient.delete(`/mes/wm/item-receipt-line/delete?id=${id}`);
}

View File

@ -0,0 +1,60 @@
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueDetailApi {
/** MES 外协发料单明细 */
export interface OutsourceIssueDetail {
id?: number; // 明细编号
lineId?: number; // 行编号
issueId?: number; // 发料单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 数量
materialStockId?: number; // 库存编号
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
warehouseId?: number; // 仓库编号
warehouseName?: string; // 仓库名称
locationId?: number; // 库区编号
locationName?: string; // 库区名称
areaId?: number; // 库位编号
areaName?: string; // 库位名称
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单明细列表 */
export function getOutsourceIssueDetailListByLineId(lineId: number) {
return requestClient.get<
MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]
>('/mes/wm/outsource-issue-detail/list-by-line', { params: { lineId } });
}
/** 查询外协发料单明细详情 */
export function getOutsourceIssueDetail(id: number) {
return requestClient.get<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>(
`/mes/wm/outsource-issue-detail/get?id=${id}`,
);
}
/** 新增外协发料单明细 */
export function createOutsourceIssueDetail(
data: MesWmOutsourceIssueDetailApi.OutsourceIssueDetail,
) {
return requestClient.post('/mes/wm/outsource-issue-detail/create', data);
}
/** 修改外协发料单明细 */
export function updateOutsourceIssueDetail(
data: MesWmOutsourceIssueDetailApi.OutsourceIssueDetail,
) {
return requestClient.put('/mes/wm/outsource-issue-detail/update', data);
}
/** 删除外协发料单明细 */
export function deleteOutsourceIssueDetail(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue-detail/delete?id=${id}`);
}

View File

@ -0,0 +1,90 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueApi {
/** MES 外协发料单 */
export interface OutsourceIssue {
id?: number; // 发料单编号
code?: string; // 发料单编号
name?: string; // 发料单名称
vendorId?: number; // 供应商编号
vendorCode?: string; // 供应商编码
vendorName?: string; // 供应商名称
workOrderId?: number; // 生产工单编号
workOrderCode?: string; // 生产工单编码
workOrderName?: string; // 生产工单名称
issueDate?: number; // 发料日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单分页 */
export function getOutsourceIssuePage(params: PageParam) {
return requestClient.get<PageResult<MesWmOutsourceIssueApi.OutsourceIssue>>(
'/mes/wm/outsource-issue/page',
{ params },
);
}
/** 查询外协发料单详情 */
export function getOutsourceIssue(id: number) {
return requestClient.get<MesWmOutsourceIssueApi.OutsourceIssue>(
`/mes/wm/outsource-issue/get?id=${id}`,
);
}
/** 新增外协发料单 */
export function createOutsourceIssue(
data: MesWmOutsourceIssueApi.OutsourceIssue,
) {
return requestClient.post<number>('/mes/wm/outsource-issue/create', data);
}
/** 修改外协发料单 */
export function updateOutsourceIssue(
data: MesWmOutsourceIssueApi.OutsourceIssue,
) {
return requestClient.put('/mes/wm/outsource-issue/update', data);
}
/** 删除外协发料单 */
export function deleteOutsourceIssue(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue/delete?id=${id}`);
}
/** 提交外协发料单 */
export function submitOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/submit?id=${id}`);
}
/** 执行拣货 */
export function stockOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/stock?id=${id}`);
}
/** 执行领出 */
export function finishOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/finish?id=${id}`);
}
/** 取消外协发料单 */
export function cancelOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/cancel?id=${id}`);
}
/** 校验外协发料单拣货数量是否与发料数量一致 */
export function checkOutsourceIssueQuantity(id: number) {
return requestClient.get<boolean>(
`/mes/wm/outsource-issue/check-quantity?id=${id}`,
);
}
/** 导出外协发料单 */
export function exportOutsourceIssue(params: any) {
return requestClient.download('/mes/wm/outsource-issue/export-excel', {
params,
});
}

View File

@ -0,0 +1,55 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueLineApi {
/** MES 外协发料单行 */
export interface OutsourceIssueLine {
id?: number; // 行编号
issueId?: number; // 发料单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 发料数量
materialStockId?: number; // 库存编号
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单行分页 */
export function getOutsourceIssueLinePage(params: PageParam) {
return requestClient.get<
PageResult<MesWmOutsourceIssueLineApi.OutsourceIssueLine>
>('/mes/wm/outsource-issue-line/page', { params });
}
/** 查询外协发料单行详情 */
export function getOutsourceIssueLine(id: number) {
return requestClient.get<MesWmOutsourceIssueLineApi.OutsourceIssueLine>(
`/mes/wm/outsource-issue-line/get?id=${id}`,
);
}
/** 新增外协发料单行 */
export function createOutsourceIssueLine(
data: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
) {
return requestClient.post('/mes/wm/outsource-issue-line/create', data);
}
/** 修改外协发料单行 */
export function updateOutsourceIssueLine(
data: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
) {
return requestClient.put('/mes/wm/outsource-issue-line/update', data);
}
/** 删除外协发料单行 */
export function deleteOutsourceIssueLine(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue-line/delete?id=${id}`);
}

View File

@ -0,0 +1,61 @@
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptDetailApi {
/** MES 外协入库单明细 */
export interface OutsourceReceiptDetail {
id?: number; // 明细编号
lineId?: number; // 行编号
receiptId?: number; // 入库单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 上架数量
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
warehouseId?: number; // 仓库编号
warehouseName?: string; // 仓库名称
locationId?: number; // 库区编号
locationName?: string; // 库区名称
areaId?: number; // 库位编号
areaName?: string; // 库位名称
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单明细列表 */
export function getOutsourceReceiptDetailListByLineId(lineId: number) {
return requestClient.get<
MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]
>('/mes/wm/outsource-receipt-detail/list-by-line', { params: { lineId } });
}
/** 查询外协入库单明细详情 */
export function getOutsourceReceiptDetail(id: number) {
return requestClient.get<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>(
`/mes/wm/outsource-receipt-detail/get?id=${id}`,
);
}
/** 新增外协入库单明细 */
export function createOutsourceReceiptDetail(
data: MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail,
) {
return requestClient.post('/mes/wm/outsource-receipt-detail/create', data);
}
/** 修改外协入库单明细 */
export function updateOutsourceReceiptDetail(
data: MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail,
) {
return requestClient.put('/mes/wm/outsource-receipt-detail/update', data);
}
/** 删除外协入库单明细 */
export function deleteOutsourceReceiptDetail(id: number) {
return requestClient.delete(
`/mes/wm/outsource-receipt-detail/delete?id=${id}`,
);
}

View File

@ -0,0 +1,81 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptApi {
/** MES 外协入库单 */
export interface OutsourceReceipt {
id?: number; // 入库单编号
code?: string; // 入库单编码
name?: string; // 入库单名称
workOrderId?: number; // 外协工单编号
workOrderCode?: string; // 外协工单编码
vendorId?: number; // 供应商编号
vendorName?: string; // 供应商名称
receiptDate?: number; // 入库日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单分页 */
export function getOutsourceReceiptPage(params: PageParam) {
return requestClient.get<PageResult<MesWmOutsourceReceiptApi.OutsourceReceipt>>(
'/mes/wm/outsource-receipt/page',
{ params },
);
}
/** 查询外协入库单详情 */
export function getOutsourceReceipt(id: number) {
return requestClient.get<MesWmOutsourceReceiptApi.OutsourceReceipt>(
`/mes/wm/outsource-receipt/get?id=${id}`,
);
}
/** 新增外协入库单 */
export function createOutsourceReceipt(
data: MesWmOutsourceReceiptApi.OutsourceReceipt,
) {
return requestClient.post<number>('/mes/wm/outsource-receipt/create', data);
}
/** 修改外协入库单 */
export function updateOutsourceReceipt(
data: MesWmOutsourceReceiptApi.OutsourceReceipt,
) {
return requestClient.put('/mes/wm/outsource-receipt/update', data);
}
/** 删除外协入库单 */
export function deleteOutsourceReceipt(id: number) {
return requestClient.delete(`/mes/wm/outsource-receipt/delete?id=${id}`);
}
/** 提交外协入库单 */
export function submitOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/submit?id=${id}`);
}
/** 执行上架 */
export function stockOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/stock?id=${id}`);
}
/** 完成入库 */
export function finishOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/finish?id=${id}`);
}
/** 取消外协入库单 */
export function cancelOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/cancel?id=${id}`);
}
/** 导出外协入库单 */
export function exportOutsourceReceipt(params: any) {
return requestClient.download('/mes/wm/outsource-receipt/export-excel', {
params,
});
}

View File

@ -0,0 +1,58 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptLineApi {
/** MES 外协入库单行 */
export interface OutsourceReceiptLine {
id?: number; // 行编号
receiptId?: number; // 入库单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 入库数量
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
productionDate?: number; // 生产日期
expireDate?: number; // 有效期
lotNumber?: string; // 生产批号
iqcCheckFlag?: boolean; // 是否需要质检
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单行分页 */
export function getOutsourceReceiptLinePage(params: PageParam) {
return requestClient.get<
PageResult<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>
>('/mes/wm/outsource-receipt-line/page', { params });
}
/** 查询外协入库单行详情 */
export function getOutsourceReceiptLine(id: number) {
return requestClient.get<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>(
`/mes/wm/outsource-receipt-line/get?id=${id}`,
);
}
/** 新增外协入库单行 */
export function createOutsourceReceiptLine(
data: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
) {
return requestClient.post('/mes/wm/outsource-receipt-line/create', data);
}
/** 修改外协入库单行 */
export function updateOutsourceReceiptLine(
data: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
) {
return requestClient.put('/mes/wm/outsource-receipt-line/update', data);
}
/** 删除外协入库单行 */
export function deleteOutsourceReceiptLine(id: number) {
return requestClient.delete(`/mes/wm/outsource-receipt-line/delete?id=${id}`);
}

View File

@ -5,12 +5,24 @@ import { requestClient } from '#/api/request';
export namespace MesWmProductSalesApi {
/** MES 销售出库单 */
export interface ProductSales {
id?: number; // 销售出库单编号
id?: number; // 出库单编号
code?: string; // 出库单编号
name?: string; // 出库单名称
noticeId?: number; // 发货通知单编号
noticeCode?: string; // 发货通知单编码
clientId?: number; // 客户编号
clientCode?: string; // 客户编码
clientName?: string; // 客户名称
salesOrderCode?: string; // 销售订单编号
salesDate?: Date; // 出库日期
salesDate?: number; // 出库日期
contactName?: string; // 收货人
contactTelephone?: string; // 联系方式
contactAddress?: string; // 收货地址
carrier?: string; // 承运商
shippingNumber?: string; // 运输单号
status?: number; // 单据状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
@ -21,3 +33,64 @@ export function getProductSalesPage(params: PageParam) {
{ params },
);
}
/** 查询销售出库单详情 */
export function getProductSales(id: number) {
return requestClient.get<MesWmProductSalesApi.ProductSales>(
`/mes/wm/product-sales/get?id=${id}`,
);
}
/** 新增销售出库单 */
export function createProductSales(data: MesWmProductSalesApi.ProductSales) {
return requestClient.post<number>('/mes/wm/product-sales/create', data);
}
/** 修改销售出库单 */
export function updateProductSales(data: MesWmProductSalesApi.ProductSales) {
return requestClient.put('/mes/wm/product-sales/update', data);
}
/** 删除销售出库单 */
export function deleteProductSales(id: number) {
return requestClient.delete(`/mes/wm/product-sales/delete?id=${id}`);
}
/** 提交销售出库单 */
export function submitProductSales(id: number) {
return requestClient.put(`/mes/wm/product-sales/submit?id=${id}`);
}
/** 执行拣货 */
export function stockProductSales(id: number) {
return requestClient.put(`/mes/wm/product-sales/stock?id=${id}`);
}
/** 填写运单 */
export function shippingProductSales(data: MesWmProductSalesApi.ProductSales) {
return requestClient.put('/mes/wm/product-sales/shipping', data);
}
/** 执行出库 */
export function finishProductSales(id: number) {
return requestClient.put(`/mes/wm/product-sales/finish?id=${id}`);
}
/** 取消销售出库单 */
export function cancelProductSales(id: number) {
return requestClient.put(`/mes/wm/product-sales/cancel?id=${id}`);
}
/** 校验销售出库单拣货数量是否与出库数量一致 */
export function checkProductSalesQuantity(id: number) {
return requestClient.get<boolean>(
`/mes/wm/product-sales/check-quantity?id=${id}`,
);
}
/** 导出销售出库单 */
export function exportProductSales(params: any) {
return requestClient.download('/mes/wm/product-sales/export-excel', {
params,
});
}

View File

@ -6,13 +6,19 @@ export namespace MesWmProductSalesLineApi {
/** MES 销售出库单行 */
export interface ProductSalesLine {
id?: number; // 行编号
salesId?: number; // 出库单编号
noticeLineId?: number; // 发货通知单行编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 单位
quantity?: number; // 出库数量
pickedQuantity?: number; // 已拣货数量
batchId?: number; // 批次编号
batchCode?: string; // 批次号
oqcCheckFlag?: boolean; // 是否检验
remark?: string; // 备注
}
}
@ -22,3 +28,29 @@ export function getProductSalesLinePage(params: PageParam) {
PageResult<MesWmProductSalesLineApi.ProductSalesLine>
>('/mes/wm/product-sales-line/page', { params });
}
/** 查询销售出库单行详情 */
export function getProductSalesLine(id: number) {
return requestClient.get<MesWmProductSalesLineApi.ProductSalesLine>(
`/mes/wm/product-sales-line/get?id=${id}`,
);
}
/** 新增销售出库单行 */
export function createProductSalesLine(
data: MesWmProductSalesLineApi.ProductSalesLine,
) {
return requestClient.post<number>('/mes/wm/product-sales-line/create', data);
}
/** 修改销售出库单行 */
export function updateProductSalesLine(
data: MesWmProductSalesLineApi.ProductSalesLine,
) {
return requestClient.put('/mes/wm/product-sales-line/update', data);
}
/** 删除销售出库单行 */
export function deleteProductSalesLine(id: number) {
return requestClient.delete(`/mes/wm/product-sales-line/delete?id=${id}`);
}

View File

@ -0,0 +1,2 @@
export { default as ProCardSelectDialog } from './pro-card-select-dialog.vue';
export { default as ProCardSelect } from './pro-card-select.vue';

View File

@ -0,0 +1,198 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
import { nextTick, ref } from 'vue';
import { Button, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getCardPage } from '#/api/mes/pro/card';
import { useCardSelectGridColumns, useCardSelectGridFormSchema } from '../data';
const emit = defineEmits<{
selected: [rows: MesProCardApi.Card[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<MesProCardApi.Card[]>([]); //
const preSelectedIds = ref<number[]>([]); //
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, MesProCardApi.Card>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as MesProCardApi.Card[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: MesProCardApi.Card) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: MesProCardApi.Card) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: MesProCardApi.Card }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选流转卡 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesProCardApi.Card[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useCardSelectGridFormSchema(),
},
gridOptions: {
columns: useCardSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getCardPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesProCardApi.Card>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: MesProCardApi.Card }) => {
handleRadioChange(row);
},
},
});
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
}
/** 打开流转卡选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: { multiple?: boolean },
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
await nextTick();
gridApi.setGridOptions({
columns: useCardSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭流转卡选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择流转卡 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<Modal
v-model:open="open"
title="流转卡选择"
width="70%"
:destroy-on-close="true"
@ok="handleConfirm"
@cancel="closeModal"
>
<Grid table-title="" />
<template #footer>
<Button @click="closeModal"></Button>
<Button type="primary" @click="handleConfirm"></Button>
</template>
</Modal>
</template>

View File

@ -0,0 +1,133 @@
<script lang="ts" setup>
import type { MesProCardApi } from '#/api/mes/pro/card';
import { computed, ref, useAttrs, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Input, Tooltip } from 'ant-design-vue';
import { getCard } from '#/api/mes/pro/card';
import ProCardSelectDialog from './pro-card-select-dialog.vue';
defineOptions({ name: 'ProCardSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
allowClear?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
allowClear: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择流转卡',
},
);
const emit = defineEmits<{
change: [item: MesProCardApi.Card | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof ProCardSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<MesProCardApi.Card>();
const displayLabel = computed(() => selectedItem.value?.code ?? '');
const showClear = computed(
() =>
props.allowClear &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询流转卡信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getCard(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选流转卡 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开流转卡选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.ant-input-suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, { multiple: false });
}
/** 弹窗选中回调 */
function handleSelected(rows: MesProCardApi.Card[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
<template #title>
<div v-if="selectedItem" class="leading-6">
<div>编号{{ selectedItem.code || '-' }}</div>
<div>工单{{ selectedItem.workOrderCode || '-' }}</div>
<div>批次{{ selectedItem.batchCode || '-' }}</div>
<div>产品{{ selectedItem.itemName || '-' }}</div>
</div>
</template>
<Input
:disabled="disabled"
:placeholder="placeholder"
:value="displayLabel"
readonly
>
<template #suffix>
<IconifyIcon
class="size-4"
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
/>
</template>
</Input>
</Tooltip>
</div>
<ProCardSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -0,0 +1,100 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
import { markRaw } from 'vue';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
/** 流转卡选择弹窗的搜索表单 */
export function useCardSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '流转卡编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入流转卡编号',
},
},
{
fieldName: 'workOrderId',
label: '生产工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
placeholder: '请选择生产工单',
},
},
{
fieldName: 'itemId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品物料',
},
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入批次号',
},
},
];
}
/** 流转卡选择弹窗的字段 */
export function useCardSelectGridColumns(
multiple = false,
): VxeTableGridOptions<MesProCardApi.Card>['columns'] {
return [
{
type: multiple ? 'checkbox' : 'radio',
width: 50,
},
{
field: 'code',
title: '流转卡编号',
width: 160,
},
{
field: 'workOrderCode',
title: '生产工单编号',
width: 160,
},
{
field: 'itemCode',
title: '产品物料编码',
width: 140,
},
{
field: 'batchCode',
title: '批次号',
width: 120,
},
{
field: 'itemName',
title: '产品物料名称',
minWidth: 150,
},
{
field: 'specification',
title: '规格型号',
width: 120,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'transferedQuantity',
title: '流转数量',
width: 100,
},
];
}

View File

@ -151,11 +151,22 @@ export const MesAutoCodeRuleCode = {
TM_TOOL_CODE: 'TM_TOOL_CODE',
WM_AREA_CODE: 'WM_AREA_CODE',
WM_LOCATION_CODE: 'WM_LOCATION_CODE',
WM_ARRIVAL_NOTICE_CODE: 'WM_ARRIVAL_NOTICE_CODE',
WM_ITEM_RECEIPT_CODE: 'WM_ITEM_RECEIPT_CODE',
WM_RETURN_VENDOR_CODE: 'WM_RETURN_VENDOR_CODE',
WM_SALES_NOTICE_CODE: 'WM_SALES_NOTICE_CODE',
WM_RETURN_SALES_CODE: 'WM_RETURN_SALES_CODE',
WM_RETURN_ISSUE_CODE: 'WM_RETURN_ISSUE_CODE',
WM_PRODUCT_ISSUE_CODE: 'WM_PRODUCT_ISSUE_CODE',
WM_PRODUCT_SALES_CODE: 'WM_PRODUCT_SALES_CODE',
PRODUCTRECPT_CODE: 'PRODUCTRECPT_CODE',
WM_MISC_ISSUE_CODE: 'WM_MISC_ISSUE_CODE',
WM_MISC_RECEIPT_CODE: 'WM_MISC_RECEIPT_CODE',
WM_OUTSOURCE_ISSUE_CODE: 'WM_OUTSOURCE_ISSUE_CODE',
WM_OUTSOURCE_RECEIPT_CODE: 'WM_OUTSOURCE_RECEIPT_CODE',
WM_PACKAGE_CODE: 'WM_PACKAGE_CODE',
WM_STOCK_TAKING_CODE: 'WM_STOCK_TAKING_CODE',
WM_STOCK_TAKING_PLAN_CODE: 'WM_STOCK_TAKING_PLAN_CODE',
WM_WAREHOUSE_CODE: 'WM_WAREHOUSE_CODE',
} as const;
@ -165,6 +176,38 @@ export const MesWmPackageStatusEnum = {
FINISHED: MesOrderStatusConstants.FINISHED,
} as const;
/** MES 盘点类型枚举 */
export const MesWmStockTakingTypeEnum = {
STATIC: 1,
DYNAMIC: 2,
} as const;
/** MES 盘点任务状态枚举 */
export const MesWmStockTakingTaskStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 盘点任务行状态枚举 */
export const MesWmStockTakingTaskLineStatusEnum = {
UNCOUNTED: 0,
NORMAL: 1,
GAIN: 2,
LOSS: 3,
} as const;
/** MES 盘点方案参数类型枚举 */
export const MesWmStockTakingParamTypeEnum = {
WAREHOUSE: 102,
LOCATION: 103,
AREA: 104,
BATCH: 107,
ITEM: 600,
QUALITY_STATUS: 900,
} as const;
/** MES 生产工单状态枚举 */
export const MesProWorkOrderStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
@ -263,6 +306,87 @@ export const MesWmOutsourceReceiptStatusEnum = {
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 到货通知单状态枚举 */
export const MesWmArrivalNoticeStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
PENDING_QC: MesOrderStatusConstants.APPROVING,
PENDING_RECEIPT: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
} as const;
/** MES 采购入库单状态枚举 */
export const MesWmItemReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 供应商退货单状态枚举 */
export const MesWmReturnVendorStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 发货通知单状态枚举 */
export const MesWmSalesNoticeStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVED: MesOrderStatusConstants.APPROVED,
} as const;
/** MES 销售退货单状态枚举 */
export const MesWmReturnSalesStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 生产退料单状态枚举 */
export const MesWmReturnIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 领料出库单状态枚举 */
export const MesWmProductIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 产品入库单状态枚举 */
export const MesWmProductReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 销售出库单状态枚举 */
export const MesWmProductSalesStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
SHIPPING: 10, // 待填写运单
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 质检结果值类型枚举 */
export const MesQcResultValueType = {
FLOAT: 1,

View File

@ -19,9 +19,12 @@ 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 { ProCardSelect } from '#/views/mes/pro/card/components';
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 { WmBatchSelect } from '#/views/mes/wm/batch/components';
import { UserSelect } from '#/views/system/user/components';
import WmMaterialStockSelect from './../materialstock/components/wm-material-stock-select.vue';
import { WmPackageSelect } from './../packages/components';
@ -51,15 +54,31 @@ async function syncBizDetail(
}
let bizCode: string | undefined;
let bizName: string | undefined;
if (bizType === BarcodeBizTypeEnum.STOCK) {
bizCode = item.itemCode;
bizName = item.itemName;
} else if (bizType === BarcodeBizTypeEnum.PACKAGE) {
bizCode = item.code;
bizName = item.clientName || item.code;
} else {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
switch (bizType) {
case BarcodeBizTypeEnum.BATCH: {
bizCode = item.code;
bizName = item.itemName || item.code;
break;
}
case BarcodeBizTypeEnum.PACKAGE: {
bizCode = item.code;
bizName = item.clientName || item.code;
break;
}
case BarcodeBizTypeEnum.PROCARD: {
bizCode = item.code;
bizName = item.workOrderName || item.code;
break;
}
case BarcodeBizTypeEnum.STOCK: {
bizCode = item.itemCode;
bizName = item.itemName;
break;
}
default: {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
}
}
// 先回填业务编码、名称并清空旧条码内容
await formApi.setValues({ bizCode, bizName, content: undefined });
@ -328,6 +347,45 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '批次',
component: markRaw(WmBatchSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.BATCH,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '流转卡',
component: markRaw(ProCardSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.PROCARD,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '人员',
component: markRaw(UserSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.USER,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '业务编号',
@ -343,13 +401,16 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
values.bizType !== undefined &&
![
BarcodeBizTypeEnum.AREA,
BarcodeBizTypeEnum.BATCH,
BarcodeBizTypeEnum.CLIENT,
BarcodeBizTypeEnum.ITEM,
BarcodeBizTypeEnum.LOCATION,
BarcodeBizTypeEnum.MACHINERY,
BarcodeBizTypeEnum.PACKAGE,
BarcodeBizTypeEnum.PROCARD,
BarcodeBizTypeEnum.STOCK,
BarcodeBizTypeEnum.TOOL,
BarcodeBizTypeEnum.USER,
BarcodeBizTypeEnum.VENDOR,
BarcodeBizTypeEnum.WAREHOUSE,
BarcodeBizTypeEnum.WORKORDER,

View File

@ -0,0 +1,2 @@
export { default as WmBatchSelectDialog } from './wm-batch-select-dialog.vue';
export { default as WmBatchSelect } from './wm-batch-select.vue';

View File

@ -0,0 +1,248 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
import { computed, nextTick, ref } from 'vue';
import { Alert, Button, message, Modal } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getBatchPage } from '#/api/mes/wm/batch';
import { useBatchSelectGridColumns, useBatchSelectGridFormSchema } from '../data';
const emit = defineEmits<{
selected: [rows: MesWmBatchApi.Batch[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<MesWmBatchApi.Batch[]>([]); //
const preSelectedIds = ref<number[]>([]); //
const externalItemId = ref<number>(); //
const externalClientId = ref<number>(); //
const externalVendorId = ref<number>(); //
const externalSalesOrderCode = ref<string>(); //
const filterTip = computed(() => {
const parts: string[] = [];
if (externalClientId.value != null) {
parts.push('客户');
}
if (externalVendorId.value != null) {
parts.push('供应商');
}
if (externalSalesOrderCode.value != null) {
parts.push('销售订单');
}
return parts.length > 0 ? `已按${parts.join('/')}预过滤` : '';
});
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, MesWmBatchApi.Batch>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as MesWmBatchApi.Batch[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: MesWmBatchApi.Batch) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: MesWmBatchApi.Batch) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: MesWmBatchApi.Batch }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选批次 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesWmBatchApi.Batch[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useBatchSelectGridFormSchema(),
},
gridOptions: {
columns: useBatchSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getBatchPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBatchApi.Batch>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: MesWmBatchApi.Batch }) => {
handleRadioChange(row);
},
},
});
/** 重置查询和选择状态,保留外部传入的默认过滤 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
if (externalItemId.value) {
await gridApi.formApi.setFieldValue('itemId', externalItemId.value);
}
if (externalClientId.value) {
await gridApi.formApi.setFieldValue('clientId', externalClientId.value);
}
if (externalVendorId.value) {
await gridApi.formApi.setFieldValue('vendorId', externalVendorId.value);
}
if (externalSalesOrderCode.value) {
await gridApi.formApi.setFieldValue(
'salesOrderCode',
externalSalesOrderCode.value,
);
}
}
/** 打开批次选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: {
clientId?: number;
itemId?: number;
multiple?: boolean;
salesOrderCode?: string;
vendorId?: number;
},
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
externalItemId.value = options?.itemId;
externalClientId.value = options?.clientId;
externalVendorId.value = options?.vendorId;
externalSalesOrderCode.value = options?.salesOrderCode;
await nextTick();
gridApi.setGridOptions({
columns: useBatchSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭批次选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择批次 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<Modal
v-model:open="open"
title="批次选择"
width="80%"
:destroy-on-close="true"
@ok="handleConfirm"
@cancel="closeModal"
>
<Alert
v-if="filterTip"
:message="filterTip"
type="info"
show-icon
class="!mb-3"
/>
<Grid table-title="" />
<template #footer>
<Button @click="closeModal"></Button>
<Button type="primary" @click="handleConfirm"></Button>
</template>
</Modal>
</template>

View File

@ -0,0 +1,147 @@
<script lang="ts" setup>
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
import { computed, ref, useAttrs, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Input, Tooltip } from 'ant-design-vue';
import { getBatch } from '#/api/mes/wm/batch';
import WmBatchSelectDialog from './wm-batch-select-dialog.vue';
defineOptions({ name: 'WmBatchSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
allowClear?: boolean;
clientId?: number; // ID
disabled?: boolean;
itemId?: number; // ID
modelValue?: number;
placeholder?: string;
salesOrderCode?: string; //
vendorId?: number; // ID
}>(),
{
allowClear: true,
clientId: undefined,
disabled: false,
itemId: undefined,
modelValue: undefined,
placeholder: '请选择批次',
salesOrderCode: undefined,
vendorId: undefined,
},
);
const emit = defineEmits<{
change: [item: MesWmBatchApi.Batch | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof WmBatchSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<MesWmBatchApi.Batch>();
const displayLabel = computed(() => selectedItem.value?.code ?? '');
const showClear = computed(
() =>
props.allowClear &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询批次信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getBatch(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选批次 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开批次选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.ant-input-suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, {
clientId: props.clientId,
itemId: props.itemId,
multiple: false,
salesOrderCode: props.salesOrderCode,
vendorId: props.vendorId,
});
}
/** 弹窗选中回调 */
function handleSelected(rows: MesWmBatchApi.Batch[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
<template #title>
<div v-if="selectedItem" class="leading-6">
<div>批次编号{{ selectedItem.code || '-' }}</div>
<div>物料编码{{ selectedItem.itemCode || '-' }}</div>
<div>物料名称{{ selectedItem.itemName || '-' }}</div>
<div>生产批号{{ selectedItem.lotNumber || '-' }}</div>
</div>
</template>
<Input
:disabled="disabled"
:placeholder="placeholder"
:value="displayLabel"
readonly
>
<template #suffix>
<IconifyIcon
class="size-4"
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
/>
</template>
</Input>
</Tooltip>
</div>
<WmBatchSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -0,0 +1,234 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
import { markRaw } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
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 { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import { TmToolSelect } from '#/views/mes/tm/tool/components';
/** 批次选择弹窗的搜索表单 */
export function useBatchSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '批次编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入批次编号',
},
},
{
fieldName: 'itemId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品物料',
},
},
{
fieldName: 'vendorId',
label: '供应商',
component: markRaw(MdVendorSelect),
componentProps: {
placeholder: '请选择供应商',
},
},
{
fieldName: 'clientId',
label: '客户',
component: markRaw(MdClientSelect),
componentProps: {
placeholder: '请选择客户',
},
},
{
fieldName: 'workOrderId',
label: '生产工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
placeholder: '请选择生产工单',
},
},
{
fieldName: 'workstationId',
label: '工作站',
component: markRaw(MdWorkstationSelect),
componentProps: {
placeholder: '请选择工作站',
},
},
{
fieldName: 'toolId',
label: '工具',
component: markRaw(TmToolSelect),
componentProps: {
placeholder: '请选择工具',
},
},
{
fieldName: 'salesOrderCode',
label: '销售订单编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入销售订单编号',
},
},
{
fieldName: 'purchaseOrderCode',
label: '采购订单编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入采购订单编号',
},
},
{
fieldName: 'lotNumber',
label: '生产批号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入生产批号',
},
},
{
fieldName: 'qualityStatus',
label: '质量状态',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.MES_WM_QUALITY_STATUS, 'number'),
placeholder: '请选择质量状态',
},
},
];
}
/** 批次选择弹窗的字段 */
export function useBatchSelectGridColumns(
multiple = false,
): VxeTableGridOptions<MesWmBatchApi.Batch>['columns'] {
return [
{
type: multiple ? 'checkbox' : 'radio',
width: 50,
},
{
field: 'code',
title: '批次编号',
width: 150,
},
{
field: 'itemCode',
title: '物料编码',
width: 150,
},
{
field: 'itemName',
title: '物料名称',
minWidth: 140,
},
{
field: 'itemSpecification',
title: '规格型号',
width: 120,
},
{
field: 'unitName',
title: '单位',
width: 80,
},
{
field: 'vendorCode',
title: '供应商编码',
width: 120,
},
{
field: 'vendorName',
title: '供应商名称',
width: 120,
},
{
field: 'clientCode',
title: '客户编码',
width: 110,
},
{
field: 'clientName',
title: '客户名称',
width: 110,
},
{
field: 'salesOrderCode',
title: '销售订单编号',
width: 140,
},
{
field: 'purchaseOrderCode',
title: '采购订单编号',
width: 140,
},
{
field: 'workOrderCode',
title: '工单编码',
width: 140,
},
{
field: 'workstationCode',
title: '工作站编码',
width: 120,
},
{
field: 'taskCode',
title: '生产任务编号',
width: 140,
},
{
field: 'toolCode',
title: '工具编号',
width: 120,
},
{
field: 'lotNumber',
title: '生产批号',
width: 120,
},
{
field: 'qualityStatus',
title: '质量状态',
width: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
},
},
{
field: 'produceDate',
title: '生产日期',
width: 120,
formatter: 'formatDate',
},
{
field: 'expireDate',
title: '有效期',
width: 120,
formatter: 'formatDate',
},
{
field: 'receiptDate',
title: '入库日期',
width: 120,
formatter: 'formatDate',
},
];
}

View File

@ -0,0 +1,488 @@
import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
import { h, markRaw } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { Button } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getRangePickerDefaultProps } from '#/utils';
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 ProWorkOrderSelect from '#/views/mes/pro/workorder/components/pro-work-order-select.vue';
import {
MesAutoCodeRuleCode,
MesProWorkOrderStatusEnum,
MesProWorkOrderTypeEnum,
} from '#/views/mes/utils/constants';
import { WmMaterialStockSelect } from '#/views/mes/wm/materialstock/components';
import {
WmWarehouseAreaSelect,
WmWarehouseLocationSelect,
WmWarehouseSelect,
} from '#/views/mes/wm/warehouse/components';
/** 表单类型 */
export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
/** 表单头部是否只读(拣货、详情、领出态) */
function isHeaderReadonly(formType: FormType): boolean {
return (
formType === 'detail' || formType === 'finish' || formType === 'stock'
);
}
/** 新增/修改的表单 */
export function useFormSchema(
formType: FormType,
formApi?: VbenFormApi,
): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'status',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'code',
label: '发料单编号',
component: 'Input',
componentProps: {
placeholder: '请输入发料单编号',
},
rules: 'required',
suffix: isHeaderReadonly(formType)
? undefined
: () =>
h(
Button,
{
type: 'default',
onClick: async () => {
const code = await generateAutoCode(
MesAutoCodeRuleCode.WM_OUTSOURCE_ISSUE_CODE,
);
await formApi?.setFieldValue('code', code);
},
},
{ default: () => '生成' },
),
},
{
fieldName: 'name',
label: '发料单名称',
component: 'Input',
componentProps: {
placeholder: '请输入发料单名称',
},
rules: 'required',
},
{
fieldName: 'issueDate',
label: '发料日期',
component: 'DatePicker',
componentProps: {
format: 'YYYY-MM-DD',
placeholder: '请选择发料日期',
valueFormat: 'x',
},
},
{
fieldName: 'workOrderId',
label: '外协工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
// 选择外协工单后,自动回填供应商
onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
},
status: MesProWorkOrderStatusEnum.CONFIRMED,
type: MesProWorkOrderTypeEnum.OUTSOURCE,
},
rules: 'selectRequired',
},
{
fieldName: 'vendorId',
label: '供应商',
component: markRaw(MdVendorSelect),
componentProps: {
placeholder: '请选择供应商',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '发料单编号',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入发料单编号',
},
},
{
fieldName: 'name',
label: '发料单名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入发料单名称',
},
},
{
fieldName: 'vendorId',
label: '供应商',
component: markRaw(MdVendorSelect),
componentProps: {
placeholder: '请选择供应商',
},
},
{
fieldName: 'issueDate',
label: '发料日期',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
allowClear: true,
},
},
];
}
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions<MesWmOutsourceIssueApi.OutsourceIssue>['columns'] {
return [
{
field: 'code',
title: '发料单编号',
minWidth: 160,
slots: { default: 'code' },
},
{
field: 'name',
title: '发料单名称',
minWidth: 150,
},
{
field: 'workOrderCode',
title: '生产工单号',
minWidth: 140,
},
{
field: 'vendorName',
title: '供应商名称',
minWidth: 120,
},
{
field: 'issueDate',
title: '发料日期',
width: 180,
formatter: 'formatDate',
},
{
field: 'status',
title: '单据状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.MES_WM_OUTSOURCE_ISSUE_STATUS },
},
},
{
title: '操作',
width: 240,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
/** 发料单行子表的字段 */
export function useLineGridColumns(
editable: boolean,
stockable: boolean,
): VxeTableGridOptions<MesWmOutsourceIssueLineApi.OutsourceIssueLine>['columns'] {
return [
{
type: 'expand',
width: 48,
slots: { content: 'detail' },
},
{
field: 'itemCode',
title: '物料编码',
minWidth: 120,
},
{
field: 'itemName',
title: '物料名称',
minWidth: 140,
},
{
field: 'specification',
title: '规格型号',
minWidth: 120,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'quantity',
title: '领料数量',
width: 100,
},
{
field: 'batchCode',
title: '批次号',
minWidth: 120,
},
{
field: 'remark',
title: '备注',
minWidth: 150,
},
...(editable || stockable
? [
{
title: '操作',
width: 160,
fixed: 'right',
slots: { default: 'actions' },
} as const,
]
: []),
];
}
/** 发料单行新增/修改的表单 */
export function useLineFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'itemId',
label: '物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择物料',
},
rules: 'selectRequired',
},
{
fieldName: 'quantity',
label: '发料数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
placeholder: '请输入发料数量',
precision: 2,
},
rules: 'required',
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
placeholder: '请输入批次号',
},
},
{
fieldName: 'remark',
label: '备注',
component: 'Textarea',
formItemClass: 'col-span-3',
componentProps: {
placeholder: '请输入备注',
rows: 3,
},
},
];
}
/** 发料明细子表的字段 */
export function useDetailGridColumns(
stockable: boolean,
): VxeTableGridOptions<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>['columns'] {
return [
{
field: 'warehouseName',
title: '仓库名称',
minWidth: 100,
},
{
field: 'locationName',
title: '库区名称',
minWidth: 100,
},
{
field: 'areaName',
title: '库位名称',
minWidth: 100,
},
{
field: 'quantity',
title: '数量',
width: 100,
},
...(stockable
? [
{
title: '操作',
width: 120,
fixed: 'right',
slots: { default: 'actions' },
} as const,
]
: []),
];
}
/** 发料明细新增/修改的表单 */
export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
return [
{
fieldName: 'quantityMax',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'materialStockId',
label: '库存记录',
component: markRaw(WmMaterialStockSelect),
componentProps: {
// 选择库存记录后,自动回填仓库/库区/库位/批次/数量
onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
await formApi?.setValues({
areaId: stock?.areaId,
batchCode: stock?.batchCode,
batchId: stock?.batchId,
locationId: stock?.locationId,
quantity: stock?.quantity,
quantityMax: stock?.quantity,
warehouseId: stock?.warehouseId,
});
},
},
rules: 'selectRequired',
dependencies: {
triggerFields: ['itemId'],
componentProps: (values) => ({
itemId: values.itemId,
}),
},
},
{
fieldName: 'itemId',
label: '物料',
component: markRaw(MdItemSelect),
componentProps: {
disabled: true,
},
},
{
fieldName: 'quantity',
label: '数量',
component: 'InputNumber',
componentProps: {
class: '!w-full',
min: 0,
placeholder: '请输入数量',
precision: 2,
},
rules: 'required',
dependencies: {
triggerFields: ['quantityMax'],
componentProps: (values) => ({
class: '!w-full',
max: values.quantityMax,
min: 0,
placeholder: '请输入数量',
precision: 2,
}),
},
},
{
fieldName: 'warehouseId',
label: '发料仓库',
component: markRaw(WmWarehouseSelect),
componentProps: {
disabled: true,
},
},
{
fieldName: 'locationId',
label: '库区',
component: markRaw(WmWarehouseLocationSelect),
componentProps: {
disabled: true,
},
dependencies: {
triggerFields: ['warehouseId'],
componentProps: (values) => ({
disabled: true,
warehouseId: values.warehouseId,
}),
},
},
{
fieldName: 'areaId',
label: '库位',
component: markRaw(WmWarehouseAreaSelect),
componentProps: {
disabled: true,
},
dependencies: {
triggerFields: ['locationId'],
componentProps: (values) => ({
disabled: true,
locationId: values.locationId,
}),
},
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
disabled: true,
},
},
];
}

View File

@ -1 +1,2 @@
export { default as DeptSelectModal } from './select-modal.vue';
export { default as DeptTreeSelect } from './tree-select.vue';

View File

@ -10,10 +10,16 @@ import { Input, Spin, Tree } from 'ant-design-vue';
import { getSimpleDeptList } from '#/api/system/dept';
const emit = defineEmits(['select']);
defineOptions({ name: 'DeptTreeSelect' });
const emit = defineEmits<{
select: [dept?: SystemDeptApi.Dept];
}>();
const deptList = ref<SystemDeptApi.Dept[]>([]); //
const deptTree = ref<any[]>([]); //
const expandedKeys = ref<number[]>([]); //
const selectedKeys = ref<number[]>([]); //
const loading = ref(false); //
const searchValue = ref(''); //
@ -31,11 +37,21 @@ function handleSearch(e: any) {
expandedKeys.value = deptTree.value.map((node) => node.id!);
}
/** 选中部门 */
/** 选中部门:点击已选中的节点时取消选中 */
function handleSelect(_selectedKeys: any[], info: any) {
emit('select', info.node.dataRef);
emit('select', info.selected ? info.node.dataRef : undefined);
}
/** 重置选中状态(供外部重置按钮调用) */
function reset() {
searchValue.value = '';
selectedKeys.value = [];
deptTree.value = handleTree(deptList.value);
emit('select', undefined);
}
defineExpose({ reset });
/** 初始化 */
onMounted(async () => {
try {
@ -43,8 +59,6 @@ onMounted(async () => {
const data = await getSimpleDeptList();
deptList.value = data;
deptTree.value = handleTree(data);
} catch (error) {
console.error('获取部门数据失败', error);
} finally {
loading.value = false;
}
@ -54,24 +68,25 @@ onMounted(async () => {
<template>
<div>
<Input
placeholder="搜索部门"
allow-clear
v-model:value="searchValue"
@change="handleSearch"
allow-clear
class="w-full"
placeholder="搜索部门"
@change="handleSearch"
>
<template #prefix>
<IconifyIcon icon="lucide:search" class="size-4" />
<IconifyIcon class="size-4" icon="lucide:search" />
</template>
</Input>
<Spin :spinning="loading" wrapper-class-name="w-full">
<Tree
@select="handleSelect"
v-if="deptTree.length > 0"
v-model:selected-keys="selectedKeys"
class="pt-2"
:tree-data="deptTree"
:default-expand-all="true"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
:tree-data="deptTree"
@select="handleSelect"
/>
<div v-else-if="!loading" class="py-4 text-center text-gray-500">
暂无数据

View File

@ -1 +1,3 @@
export { default as UserSelectDialog } from './select-dialog.vue';
export { default as UserSelectModal } from './select-modal.vue';
export { default as UserSelect } from './select.vue';

View File

@ -0,0 +1,305 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { nextTick, ref } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button, Col, message, Modal, Row } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getUserPage } from '#/api/system/user';
import { DeptTreeSelect } from '#/views/system/dept/components';
const emit = defineEmits<{
selected: [rows: SystemUserApi.User[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<SystemUserApi.User[]>([]); //
const preSelectedIds = ref<number[]>([]); //
const deptId = ref<number>(); //
const deptTreeRef = ref<InstanceType<typeof DeptTreeSelect>>(); //
/** 用户选择弹窗的搜索表单 */
function useUserSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'username',
label: '用户名称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入用户名称',
},
},
{
fieldName: 'nickname',
label: '用户昵称',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入用户昵称',
},
},
{
fieldName: 'mobile',
label: '手机号码',
component: 'Input',
componentProps: {
allowClear: true,
placeholder: '请输入手机号码',
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
allowClear: true,
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
},
];
}
/** 用户选择弹窗的字段 */
function useUserSelectGridColumns(
isMultiple = false,
): VxeTableGridOptions<SystemUserApi.User>['columns'] {
return [
{
type: isMultiple ? 'checkbox' : 'radio',
width: 50,
},
{
field: 'id',
title: '用户编号',
width: 120,
},
{
field: 'username',
title: '用户名称',
width: 150,
},
{
field: 'nickname',
title: '用户昵称',
minWidth: 150,
},
{
field: 'mobile',
title: '手机号码',
width: 130,
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
];
}
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, SystemUserApi.User>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as SystemUserApi.User[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: SystemUserApi.User) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: SystemUserApi.User) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: SystemUserApi.User }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选用户 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as SystemUserApi.User[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useUserSelectGridFormSchema(),
},
gridOptions: {
columns: useUserSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getUserPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
deptId: deptId.value,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<SystemUserApi.User>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: SystemUserApi.User }) => {
handleRadioChange(row);
},
},
});
/** 部门树节点点击 */
function handleDeptNodeClick(dept?: SystemDeptApi.Dept) {
deptId.value = dept?.id;
gridApi.query();
}
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
deptId.value = undefined;
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
deptTreeRef.value?.reset();
}
/** 打开用户选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: { multiple?: boolean },
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
await nextTick();
gridApi.setGridOptions({
columns: useUserSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.formApi.setFieldValue('status', CommonStatusEnum.ENABLE);
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭用户选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择用户 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<Modal
v-model:open="open"
title="人员选择"
width="80%"
:destroy-on-close="true"
@ok="handleConfirm"
@cancel="closeModal"
>
<Row :gutter="12">
<Col :span="5">
<DeptTreeSelect ref="deptTreeRef" @select="handleDeptNodeClick" />
</Col>
<Col :span="19">
<Grid table-title="" />
</Col>
</Row>
<template #footer>
<Button @click="closeModal"></Button>
<Button type="primary" @click="handleConfirm"></Button>
</template>
</Modal>
</template>

View File

@ -0,0 +1,134 @@
<script lang="ts" setup>
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref, useAttrs, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { Input, Tooltip } from 'ant-design-vue';
import { getUser } from '#/api/system/user';
import UserSelectDialog from './select-dialog.vue';
defineOptions({ name: 'UserSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
allowClear?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
allowClear: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择用户',
},
);
const emit = defineEmits<{
change: [item: SystemUserApi.User | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof UserSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<SystemUserApi.User>();
const displayLabel = computed(
() => selectedItem.value?.nickname ?? selectedItem.value?.username ?? '',
);
const showClear = computed(
() =>
props.allowClear &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询用户信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getUser(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选用户 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开用户选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.ant-input-suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, { multiple: false });
}
/** 弹窗选中回调 */
function handleSelected(rows: SystemUserApi.User[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
<template #title>
<div v-if="selectedItem" class="leading-6">
<div>用户名称{{ selectedItem.username || '-' }}</div>
<div>用户昵称{{ selectedItem.nickname || '-' }}</div>
<div>手机号码{{ selectedItem.mobile || '-' }}</div>
</div>
</template>
<Input
:disabled="disabled"
:placeholder="placeholder"
:value="displayLabel"
readonly
>
<template #suffix>
<IconifyIcon
class="size-4"
:icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
/>
</template>
</Input>
</Tooltip>
</div>
<UserSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -21,10 +21,10 @@ import {
updateUserStatus,
} from '#/api/system/user';
import { $t } from '#/locales';
import { DeptTreeSelect } from '#/views/system/dept/components';
import { useGridColumns, useGridFormSchema } from './data';
import AssignRoleForm from './modules/assign-role-form.vue';
import DeptTree from './modules/dept-tree.vue';
import Form from './modules/form.vue';
import ImportForm from './modules/import-form.vue';
import ResetPasswordForm from './modules/reset-password-form.vue';
@ -62,8 +62,8 @@ async function handleExport() {
/** 选择部门 */
const searchDeptId = ref<number | undefined>(undefined);
async function handleDeptSelect(dept: SystemDeptApi.Dept) {
searchDeptId.value = dept.id;
async function handleDeptSelect(dept?: SystemDeptApi.Dept) {
searchDeptId.value = dept?.id;
handleRefresh();
}
@ -205,7 +205,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<div class="flex h-full w-full">
<!-- 左侧部门树 -->
<Card class="mr-4 h-full w-1/6">
<DeptTree @select="handleDeptSelect" />
<DeptTreeSelect @select="handleDeptSelect" />
</Card>
<!-- 右侧用户列表 -->
<div class="w-5/6">

View File

@ -21,6 +21,8 @@ export namespace MesProWorkOrderApi {
routeName?: string;
clientId?: number;
clientName?: string;
vendorId?: number; // 供应商编号
vendorName?: string; // 供应商名称
planStartTime?: number | string;
planEndTime?: number | string;
actualStartTime?: number | string;

View File

@ -0,0 +1,60 @@
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueDetailApi {
/** MES 外协发料单明细 */
export interface OutsourceIssueDetail {
id?: number; // 明细编号
lineId?: number; // 行编号
issueId?: number; // 发料单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 数量
materialStockId?: number; // 库存编号
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
warehouseId?: number; // 仓库编号
warehouseName?: string; // 仓库名称
locationId?: number; // 库区编号
locationName?: string; // 库区名称
areaId?: number; // 库位编号
areaName?: string; // 库位名称
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单明细列表 */
export function getOutsourceIssueDetailListByLineId(lineId: number) {
return requestClient.get<
MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]
>('/mes/wm/outsource-issue-detail/list-by-line', { params: { lineId } });
}
/** 查询外协发料单明细详情 */
export function getOutsourceIssueDetail(id: number) {
return requestClient.get<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>(
`/mes/wm/outsource-issue-detail/get?id=${id}`,
);
}
/** 新增外协发料单明细 */
export function createOutsourceIssueDetail(
data: MesWmOutsourceIssueDetailApi.OutsourceIssueDetail,
) {
return requestClient.post('/mes/wm/outsource-issue-detail/create', data);
}
/** 修改外协发料单明细 */
export function updateOutsourceIssueDetail(
data: MesWmOutsourceIssueDetailApi.OutsourceIssueDetail,
) {
return requestClient.put('/mes/wm/outsource-issue-detail/update', data);
}
/** 删除外协发料单明细 */
export function deleteOutsourceIssueDetail(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue-detail/delete?id=${id}`);
}

View File

@ -0,0 +1,90 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueApi {
/** MES 外协发料单 */
export interface OutsourceIssue {
id?: number; // 发料单编号
code?: string; // 发料单编号
name?: string; // 发料单名称
vendorId?: number; // 供应商编号
vendorCode?: string; // 供应商编码
vendorName?: string; // 供应商名称
workOrderId?: number; // 生产工单编号
workOrderCode?: string; // 生产工单编码
workOrderName?: string; // 生产工单名称
issueDate?: number; // 发料日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单分页 */
export function getOutsourceIssuePage(params: PageParam) {
return requestClient.get<PageResult<MesWmOutsourceIssueApi.OutsourceIssue>>(
'/mes/wm/outsource-issue/page',
{ params },
);
}
/** 查询外协发料单详情 */
export function getOutsourceIssue(id: number) {
return requestClient.get<MesWmOutsourceIssueApi.OutsourceIssue>(
`/mes/wm/outsource-issue/get?id=${id}`,
);
}
/** 新增外协发料单 */
export function createOutsourceIssue(
data: MesWmOutsourceIssueApi.OutsourceIssue,
) {
return requestClient.post<number>('/mes/wm/outsource-issue/create', data);
}
/** 修改外协发料单 */
export function updateOutsourceIssue(
data: MesWmOutsourceIssueApi.OutsourceIssue,
) {
return requestClient.put('/mes/wm/outsource-issue/update', data);
}
/** 删除外协发料单 */
export function deleteOutsourceIssue(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue/delete?id=${id}`);
}
/** 提交外协发料单 */
export function submitOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/submit?id=${id}`);
}
/** 执行拣货 */
export function stockOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/stock?id=${id}`);
}
/** 执行领出 */
export function finishOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/finish?id=${id}`);
}
/** 取消外协发料单 */
export function cancelOutsourceIssue(id: number) {
return requestClient.put(`/mes/wm/outsource-issue/cancel?id=${id}`);
}
/** 校验外协发料单拣货数量是否与发料数量一致 */
export function checkOutsourceIssueQuantity(id: number) {
return requestClient.get<boolean>(
`/mes/wm/outsource-issue/check-quantity?id=${id}`,
);
}
/** 导出外协发料单 */
export function exportOutsourceIssue(params: any) {
return requestClient.download('/mes/wm/outsource-issue/export-excel', {
params,
});
}

View File

@ -0,0 +1,55 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceIssueLineApi {
/** MES 外协发料单行 */
export interface OutsourceIssueLine {
id?: number; // 行编号
issueId?: number; // 发料单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 发料数量
materialStockId?: number; // 库存编号
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协发料单行分页 */
export function getOutsourceIssueLinePage(params: PageParam) {
return requestClient.get<
PageResult<MesWmOutsourceIssueLineApi.OutsourceIssueLine>
>('/mes/wm/outsource-issue-line/page', { params });
}
/** 查询外协发料单行详情 */
export function getOutsourceIssueLine(id: number) {
return requestClient.get<MesWmOutsourceIssueLineApi.OutsourceIssueLine>(
`/mes/wm/outsource-issue-line/get?id=${id}`,
);
}
/** 新增外协发料单行 */
export function createOutsourceIssueLine(
data: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
) {
return requestClient.post('/mes/wm/outsource-issue-line/create', data);
}
/** 修改外协发料单行 */
export function updateOutsourceIssueLine(
data: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
) {
return requestClient.put('/mes/wm/outsource-issue-line/update', data);
}
/** 删除外协发料单行 */
export function deleteOutsourceIssueLine(id: number) {
return requestClient.delete(`/mes/wm/outsource-issue-line/delete?id=${id}`);
}

View File

@ -0,0 +1,61 @@
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptDetailApi {
/** MES 外协入库单明细 */
export interface OutsourceReceiptDetail {
id?: number; // 明细编号
lineId?: number; // 行编号
receiptId?: number; // 入库单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 上架数量
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
warehouseId?: number; // 仓库编号
warehouseName?: string; // 仓库名称
locationId?: number; // 库区编号
locationName?: string; // 库区名称
areaId?: number; // 库位编号
areaName?: string; // 库位名称
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单明细列表 */
export function getOutsourceReceiptDetailListByLineId(lineId: number) {
return requestClient.get<
MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]
>('/mes/wm/outsource-receipt-detail/list-by-line', { params: { lineId } });
}
/** 查询外协入库单明细详情 */
export function getOutsourceReceiptDetail(id: number) {
return requestClient.get<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>(
`/mes/wm/outsource-receipt-detail/get?id=${id}`,
);
}
/** 新增外协入库单明细 */
export function createOutsourceReceiptDetail(
data: MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail,
) {
return requestClient.post('/mes/wm/outsource-receipt-detail/create', data);
}
/** 修改外协入库单明细 */
export function updateOutsourceReceiptDetail(
data: MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail,
) {
return requestClient.put('/mes/wm/outsource-receipt-detail/update', data);
}
/** 删除外协入库单明细 */
export function deleteOutsourceReceiptDetail(id: number) {
return requestClient.delete(
`/mes/wm/outsource-receipt-detail/delete?id=${id}`,
);
}

View File

@ -0,0 +1,81 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptApi {
/** MES 外协入库单 */
export interface OutsourceReceipt {
id?: number; // 入库单编号
code?: string; // 入库单编码
name?: string; // 入库单名称
workOrderId?: number; // 外协工单编号
workOrderCode?: string; // 外协工单编码
vendorId?: number; // 供应商编号
vendorName?: string; // 供应商名称
receiptDate?: number; // 入库日期
status?: number; // 单据状态
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单分页 */
export function getOutsourceReceiptPage(params: PageParam) {
return requestClient.get<PageResult<MesWmOutsourceReceiptApi.OutsourceReceipt>>(
'/mes/wm/outsource-receipt/page',
{ params },
);
}
/** 查询外协入库单详情 */
export function getOutsourceReceipt(id: number) {
return requestClient.get<MesWmOutsourceReceiptApi.OutsourceReceipt>(
`/mes/wm/outsource-receipt/get?id=${id}`,
);
}
/** 新增外协入库单 */
export function createOutsourceReceipt(
data: MesWmOutsourceReceiptApi.OutsourceReceipt,
) {
return requestClient.post<number>('/mes/wm/outsource-receipt/create', data);
}
/** 修改外协入库单 */
export function updateOutsourceReceipt(
data: MesWmOutsourceReceiptApi.OutsourceReceipt,
) {
return requestClient.put('/mes/wm/outsource-receipt/update', data);
}
/** 删除外协入库单 */
export function deleteOutsourceReceipt(id: number) {
return requestClient.delete(`/mes/wm/outsource-receipt/delete?id=${id}`);
}
/** 提交外协入库单 */
export function submitOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/submit?id=${id}`);
}
/** 执行上架 */
export function stockOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/stock?id=${id}`);
}
/** 完成入库 */
export function finishOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/finish?id=${id}`);
}
/** 取消外协入库单 */
export function cancelOutsourceReceipt(id: number) {
return requestClient.put(`/mes/wm/outsource-receipt/cancel?id=${id}`);
}
/** 导出外协入库单 */
export function exportOutsourceReceipt(params: any) {
return requestClient.download('/mes/wm/outsource-receipt/export-excel', {
params,
});
}

View File

@ -0,0 +1,58 @@
import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace MesWmOutsourceReceiptLineApi {
/** MES 外协入库单行 */
export interface OutsourceReceiptLine {
id?: number; // 行编号
receiptId?: number; // 入库单编号
itemId?: number; // 物料编号
itemCode?: string; // 物料编码
itemName?: string; // 物料名称
specification?: string; // 规格型号
unitMeasureName?: string; // 计量单位名称
quantity?: number; // 入库数量
batchId?: number; // 批次编号
batchCode?: string; // 批次编码
productionDate?: number; // 生产日期
expireDate?: number; // 有效期
lotNumber?: string; // 生产批号
iqcCheckFlag?: boolean; // 是否需要质检
remark?: string; // 备注
createTime?: Date; // 创建时间
}
}
/** 查询外协入库单行分页 */
export function getOutsourceReceiptLinePage(params: PageParam) {
return requestClient.get<
PageResult<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>
>('/mes/wm/outsource-receipt-line/page', { params });
}
/** 查询外协入库单行详情 */
export function getOutsourceReceiptLine(id: number) {
return requestClient.get<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>(
`/mes/wm/outsource-receipt-line/get?id=${id}`,
);
}
/** 新增外协入库单行 */
export function createOutsourceReceiptLine(
data: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
) {
return requestClient.post('/mes/wm/outsource-receipt-line/create', data);
}
/** 修改外协入库单行 */
export function updateOutsourceReceiptLine(
data: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
) {
return requestClient.put('/mes/wm/outsource-receipt-line/update', data);
}
/** 删除外协入库单行 */
export function deleteOutsourceReceiptLine(id: number) {
return requestClient.delete(`/mes/wm/outsource-receipt-line/delete?id=${id}`);
}

View File

@ -0,0 +1,2 @@
export { default as ProCardSelectDialog } from './pro-card-select-dialog.vue';
export { default as ProCardSelect } from './pro-card-select.vue';

View File

@ -0,0 +1,191 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
import { nextTick, ref } from 'vue';
import { ElButton, ElDialog, ElMessage } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getCardPage } from '#/api/mes/pro/card';
import { useCardSelectGridColumns, useCardSelectGridFormSchema } from '../data';
const emit = defineEmits<{
selected: [rows: MesProCardApi.Card[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<MesProCardApi.Card[]>([]); //
const preSelectedIds = ref<number[]>([]); //
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, MesProCardApi.Card>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as MesProCardApi.Card[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: MesProCardApi.Card) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: MesProCardApi.Card) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: MesProCardApi.Card }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选流转卡 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesProCardApi.Card[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useCardSelectGridFormSchema(),
},
gridOptions: {
columns: useCardSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getCardPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesProCardApi.Card>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: MesProCardApi.Card }) => {
handleRadioChange(row);
},
},
});
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
}
/** 打开流转卡选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: { multiple?: boolean },
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
await nextTick();
gridApi.setGridOptions({
columns: useCardSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭流转卡选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择流转卡 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<ElDialog v-model="open" title="流转卡选择" width="70%">
<Grid table-title="" />
<template #footer>
<ElButton @click="closeModal"></ElButton>
<ElButton type="primary" @click="handleConfirm"></ElButton>
</template>
</ElDialog>
</template>

View File

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { MesProCardApi } from '#/api/mes/pro/card';
import { computed, ref, useAttrs, watch } from 'vue';
import { CircleX, Search } from '@vben/icons';
import { ElInput, ElTooltip } from 'element-plus';
import { getCard } from '#/api/mes/pro/card';
import ProCardSelectDialog from './pro-card-select-dialog.vue';
defineOptions({ name: 'ProCardSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
clearable?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
clearable: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择流转卡',
},
);
const emit = defineEmits<{
change: [item: MesProCardApi.Card | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof ProCardSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<MesProCardApi.Card>();
const displayLabel = computed(() => selectedItem.value?.code ?? '');
const showClear = computed(
() =>
props.clearable &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询流转卡信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getCard(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选流转卡 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开流转卡选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.el-input__suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, { multiple: false });
}
/** 弹窗选中回调 */
function handleSelected(rows: MesProCardApi.Card[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
<template #content>
<div v-if="selectedItem" class="leading-6">
<div>编号{{ selectedItem.code || '-' }}</div>
<div>工单{{ selectedItem.workOrderCode || '-' }}</div>
<div>批次{{ selectedItem.batchCode || '-' }}</div>
<div>产品{{ selectedItem.itemName || '-' }}</div>
</div>
</template>
<ElInput
:disabled="disabled"
:model-value="displayLabel"
:placeholder="placeholder"
readonly
>
<template #suffix>
<CircleX v-if="showClear" class="size-4" />
<Search v-else class="size-4" />
</template>
</ElInput>
</ElTooltip>
</div>
<ProCardSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -0,0 +1,100 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesProCardApi } from '#/api/mes/pro/card';
import { markRaw } from 'vue';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
/** 流转卡选择弹窗的搜索表单 */
export function useCardSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'code',
label: '流转卡编号',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入流转卡编号',
},
},
{
fieldName: 'workOrderId',
label: '生产工单',
component: markRaw(ProWorkOrderSelect),
componentProps: {
placeholder: '请选择生产工单',
},
},
{
fieldName: 'itemId',
label: '产品物料',
component: markRaw(MdItemSelect),
componentProps: {
placeholder: '请选择产品物料',
},
},
{
fieldName: 'batchCode',
label: '批次号',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入批次号',
},
},
];
}
/** 流转卡选择弹窗的字段 */
export function useCardSelectGridColumns(
multiple = false,
): VxeTableGridOptions<MesProCardApi.Card>['columns'] {
return [
{
type: multiple ? 'checkbox' : 'radio',
width: 50,
},
{
field: 'code',
title: '流转卡编号',
width: 160,
},
{
field: 'workOrderCode',
title: '生产工单编号',
width: 160,
},
{
field: 'itemCode',
title: '产品物料编码',
width: 140,
},
{
field: 'batchCode',
title: '批次号',
width: 120,
},
{
field: 'itemName',
title: '产品物料名称',
minWidth: 150,
},
{
field: 'specification',
title: '规格型号',
width: 120,
},
{
field: 'unitMeasureName',
title: '单位',
width: 80,
},
{
field: 'transferedQuantity',
title: '流转数量',
width: 100,
},
];
}

View File

@ -151,9 +151,22 @@ export const MesAutoCodeRuleCode = {
TM_TOOL_CODE: 'TM_TOOL_CODE',
WM_AREA_CODE: 'WM_AREA_CODE',
WM_LOCATION_CODE: 'WM_LOCATION_CODE',
WM_ARRIVAL_NOTICE_CODE: 'WM_ARRIVAL_NOTICE_CODE',
WM_ITEM_RECEIPT_CODE: 'WM_ITEM_RECEIPT_CODE',
WM_RETURN_VENDOR_CODE: 'WM_RETURN_VENDOR_CODE',
WM_SALES_NOTICE_CODE: 'WM_SALES_NOTICE_CODE',
WM_RETURN_SALES_CODE: 'WM_RETURN_SALES_CODE',
WM_RETURN_ISSUE_CODE: 'WM_RETURN_ISSUE_CODE',
WM_PRODUCT_ISSUE_CODE: 'WM_PRODUCT_ISSUE_CODE',
WM_PRODUCT_SALES_CODE: 'WM_PRODUCT_SALES_CODE',
PRODUCTRECPT_CODE: 'PRODUCTRECPT_CODE',
WM_MISC_ISSUE_CODE: 'WM_MISC_ISSUE_CODE',
WM_MISC_RECEIPT_CODE: 'WM_MISC_RECEIPT_CODE',
WM_OUTSOURCE_ISSUE_CODE: 'WM_OUTSOURCE_ISSUE_CODE',
WM_OUTSOURCE_RECEIPT_CODE: 'WM_OUTSOURCE_RECEIPT_CODE',
WM_PACKAGE_CODE: 'WM_PACKAGE_CODE',
WM_STOCK_TAKING_CODE: 'WM_STOCK_TAKING_CODE',
WM_STOCK_TAKING_PLAN_CODE: 'WM_STOCK_TAKING_PLAN_CODE',
WM_WAREHOUSE_CODE: 'WM_WAREHOUSE_CODE',
} as const;
@ -163,6 +176,38 @@ export const MesWmPackageStatusEnum = {
FINISHED: MesOrderStatusConstants.FINISHED,
} as const;
/** MES 盘点类型枚举 */
export const MesWmStockTakingTypeEnum = {
STATIC: 1,
DYNAMIC: 2,
} as const;
/** MES 盘点任务状态枚举 */
export const MesWmStockTakingTaskStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 盘点任务行状态枚举 */
export const MesWmStockTakingTaskLineStatusEnum = {
UNCOUNTED: 0,
NORMAL: 1,
GAIN: 2,
LOSS: 3,
} as const;
/** MES 盘点方案参数类型枚举 */
export const MesWmStockTakingParamTypeEnum = {
WAREHOUSE: 102,
LOCATION: 103,
AREA: 104,
BATCH: 107,
ITEM: 600,
QUALITY_STATUS: 900,
} as const;
/** MES 生产工单状态枚举 */
export const MesProWorkOrderStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
@ -173,6 +218,13 @@ export const MesProWorkOrderStatusEnum = {
CANCELLED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 工单类型枚举 */
export const MesProWorkOrderTypeEnum = {
SELF: 1, // 自行生产
OUTSOURCE: 2, // 代工
PURCHASE: 3, // 采购
} as const;
/** MES 生产任务状态枚举 */
export const MesProTaskStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
@ -236,6 +288,105 @@ export const MesWmMiscReceiptStatusEnum = {
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 外协发料单状态枚举 */
export const MesWmOutsourceIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELLED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 外协入库单状态枚举 */
export const MesWmOutsourceReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 到货通知单状态枚举 */
export const MesWmArrivalNoticeStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
PENDING_QC: MesOrderStatusConstants.APPROVING,
PENDING_RECEIPT: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
} as const;
/** MES 采购入库单状态枚举 */
export const MesWmItemReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 供应商退货单状态枚举 */
export const MesWmReturnVendorStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 发货通知单状态枚举 */
export const MesWmSalesNoticeStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVED: MesOrderStatusConstants.APPROVED,
} as const;
/** MES 销售退货单状态枚举 */
export const MesWmReturnSalesStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 生产退料单状态枚举 */
export const MesWmReturnIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 领料出库单状态枚举 */
export const MesWmProductIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 产品入库单状态枚举 */
export const MesWmProductReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 销售出库单状态枚举 */
export const MesWmProductSalesStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
CONFIRMED: MesOrderStatusConstants.CONFIRMED,
APPROVING: MesOrderStatusConstants.APPROVING,
SHIPPING: 10, // 待填写运单
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
} as const;
/** MES 质检结果值类型枚举 */
export const MesQcResultValueType = {
FLOAT: 1,

View File

@ -19,9 +19,12 @@ 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 { ProCardSelect } from '#/views/mes/pro/card/components';
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 { WmBatchSelect } from '#/views/mes/wm/batch/components';
import { UserSelect } from '#/views/system/user/components';
import WmMaterialStockSelect from './../materialstock/components/wm-material-stock-select.vue';
import { WmPackageSelect } from './../packages/components';
@ -51,15 +54,31 @@ async function syncBizDetail(
}
let bizCode: string | undefined;
let bizName: string | undefined;
if (bizType === BarcodeBizTypeEnum.STOCK) {
bizCode = item.itemCode;
bizName = item.itemName;
} else if (bizType === BarcodeBizTypeEnum.PACKAGE) {
bizCode = item.code;
bizName = item.clientName || item.code;
} else {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
switch (bizType) {
case BarcodeBizTypeEnum.BATCH: {
bizCode = item.code;
bizName = item.itemName || item.code;
break;
}
case BarcodeBizTypeEnum.PACKAGE: {
bizCode = item.code;
bizName = item.clientName || item.code;
break;
}
case BarcodeBizTypeEnum.PROCARD: {
bizCode = item.code;
bizName = item.workOrderName || item.code;
break;
}
case BarcodeBizTypeEnum.STOCK: {
bizCode = item.itemCode;
bizName = item.itemName;
break;
}
default: {
bizCode = item.code || item.username;
bizName = item.name || item.nickname;
}
}
// 先回填业务编码、名称并清空旧条码内容
await formApi.setValues({ bizCode, bizName, content: undefined });
@ -328,6 +347,45 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '批次',
component: markRaw(WmBatchSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.BATCH,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '流转卡',
component: markRaw(ProCardSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.PROCARD,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '人员',
component: markRaw(UserSelect),
componentProps: {
onChange: (item: any) => syncBizDetail(formApi, item),
},
dependencies: {
triggerFields: ['bizType'],
show: (values) => values.bizType === BarcodeBizTypeEnum.USER,
},
rules: 'required',
},
{
fieldName: 'bizId',
label: '业务编号',
@ -344,13 +402,16 @@ export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
values.bizType !== undefined &&
![
BarcodeBizTypeEnum.AREA,
BarcodeBizTypeEnum.BATCH,
BarcodeBizTypeEnum.CLIENT,
BarcodeBizTypeEnum.ITEM,
BarcodeBizTypeEnum.LOCATION,
BarcodeBizTypeEnum.MACHINERY,
BarcodeBizTypeEnum.PACKAGE,
BarcodeBizTypeEnum.PROCARD,
BarcodeBizTypeEnum.STOCK,
BarcodeBizTypeEnum.TOOL,
BarcodeBizTypeEnum.USER,
BarcodeBizTypeEnum.VENDOR,
BarcodeBizTypeEnum.WAREHOUSE,
BarcodeBizTypeEnum.WORKORDER,

View File

@ -0,0 +1,2 @@
export { default as WmBatchSelectDialog } from './wm-batch-select-dialog.vue';
export { default as WmBatchSelect } from './wm-batch-select.vue';

View File

@ -0,0 +1,241 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
import { computed, nextTick, ref } from 'vue';
import { ElAlert, ElButton, ElDialog, ElMessage } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getBatchPage } from '#/api/mes/wm/batch';
import { useBatchSelectGridColumns, useBatchSelectGridFormSchema } from '../data';
const emit = defineEmits<{
selected: [rows: MesWmBatchApi.Batch[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<MesWmBatchApi.Batch[]>([]); //
const preSelectedIds = ref<number[]>([]); //
const externalItemId = ref<number>(); //
const externalClientId = ref<number>(); //
const externalVendorId = ref<number>(); //
const externalSalesOrderCode = ref<string>(); //
const filterTip = computed(() => {
const parts: string[] = [];
if (externalClientId.value != null) {
parts.push('客户');
}
if (externalVendorId.value != null) {
parts.push('供应商');
}
if (externalSalesOrderCode.value != null) {
parts.push('销售订单');
}
return parts.length > 0 ? `已按${parts.join('/')}预过滤` : '';
});
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, MesWmBatchApi.Batch>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as MesWmBatchApi.Batch[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: MesWmBatchApi.Batch) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: MesWmBatchApi.Batch) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: MesWmBatchApi.Batch }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选批次 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as MesWmBatchApi.Batch[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useBatchSelectGridFormSchema(),
},
gridOptions: {
columns: useBatchSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getBatchPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MesWmBatchApi.Batch>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: MesWmBatchApi.Batch }) => {
handleRadioChange(row);
},
},
});
/** 重置查询和选择状态,保留外部传入的默认过滤 */
async function resetQueryState() {
selectedRows.value = [];
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
if (externalItemId.value) {
await gridApi.formApi.setFieldValue('itemId', externalItemId.value);
}
if (externalClientId.value) {
await gridApi.formApi.setFieldValue('clientId', externalClientId.value);
}
if (externalVendorId.value) {
await gridApi.formApi.setFieldValue('vendorId', externalVendorId.value);
}
if (externalSalesOrderCode.value) {
await gridApi.formApi.setFieldValue(
'salesOrderCode',
externalSalesOrderCode.value,
);
}
}
/** 打开批次选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: {
clientId?: number;
itemId?: number;
multiple?: boolean;
salesOrderCode?: string;
vendorId?: number;
},
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
externalItemId.value = options?.itemId;
externalClientId.value = options?.clientId;
externalVendorId.value = options?.vendorId;
externalSalesOrderCode.value = options?.salesOrderCode;
await nextTick();
gridApi.setGridOptions({
columns: useBatchSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭批次选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择批次 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<ElDialog v-model="open" title="批次选择" width="80%">
<ElAlert
v-if="filterTip"
:closable="false"
class="!mb-3"
:title="filterTip"
type="info"
/>
<Grid table-title="" />
<template #footer>
<ElButton @click="closeModal"></ElButton>
<ElButton type="primary" @click="handleConfirm"></ElButton>
</template>
</ElDialog>
</template>

View File

@ -0,0 +1,145 @@
<script lang="ts" setup>
import type { MesWmBatchApi } from '#/api/mes/wm/batch';
import { computed, ref, useAttrs, watch } from 'vue';
import { CircleX, Search } from '@vben/icons';
import { ElInput, ElTooltip } from 'element-plus';
import { getBatch } from '#/api/mes/wm/batch';
import WmBatchSelectDialog from './wm-batch-select-dialog.vue';
defineOptions({ name: 'WmBatchSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
clearable?: boolean;
clientId?: number; // ID
disabled?: boolean;
itemId?: number; // ID
modelValue?: number;
placeholder?: string;
salesOrderCode?: string; //
vendorId?: number; // ID
}>(),
{
clearable: true,
clientId: undefined,
disabled: false,
itemId: undefined,
modelValue: undefined,
placeholder: '请选择批次',
salesOrderCode: undefined,
vendorId: undefined,
},
);
const emit = defineEmits<{
change: [item: MesWmBatchApi.Batch | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof WmBatchSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<MesWmBatchApi.Batch>();
const displayLabel = computed(() => selectedItem.value?.code ?? '');
const showClear = computed(
() =>
props.clearable &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询批次信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getBatch(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选批次 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开批次选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.el-input__suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, {
clientId: props.clientId,
itemId: props.itemId,
multiple: false,
salesOrderCode: props.salesOrderCode,
vendorId: props.vendorId,
});
}
/** 弹窗选中回调 */
function handleSelected(rows: MesWmBatchApi.Batch[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
<template #content>
<div v-if="selectedItem" class="leading-6">
<div>批次编号{{ selectedItem.code || '-' }}</div>
<div>物料编码{{ selectedItem.itemCode || '-' }}</div>
<div>物料名称{{ selectedItem.itemName || '-' }}</div>
<div>生产批号{{ selectedItem.lotNumber || '-' }}</div>
</div>
</template>
<ElInput
:disabled="disabled"
:model-value="displayLabel"
:placeholder="placeholder"
readonly
>
<template #suffix>
<CircleX v-if="showClear" class="size-4" />
<Search v-else class="size-4" />
</template>
</ElInput>
</ElTooltip>
</div>
<WmBatchSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -1 +1,2 @@
export { default as DeptSelectModal } from './select-modal.vue';
export { default as DeptTreeSelect } from './tree-select.vue';

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
// TODO @jason /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/system/dept/components/select-modal.vue ~
import type { SystemDeptApi } from '#/api/system/dept';
import { nextTick, ref } from 'vue';
@ -15,16 +14,11 @@ defineOptions({ name: 'DeptSelectModal' });
const props = withDefaults(
defineProps<{
//
cancelText?: string;
// checkable
checkStrictly?: boolean;
//
confirmText?: string;
//
multiple?: boolean;
//
title?: string;
cancelText?: string; //
checkStrictly?: boolean; // checkable
confirmText?: string; //
multiple?: boolean; //
title?: string; //
}>(),
{
cancelText: '取消',
@ -39,16 +33,11 @@ const emit = defineEmits<{
confirm: [deptList: SystemDeptApi.Dept[]];
}>();
//
const deptTree = ref<any[]>([]);
// ID
const selectedDeptIds = ref<number[]>([]);
//
const deptData = ref<SystemDeptApi.Dept[]>([]);
// Tree
const treeRef = ref();
const deptTree = ref<any[]>([]); //
const selectedDeptIds = ref<number[]>([]); // ID
const deptData = ref<SystemDeptApi.Dept[]>([]); //
const treeRef = ref(); // Tree
//
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
// ID

View File

@ -10,12 +10,18 @@ import { ElInput, ElTree } from 'element-plus';
import { getSimpleDeptList } from '#/api/system/dept';
const emit = defineEmits(['select']);
defineOptions({ name: 'DeptTreeSelect' });
const emit = defineEmits<{
select: [dept?: SystemDeptApi.Dept];
}>();
const deptList = ref<SystemDeptApi.Dept[]>([]); //
const deptTree = ref<any[]>([]); //
const expandedKeys = ref<number[]>([]); //
const loading = ref(false); //
const searchValue = ref(''); //
const treeRef = ref<InstanceType<typeof ElTree>>(); // Ref
let currentNodeId: number | undefined; // ID
/** 处理搜索逻辑 */
function handleSearch(value: string) {
@ -26,15 +32,31 @@ function handleSearch(value: string) {
)
: deptList.value;
deptTree.value = handleTree(filteredList);
//
expandedKeys.value = deptTree.value.map((node) => node.id!);
}
/** 选中部门 */
function handleSelect(data: any) {
/** 选中部门:点击已选中的节点时取消选中 */
function handleSelect(data: SystemDeptApi.Dept) {
if (currentNodeId === data.id) {
currentNodeId = undefined;
treeRef.value?.setCurrentKey(undefined);
emit('select', undefined);
return;
}
currentNodeId = data.id;
emit('select', data);
}
/** 重置选中状态(供外部重置按钮调用) */
function reset() {
searchValue.value = '';
currentNodeId = undefined;
treeRef.value?.setCurrentKey(undefined);
deptTree.value = handleTree(deptList.value);
emit('select', undefined);
}
defineExpose({ reset });
/** 初始化 */
onMounted(async () => {
try {
@ -42,8 +64,6 @@ onMounted(async () => {
const data = await getSimpleDeptList();
deptList.value = data;
deptTree.value = handleTree(data);
} catch (error) {
console.error('获取部门数据失败', error);
} finally {
loading.value = false;
}
@ -53,11 +73,11 @@ onMounted(async () => {
<template>
<div>
<ElInput
placeholder="搜索部门"
clearable
v-model="searchValue"
@input="handleSearch"
class="w-full"
clearable
placeholder="搜索部门"
@input="handleSearch"
>
<template #prefix>
<Search class="size-4" />
@ -65,13 +85,15 @@ onMounted(async () => {
</ElInput>
<div v-loading="loading">
<ElTree
class="pt-2"
v-if="deptTree.length > 0"
ref="treeRef"
class="pt-2"
:data="deptTree"
default-expand-all
highlight-current
node-key="id"
:props="{ label: 'name', children: 'children' }"
@node-click="handleSelect"
default-expand-all
node-key="id"
/>
<div v-else-if="!loading" class="py-4 text-center text-gray-500">
暂无数据

View File

@ -1 +1,3 @@
export { default as UserSelectDialog } from './select-dialog.vue';
export { default as UserSelectModal } from './select-modal.vue';
export { default as UserSelect } from './select.vue';

View File

@ -0,0 +1,298 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
import { nextTick, ref } from 'vue';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { ElButton, ElCol, ElDialog, ElMessage, ElRow } from 'element-plus';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getUserPage } from '#/api/system/user';
import { DeptTreeSelect } from '#/views/system/dept/components';
const emit = defineEmits<{
selected: [rows: SystemUserApi.User[]];
}>();
const open = ref(false); //
const multiple = ref(false); // 使
const selectedRows = ref<SystemUserApi.User[]>([]); //
const preSelectedIds = ref<number[]>([]); //
const deptId = ref<number>(); //
const deptTreeRef = ref<InstanceType<typeof DeptTreeSelect>>(); //
/** 用户选择弹窗的搜索表单 */
function useUserSelectGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'username',
label: '用户名称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入用户名称',
},
},
{
fieldName: 'nickname',
label: '用户昵称',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入用户昵称',
},
},
{
fieldName: 'mobile',
label: '手机号码',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入手机号码',
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
clearable: true,
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
},
},
];
}
/** 用户选择弹窗的字段 */
function useUserSelectGridColumns(
isMultiple = false,
): VxeTableGridOptions<SystemUserApi.User>['columns'] {
return [
{
type: isMultiple ? 'checkbox' : 'radio',
width: 50,
},
{
field: 'id',
title: '用户编号',
width: 120,
},
{
field: 'username',
title: '用户名称',
width: 150,
},
{
field: 'nickname',
title: '用户昵称',
minWidth: 150,
},
{
field: 'mobile',
title: '手机号码',
width: 130,
},
{
field: 'status',
title: '状态',
width: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
];
}
/** 获取多选记录,包含 VXE reserve 跨页记录 */
function getMultipleSelectedRows() {
const selectedMap = new Map<number, SystemUserApi.User>();
const records = [
...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
...(gridApi.grid.getCheckboxRecords?.() ?? []),
] as SystemUserApi.User[];
records.forEach((row) => {
const rowId = row.id;
if (rowId !== undefined) {
selectedMap.set(rowId, row);
}
});
return [...selectedMap.values()];
}
/** 处理多选勾选变化 */
function handleCheckboxSelectChange() {
selectedRows.value = getMultipleSelectedRows();
}
/** 处理单选切换 */
function handleRadioChange(row: SystemUserApi.User) {
selectedRows.value = [row];
}
/** 多选模式下切换行勾选 */
async function toggleMultipleRow(row: SystemUserApi.User) {
const selected = gridApi.grid.isCheckedByCheckboxRow(row);
await gridApi.grid.setCheckboxRow(row, !selected);
selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击:单选直接确认,多选切换勾选 */
async function handleCellDblclick({ row }: { row: SystemUserApi.User }) {
if (multiple.value) {
await toggleMultipleRow(row);
return;
}
selectedRows.value = [row];
await gridApi.grid.setRadioRow(row);
handleConfirm();
}
/** 回显预选用户 */
async function applyPreSelection() {
if (preSelectedIds.value.length === 0) {
return;
}
const rows = gridApi.grid.getData() as SystemUserApi.User[];
for (const row of rows) {
if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
continue;
}
if (multiple.value) {
await gridApi.grid.setCheckboxRow(row, true);
} else {
await gridApi.grid.setRadioRow(row);
selectedRows.value = [row];
return;
}
}
if (multiple.value) {
selectedRows.value = getMultipleSelectedRows();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useUserSelectGridFormSchema(),
},
gridOptions: {
columns: useUserSelectGridColumns(false),
height: 520,
keepSource: true,
checkboxConfig: {
highlight: true,
range: true,
reserve: true,
},
radioConfig: {
highlight: true,
trigger: 'row',
},
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getUserPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
deptId: deptId.value,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<SystemUserApi.User>,
gridEvents: {
cellDblclick: handleCellDblclick,
checkboxAll: handleCheckboxSelectChange,
checkboxChange: handleCheckboxSelectChange,
radioChange: ({ row }: { row: SystemUserApi.User }) => {
handleRadioChange(row);
},
},
});
/** 部门树节点点击 */
function handleDeptNodeClick(dept?: SystemDeptApi.Dept) {
deptId.value = dept?.id;
gridApi.query();
}
/** 重置查询和选择状态 */
async function resetQueryState() {
selectedRows.value = [];
deptId.value = undefined;
await gridApi.grid.clearCheckboxRow();
await gridApi.grid.clearCheckboxReserve();
await gridApi.grid.clearRadioRow();
await gridApi.formApi.resetForm();
deptTreeRef.value?.reset();
}
/** 打开用户选择弹窗 */
async function openModal(
selectedIds?: number[],
options?: { multiple?: boolean },
) {
open.value = true;
multiple.value = options?.multiple ?? false;
preSelectedIds.value = selectedIds || [];
await nextTick();
gridApi.setGridOptions({
columns: useUserSelectGridColumns(multiple.value),
});
await resetQueryState();
await gridApi.formApi.setFieldValue('status', CommonStatusEnum.ENABLE);
await gridApi.query();
await nextTick();
await applyPreSelection();
}
/** 关闭用户选择弹窗 */
function closeModal() {
open.value = false;
}
/** 确认选择用户 */
function handleConfirm() {
const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
if (rows.length === 0) {
ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? rows : [rows[0]!]);
open.value = false;
}
defineExpose({ open: openModal });
</script>
<template>
<ElDialog v-model="open" title="人员选择" width="80%">
<ElRow :gutter="12">
<ElCol :span="5">
<DeptTreeSelect ref="deptTreeRef" @select="handleDeptNodeClick" />
</ElCol>
<ElCol :span="19">
<Grid table-title="" />
</ElCol>
</ElRow>
<template #footer>
<ElButton @click="closeModal"></ElButton>
<ElButton type="primary" @click="handleConfirm"></ElButton>
</template>
</ElDialog>
</template>

View File

@ -1,5 +1,4 @@
<script lang="ts" setup>
// TODO @jason /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/system/user/components/select-modal.vue ~
import type { SystemDeptApi } from '#/api/system/dept';
import type { SystemUserApi } from '#/api/system/user';
@ -22,7 +21,7 @@ import {
import { getSimpleDeptList } from '#/api/system/dept';
import { getUserPage } from '#/api/system/user';
//
/** 部门树节点接口 */
interface DeptTreeNode {
id: string;
label: string;
@ -67,7 +66,6 @@ const deptSearchKeys = ref('');
const userList = ref<SystemUserApi.User[]>([]); //
const selectedUserIds = ref<number[]>([]);
//
const [Modal, modalApi] = useVbenModal({
onCancel: handleCancel,
onClosed: handleClosed,
@ -143,7 +141,7 @@ const rightListState = ref({
},
});
// Transfer
/** 计算属性Transfer 数据源 */
const transferDataSource = computed(() => {
// 使 Map
const userMap = new Map<number, any>();
@ -170,7 +168,7 @@ const transferDataSource = computed(() => {
}));
});
//
/** 过滤部门树数据 */
const filteredDeptTree = computed(() => {
if (!deptSearchKeys.value) return deptTree.value;
@ -210,7 +208,7 @@ const filteredDeptTree = computed(() => {
return deptTree.value.map((node: any) => filterNode(node)).filter(Boolean);
});
//
/** 加载用户数据 */
async function loadUserData(pageNo: number, pageSize: number) {
try {
const { list, total } = await getUserPage({
@ -237,7 +235,7 @@ async function loadUserData(pageNo: number, pageSize: number) {
}
}
//
/** 更新右侧列表数据 */
function updateRightListData() {
// 使 Set ID
const uniqueSelectedIds = new Set(selectedUserIds.value);
@ -269,18 +267,18 @@ function updateRightListData() {
rightListState.value.dataSource = filteredUsers.slice(startIndex, endIndex);
}
//
/** 处理左侧分页变化 */
async function handleLeftPaginationChange(page: number) {
await loadUserData(page, leftListState.value.pagination.pageSize);
}
//
/** 处理右侧分页变化 */
function handleRightPaginationChange(page: number) {
rightListState.value.pagination.current = page;
updateRightListData();
}
//
/** 处理用户选择变化 */
function handleUserChange(
value: (number | string)[],
_direction: string,
@ -292,7 +290,7 @@ function handleUserChange(
updateRightListData();
}
//
/** 重置数据 */
function resetData() {
userList.value = [];
selectedUserIds.value = [];
@ -324,7 +322,7 @@ function resetData() {
};
}
//
/** 处理部门搜索 */
function handleDeptSearch(value: string) {
deptSearchKeys.value = value;
@ -347,7 +345,7 @@ function handleDeptSearch(value: string) {
}
}
//
/** 处理部门选择 */
async function handleDeptSelect(node: any) {
// ID
const newDeptId = node.id ? Number(node.id) : undefined;

View File

@ -0,0 +1,132 @@
<script lang="ts" setup>
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref, useAttrs, watch } from 'vue';
import { CircleX, Search } from '@vben/icons';
import { ElInput, ElTooltip } from 'element-plus';
import { getUser } from '#/api/system/user';
import UserSelectDialog from './select-dialog.vue';
defineOptions({ name: 'UserSelect', inheritAttrs: false });
const props = withDefaults(
defineProps<{
clearable?: boolean;
disabled?: boolean;
modelValue?: number;
placeholder?: string;
}>(),
{
clearable: true,
disabled: false,
modelValue: undefined,
placeholder: '请选择用户',
},
);
const emit = defineEmits<{
change: [item: SystemUserApi.User | undefined];
'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs();
const dialogRef = ref<InstanceType<typeof UserSelectDialog>>();
const hovering = ref(false);
const selectedItem = ref<SystemUserApi.User>();
const displayLabel = computed(
() => selectedItem.value?.nickname ?? selectedItem.value?.username ?? '',
);
const showClear = computed(
() =>
props.clearable &&
!props.disabled &&
hovering.value &&
props.modelValue != null,
);
/** 根据编号单条查询用户信息(用于编辑回显) */
async function resolveItemById(id: number | undefined) {
if (id == null) {
selectedItem.value = undefined;
return;
}
if (selectedItem.value?.id === id) {
return;
}
selectedItem.value = await getUser(id);
}
watch(() => props.modelValue, resolveItemById, { immediate: true });
/** 清空已选用户 */
function clearSelected() {
selectedItem.value = undefined;
emit('update:modelValue', undefined);
emit('change', undefined);
}
/** 打开用户选择弹窗 */
function handleClick(event: MouseEvent) {
if (props.disabled) {
return;
}
const target = event.target as HTMLElement;
if (showClear.value && target.closest('.el-input__suffix')) {
event.stopPropagation();
clearSelected();
return;
}
const selectedIds = props.modelValue == null ? [] : [props.modelValue];
dialogRef.value?.open(selectedIds, { multiple: false });
}
/** 弹窗选中回调 */
function handleSelected(rows: SystemUserApi.User[]) {
const item = rows[0];
if (!item) {
return;
}
selectedItem.value = item;
emit('update:modelValue', item.id);
emit('change', item);
}
</script>
<template>
<div
v-bind="attrs"
class="w-full"
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
@click="handleClick"
@mouseenter="hovering = true"
@mouseleave="hovering = false"
>
<ElTooltip :disabled="!selectedItem" placement="top" :show-after="500">
<template #content>
<div v-if="selectedItem" class="leading-6">
<div>用户名称{{ selectedItem.username || '-' }}</div>
<div>用户昵称{{ selectedItem.nickname || '-' }}</div>
<div>手机号码{{ selectedItem.mobile || '-' }}</div>
</div>
</template>
<ElInput
:disabled="disabled"
:model-value="displayLabel"
:placeholder="placeholder"
readonly
>
<template #suffix>
<CircleX v-if="showClear" class="size-4" />
<Search v-else class="size-4" />
</template>
</ElInput>
</ElTooltip>
</div>
<UserSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>

View File

@ -21,10 +21,10 @@ import {
updateUserStatus,
} from '#/api/system/user';
import { $t } from '#/locales';
import { DeptTreeSelect } from '#/views/system/dept/components';
import { useGridColumns, useGridFormSchema } from './data';
import AssignRoleForm from './modules/assign-role-form.vue';
import DeptTree from './modules/dept-tree.vue';
import Form from './modules/form.vue';
import ImportForm from './modules/import-form.vue';
import ResetPasswordForm from './modules/reset-password-form.vue';
@ -62,8 +62,8 @@ async function handleExport() {
/** 选择部门 */
const searchDeptId = ref<number | undefined>(undefined);
async function handleDeptSelect(dept: SystemDeptApi.Dept) {
searchDeptId.value = dept.id;
async function handleDeptSelect(dept?: SystemDeptApi.Dept) {
searchDeptId.value = dept?.id;
handleRefresh();
}
@ -203,7 +203,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<div class="flex h-full w-full">
<!-- 左侧部门树 -->
<ElCard class="mr-4 h-full w-1/6">
<DeptTree @select="handleDeptSelect" />
<DeptTreeSelect @select="handleDeptSelect" />
</ElCard>
<!-- 右侧用户列表 -->
<div class="w-5/6">

View File

@ -232,6 +232,19 @@ const MES_DICT = {
MES_WM_MISC_RECEIPT_STATUS: 'mes_wm_misc_receipt_status', // MES 杂项入库单状态
MES_WM_OUTSOURCE_ISSUE_STATUS: 'mes_wm_outsource_issue_status', // MES 外协发料单状态
MES_WM_OUTSOURCE_RECEIPT_STATUS: 'mes_wm_outsource_receipt_status', // MES 外协入库单状态
MES_WM_ARRIVAL_NOTICE_STATUS: 'mes_wm_arrival_notice_status', // MES 到货通知单状态
MES_WM_ITEM_RECEIPT_STATUS: 'mes_wm_item_receipt_status', // MES 采购入库单状态
MES_WM_RETURN_VENDOR_STATUS: 'mes_wm_return_vendor_status', // MES 供应商退货单状态
MES_WM_SALES_NOTICE_STATUS: 'mes_wm_sales_notice_status', // MES 发货通知单状态
MES_WM_RETURN_SALES_STATUS: 'mes_wm_return_sales_status', // MES 销售退货单状态
MES_WM_RETURN_ISSUE_STATUS: 'mes_wm_return_issue_status', // MES 生产退料单状态
MES_WM_RETURN_ISSUE_TYPE: 'mes_wm_return_issue_type', // MES 退料类型
MES_WM_PRODUCT_ISSUE_STATUS: 'mes_wm_product_issue_status', // MES 领料出库单状态
MES_WM_PRODUCT_RECEIPT_STATUS: 'mes_wm_product_receipt_status', // MES 产品入库单状态
MES_WM_STOCK_TAKING_TYPE: 'mes_wm_stock_taking_type', // MES 盘点类型
MES_WM_STOCK_TAKING_TASK_STATUS: 'mes_wm_stock_taking_task_status', // MES 盘点任务状态
MES_WM_STOCK_TAKING_LINE_STATUS: 'mes_wm_stock_taking_task_line_status', // MES 盘点任务行状态
MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE: 'mes_wm_stock_taking_plan_param_type', // MES 盘点方案参数类型
} as const;
/** ========== WMS - 仓储管理模块 ========== */