| | |
| | | import { MesProBagCodeStatusEnum } from "@vben/constants"; |
| | | import { downloadFileFromBlobPart } from "@vben/utils"; |
| | | |
| | | import { message, Modal } from "ant-design-vue"; |
| | | import { Button, message, Modal } from "ant-design-vue"; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from "#/adapter/vxe-table"; |
| | | import { |
| | | deleteBagCodesByUuid, |
| | | exportBagCodeExcel, |
| | | exportBagQr, |
| | | exportBagBusinessQr, |
| | | generateBagBusinessCodes, |
| | | getBagCodePage, |
| | | bindBagCodeBatch, |
| | | unbindBagCodePallet, |
| | | } from "#/api/mes/pro/bagCode"; |
| | | import { IconifyIcon } from "@vben/icons"; |
| | | import { $t } from "#/locales"; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from "./data"; |
| | |
| | | import RawGenerateModal from "./modules/raw-generate-form.vue"; |
| | | import ReplaceModal from "./modules/replace-form.vue"; |
| | | import QrTraceModal from "../components/qr-trace-modal.vue"; |
| | | import BusinessQrModal from "../components/business-qr.vue"; |
| | | import QrExportProgress from "../qrexport/qr-export-progress.vue"; |
| | | import { useQrExport } from "../qrexport/use-qr-export"; |
| | | |
| | | defineOptions({ name: "MesProBagCode" }); |
| | | |
| | |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [BusinessQrViewModal, businessQrApi] = useVbenModal({ |
| | | connectedComponent: BusinessQrModal, destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleBusinessQr(row: MesProBagCodeApi.BagCode) { |
| | | if (!row.businessCode) { message.warning('请先补生成业务码'); return; } |
| | | businessQrApi.setData({ code: row.businessCode }).open(); |
| | | } |
| | | |
| | | async function handleGenerateBusinessCodes() { |
| | | const records = getSelectedRecords(); |
| | | if (!records) return; |
| | | if (records.length > 200) { message.warning('单次最多补生成 200 条'); return; } |
| | | if (records.some(row => row.status === MesProBagCodeStatusEnum.CANCELED)) { |
| | | message.warning('已作废袋码不可补生成业务码'); return; |
| | | } |
| | | const ids = records.map(row => row.id).filter((id): id is number => id != null); |
| | | if (ids.length !== records.length) { message.warning('所选袋码缺少编号'); return; } |
| | | const count = await generateBagBusinessCodes(ids); |
| | | message.success(`已补生成 ${count} 份业务码,已有码值保持不变`); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | const { start: startQrExport } = useQrExport(); |
| | | |
| | | /** 生成数据包 */ |
| | | function handleGenerate() { |
| | |
| | | } |
| | | |
| | | /** 导出二维码 ZIP(按当前查询条件的生成组/批次) */ |
| | | async function handleExportQr() { |
| | | async function handleExportQr(kind: 'trace' | 'business' = 'trace') { |
| | | const values = await gridApi.formApi.getValues(); |
| | | const params: { uuid?: string; batchId?: number } = {}; |
| | | if (values.uuid) { |
| | |
| | | message.warning("请先按生成组或批次筛选后再导出二维码"); |
| | | return; |
| | | } |
| | | const data = await exportBagQr(params); |
| | | downloadFileFromBlobPart({ fileName: "袋码二维码.zip", source: data }); |
| | | // 数量少则同步秒出;超过分界线自动转后台任务,由进度浮层展示真实进度并支持取消/隐藏 |
| | | const bizType = kind === "business" ? "BAG_BUSINESS" : "BAG_LABEL"; |
| | | const fileName = kind === "business" ? "袋码业务二维码" : "袋码追溯二维码"; |
| | | await startQrExport( |
| | | { bizType, ...params }, |
| | | { |
| | | fileName, |
| | | syncDownload: async () => { |
| | | const data = await (kind === "business" |
| | | ? exportBagBusinessQr |
| | | : exportBagQr)(params); |
| | | downloadFileFromBlobPart({ |
| | | fileName: `${fileName}.zip`, |
| | | source: data, |
| | | }); |
| | | }, |
| | | }, |
| | | ); |
| | | } |
| | | |
| | | /** 导出袋码 Excel(交付印刷),导出后袋码状态变为已印刷,需刷新列表 */ |
| | |
| | | function handleBindPallet() { |
| | | const records = getSelectedRecords(); |
| | | if (!records) return; |
| | | if (records.some((record) => !record.batchId)) { |
| | | message.warning("请先录入生产批次,再绑定托盘"); |
| | | // 已录入批次的袋码只能绑到本批次的托盘;未录入批次的袋码可绑任意托盘(选到正式托盘时后端会先补录批次)。 |
| | | // 两者混选时批次归属不明确,要求分开操作。 |
| | | const boundRecords = records.filter((record) => record.batchId); |
| | | if (boundRecords.length > 0 && boundRecords.length < records.length) { |
| | | message.warning("所选袋码中既有已录入批次、也有未录入批次的记录,请分开绑定"); |
| | | return; |
| | | } |
| | | const batchIds = new Set(records.map((record) => record.batchId)); |
| | | const batchIds = new Set(boundRecords.map((record) => record.batchId)); |
| | | if (batchIds.size > 1) { |
| | | message.warning("请选择同一批次下的袋码进行绑定"); |
| | | return; |
| | |
| | | .setData({ |
| | | batchId: first.batchId, |
| | | batchCode: first.batchCode, |
| | | itemId: first.itemId, |
| | | ids: records.map((record) => record.id!), |
| | | }) |
| | | .open(); |
| | |
| | | <BindPalletFormModal @success="handleRefresh" /> |
| | | <ReplaceFormModal @success="handleReplaceSuccess" /> |
| | | <QrTraceQueryModal /> |
| | | <BusinessQrViewModal /> |
| | | <QrExportProgress /> |
| | | <Grid table-title="袋码列表(一袋一码)"> |
| | | <template #table-title><span class="text-[1rem] font-bold" style="white-space:nowrap">袋码列表(一袋一码)</span></template> |
| | | <template #toolbar-tools> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: '生成数据包', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-bag-code:create'], |
| | | onClick: handleGenerate, |
| | | }, |
| | | { |
| | | label: '生成原始袋码', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-bag-code:create'], |
| | | onClick: handleRawGenerate, |
| | | }, |
| | | { |
| | | label: '录入批次', |
| | | type: 'primary', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleBindBatch, |
| | | }, |
| | | { |
| | | label: '绑定托盘', |
| | | type: 'primary', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleBindPallet, |
| | | }, |
| | | { |
| | | label: '破损替换', |
| | | type: 'primary', |
| | | auth: ['mes:pro-bag-code:replace'], |
| | | onClick: handleReplace, |
| | | }, |
| | | { |
| | | label: '解绑托盘', |
| | | auth: ['mes:pro-bag-code:update'], |
| | | onClick: handleUnbindPallet, |
| | | }, |
| | | { |
| | | label: '扫码追溯', |
| | | icon: ACTION_ICON.SEARCH, |
| | | auth: ['mes:pro-bag-code:query'], |
| | | onClick: handleTrace, |
| | | }, |
| | | { |
| | | label: '导出二维码', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-bag-code:export'], |
| | | onClick: handleExportQr, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mes:pro-bag-code:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" /> |
| | | <div class="mes-code-toolbar"> |
| | | <TableAction :actions="[{ label: '生成数据包', type: 'primary', icon: ACTION_ICON.ADD, auth: ['mes:pro-bag-code:create'], onClick: handleGenerate }, |
| | | { label: '录入批次', auth: ['mes:pro-bag-code:update'], onClick: handleBindBatch }, |
| | | { label: '绑定托盘', auth: ['mes:pro-bag-code:update'], onClick: handleBindPallet }, |
| | | { label: '扫码追溯', icon: ACTION_ICON.SEARCH, auth: ['mes:pro-bag-code:query'], onClick: handleTrace }]" /> |
| | | <TableAction :dropdown-trigger="['click']" :drop-down-actions="[{ label: '追溯二维码', auth: ['mes:pro-bag-code:export'], onClick: () => handleExportQr('trace') }, |
| | | { label: '业务二维码', auth: ['mes:pro-bag-code:export'], onClick: () => handleExportQr('business') }, |
| | | { label: 'Excel 数据包', auth: ['mes:pro-bag-code:export'], onClick: handleExport }]"> |
| | | <template #more><Button>导出 <IconifyIcon icon="lucide:chevron-down" /></Button></template> |
| | | </TableAction> |
| | | <TableAction :dropdown-trigger="['click']" :drop-down-actions="[{ label: '生成原始袋码', auth: ['mes:pro-bag-code:create'], onClick: handleRawGenerate }, |
| | | { label: '补生成业务码', auth: ['mes:pro-bag-code:create'], onClick: handleGenerateBusinessCodes }, |
| | | { label: '破损替换', auth: ['mes:pro-bag-code:replace'], onClick: handleReplace }, |
| | | { label: '解绑托盘', auth: ['mes:pro-bag-code:update'], onClick: handleUnbindPallet }]"> |
| | | <template #more><Button>更多操作 <IconifyIcon icon="lucide:chevron-down" /></Button></template> |
| | | </TableAction> |
| | | </div> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction :actions="[ |
| | | { |
| | | label: '查看二维码', |
| | | label: '追溯二维码', |
| | | type: 'link', |
| | | auth: ['mes:pro-bag-code:query'], |
| | | onClick: handleViewQr.bind(null, row), |
| | | }, |
| | | { |
| | | label: '业务二维码', type: 'link', auth: ['mes:pro-bag-code:query'], |
| | | ifShow: !!row.businessCode, onClick: handleBusinessQr.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .mes-code-toolbar { display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px; } |
| | | .mes-code-toolbar :deep(.ant-btn) { white-space:nowrap; } |
| | | .mes-code-toolbar :deep(.ant-btn > .iconify) { display:inline-block;width:14px;height:14px;vertical-align:middle; } |
| | | </style> |