385 lines
14 KiB
Vue
385 lines
14 KiB
Vue
<template>
|
||
<div>
|
||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||
<template #tableTitle>
|
||
<a-button type="primary" v-auth="'mes:mes_raw_material_inspect_std:add'" @click="handleAdd" preIcon="ant-design:plus-outlined">
|
||
新增
|
||
</a-button>
|
||
<JDictSelectTag
|
||
v-model:value="printDotWsUrl"
|
||
dictCode="xslmes_print_dot_ws"
|
||
:showChooseOption="false"
|
||
style="width: 280px; margin-left: 8px"
|
||
placeholder="选择 PrintDot 地址"
|
||
@change="onPrintDotWsUrlChange"
|
||
/>
|
||
<a-button v-if="!printDotConnected" style="margin-left: 8px" @click="downloadPrintPlugin">下载打印插件</a-button>
|
||
<a-select
|
||
v-model:value="selectedPrinterName"
|
||
:options="printerOptions"
|
||
style="width: 220px; margin-left: 8px"
|
||
allow-clear
|
||
show-search
|
||
option-filter-prop="label"
|
||
:placeholder="printerSelectPlaceholder"
|
||
/>
|
||
<a-button style="margin-left: 8px" @click="() => refreshPrinterOptions(true)">刷新打印机</a-button>
|
||
<a-button
|
||
type="primary"
|
||
ghost
|
||
v-auth="'mes:mes_raw_material_inspect_std:edit'"
|
||
:loading="printLoading"
|
||
:disabled="selectedRowKeys.length === 0"
|
||
@click="handlePrintSelected"
|
||
>
|
||
<Icon icon="ant-design:printer-outlined" />
|
||
打印选中
|
||
</a-button>
|
||
</template>
|
||
<template #action="{ record }">
|
||
<TableAction
|
||
:actions="getTableAction(record)"
|
||
:dropDownActions="getDropDownAction(record)"
|
||
/>
|
||
</template>
|
||
</BasicTable>
|
||
<MesRawMaterialInspectStdModal @register="registerModal" @success="reload" />
|
||
<MesRawMaterialInspectStdPrintPreviewModal
|
||
v-model:open="printPreviewOpen"
|
||
:std-id="printPreviewStdId"
|
||
:standard-no="printPreviewStandardNo"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<script lang="ts" setup>
|
||
import { onMounted, ref, watch } from 'vue';
|
||
import { Icon } from '/@/components/Icon';
|
||
import { BasicTable, TableAction } from '/@/components/Table';
|
||
import { useModal } from '/@/components/Modal';
|
||
import { useListPage } from '/@/hooks/system/useListPage';
|
||
import { initDictOptions } from '/@/utils/dict';
|
||
import { JDictSelectTag } from '/@/components/Form';
|
||
import MesRawMaterialInspectStdModal from './modules/MesRawMaterialInspectStdModal.vue';
|
||
import MesRawMaterialInspectStdPrintPreviewModal from './modules/MesRawMaterialInspectStdPrintPreviewModal.vue';
|
||
import { columns, searchFormSchema } from './MesRawMaterialInspectStd.data';
|
||
import { list, deleteOne, setEnable, prepareNativePrint } from './MesRawMaterialInspectStd.api';
|
||
import { useMessage } from '/@/hooks/web/useMessage';
|
||
import {
|
||
PRINT_TEMPLATE_SELECTED_PRINTER_KEY,
|
||
printNativeSchemaViaPrintDot,
|
||
} from '/@/views/print/template/utils/printNativeViaPrintDot';
|
||
import { normalizeImportedNativeSchema } from '/@/views/print/template/native/core/nativeSchemaNormalize';
|
||
import {
|
||
fetchPrintDotPrinters,
|
||
getPrintDotBridgeConfig,
|
||
setPrintDotBridgeConfig,
|
||
} from '/@/views/print/template/utils/printDotBridge';
|
||
|
||
const { createMessage } = useMessage();
|
||
const PRINT_DOT_WS_DICT = 'xslmes_print_dot_ws';
|
||
const printDotWsUrl = ref('');
|
||
const printDotConnected = ref(false);
|
||
|
||
function persistPrintDotConfig() {
|
||
setPrintDotBridgeConfig(String(printDotWsUrl.value || '').trim(), '');
|
||
void refreshPrinterOptions(false);
|
||
}
|
||
|
||
function onPrintDotWsUrlChange() {
|
||
printDotConnected.value = false;
|
||
persistPrintDotConfig();
|
||
}
|
||
|
||
function downloadPrintPlugin() {
|
||
const base = import.meta.env.BASE_URL || '/';
|
||
const normalizedBase = base.endsWith('/') ? base : `${base}/`;
|
||
const url = `${normalizedBase}print-plugin/XSL-PrintDot.exe`;
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.setAttribute('download', 'XSL-PrintDot.exe');
|
||
link.rel = 'noopener';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
}
|
||
|
||
const [registerModal, { openModal }] = useModal();
|
||
const { tableContext } = useListPage({
|
||
tableProps: {
|
||
title: '原材料检验标准',
|
||
api: list,
|
||
columns,
|
||
canResize: true,
|
||
formConfig: { labelWidth: 100, schemas: searchFormSchema, autoSubmitOnEnter: true },
|
||
actionColumn: { width: 280, fixed: 'right' },
|
||
},
|
||
});
|
||
const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||
|
||
const printPreviewOpen = ref(false);
|
||
const printPreviewStdId = ref<string | null>(null);
|
||
const printPreviewStandardNo = ref<string | undefined>(undefined);
|
||
|
||
function handlePrintPreview(record: Recordable) {
|
||
printPreviewStdId.value = record.id as string;
|
||
printPreviewStandardNo.value = record.standardNo as string | undefined;
|
||
printPreviewOpen.value = true;
|
||
}
|
||
|
||
const printerOptions = ref<Array<{ label: string; value: string }>>([]);
|
||
const selectedPrinterName = ref<string>('__system_default__');
|
||
const printLoading = ref(false);
|
||
const PRINT_ROW_LOADING_KEY = 'mesRawMaterialInspectStd-print-row';
|
||
const PRINT_BATCH_LOADING_KEY = 'mesRawMaterialInspectStd-print-batch';
|
||
const printerSelectPlaceholder = '选择打印机(PrintDot 桥接)';
|
||
|
||
watch(selectedPrinterName, (v) => {
|
||
if (v) {
|
||
localStorage.setItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY, v);
|
||
}
|
||
});
|
||
|
||
async function refreshPrinterOptions(showMessage = true) {
|
||
const optionMap = new Map<string, { label: string; value: string }>();
|
||
optionMap.set('__system_default__', { label: '系统默认打印机', value: '__system_default__' });
|
||
try {
|
||
const dotList = await fetchPrintDotPrinters();
|
||
printDotConnected.value = true;
|
||
dotList.forEach((p) => {
|
||
const name = String(p.name || '').trim();
|
||
if (!name) return;
|
||
const defMark = p.isDefault ? '(默认)' : '';
|
||
optionMap.set(name, { label: `${name}${defMark}`, value: name });
|
||
});
|
||
printerOptions.value = Array.from(optionMap.values());
|
||
if (showMessage) {
|
||
if (dotList.length) {
|
||
createMessage.success(`已从 PrintDot 桥接识别 ${dotList.length} 台打印机`);
|
||
} else {
|
||
createMessage.warning('PrintDot 已连接但未返回打印机列表');
|
||
}
|
||
}
|
||
} catch (e: unknown) {
|
||
printDotConnected.value = false;
|
||
printerOptions.value = Array.from(optionMap.values());
|
||
if (showMessage) {
|
||
createMessage.warning(`PrintDot:${e instanceof Error ? e.message : String(e)}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
async function executePrint(record: Recordable, options?: { silentSuccess?: boolean }) {
|
||
try {
|
||
const prep = (await prepareNativePrint(record.id as string)) as Record<string, unknown>;
|
||
const templateJsonRaw = prep.templateJson as string;
|
||
const printData = prep.printData as Record<string, unknown>;
|
||
const paperWidthMm = Number((prep as any).paperWidthMm ?? 0);
|
||
const paperHeightMm = Number((prep as any).paperHeightMm ?? 0);
|
||
const paperOrientation = String((prep as any).paperOrientation || '').toLowerCase();
|
||
if (!templateJsonRaw) {
|
||
throw new Error('模板 JSON 为空');
|
||
}
|
||
let raw: unknown;
|
||
try {
|
||
raw = typeof templateJsonRaw === 'string' ? JSON.parse(templateJsonRaw) : templateJsonRaw;
|
||
} catch {
|
||
throw new Error('模板 JSON 格式错误');
|
||
}
|
||
const schema = normalizeImportedNativeSchema(raw);
|
||
if (paperWidthMm > 0 && paperHeightMm > 0) {
|
||
const orient = paperOrientation === 'landscape' ? 'landscape' : paperOrientation === 'portrait' ? 'portrait' : '';
|
||
const normalized =
|
||
orient === 'landscape'
|
||
? {
|
||
width: Math.max(paperWidthMm, paperHeightMm),
|
||
height: Math.min(paperWidthMm, paperHeightMm),
|
||
}
|
||
: orient === 'portrait'
|
||
? {
|
||
width: Math.min(paperWidthMm, paperHeightMm),
|
||
height: Math.max(paperWidthMm, paperHeightMm),
|
||
}
|
||
: {
|
||
width: paperWidthMm,
|
||
height: paperHeightMm,
|
||
};
|
||
schema.page.width = normalized.width;
|
||
schema.page.height = normalized.height;
|
||
}
|
||
const no = String(record.standardNo || '').trim();
|
||
await printNativeSchemaViaPrintDot({
|
||
schema,
|
||
data: printData as Record<string, unknown>,
|
||
jobName: `原材料检验标准-${no || record.id}.pdf`,
|
||
printerSelection:
|
||
selectedPrinterName.value ||
|
||
localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY) ||
|
||
'__system_default__',
|
||
});
|
||
if (!options?.silentSuccess) {
|
||
createMessage.success('已通过 PrintDot 提交打印');
|
||
}
|
||
} catch (e: unknown) {
|
||
throw new Error(e instanceof Error ? e.message : String(e));
|
||
}
|
||
}
|
||
|
||
function handlePrintSelected() {
|
||
const rows = selectedRows.value || [];
|
||
if (!rows.length) {
|
||
createMessage.warning('请至少勾选一条记录后再点击「打印选中」');
|
||
return;
|
||
}
|
||
printLoading.value = true;
|
||
createMessage.destroy(PRINT_BATCH_LOADING_KEY);
|
||
createMessage.loading({
|
||
content: `正在打印 ${rows.length} 条记录,请稍候…`,
|
||
key: PRINT_BATCH_LOADING_KEY,
|
||
duration: 0,
|
||
});
|
||
(async () => {
|
||
try {
|
||
let ok = 0;
|
||
let firstError = '';
|
||
for (const row of rows) {
|
||
try {
|
||
await executePrint(row, { silentSuccess: true });
|
||
ok += 1;
|
||
} catch (e: unknown) {
|
||
if (!firstError) {
|
||
firstError = e instanceof Error ? e.message : String(e);
|
||
}
|
||
}
|
||
}
|
||
if (ok === rows.length) {
|
||
createMessage.success(`已通过 PrintDot 提交 ${ok} 条打印任务`);
|
||
} else {
|
||
createMessage.warning(
|
||
`打印完成:成功 ${ok},失败 ${rows.length - ok}${firstError ? `。首条错误:${firstError}` : ''}`,
|
||
);
|
||
}
|
||
} finally {
|
||
createMessage.destroy(PRINT_BATCH_LOADING_KEY);
|
||
printLoading.value = false;
|
||
}
|
||
})();
|
||
}
|
||
|
||
async function handlePrintRow(record: Recordable) {
|
||
printLoading.value = true;
|
||
createMessage.destroy(PRINT_ROW_LOADING_KEY);
|
||
createMessage.loading({
|
||
content: '正在生成 PDF 并提交打印,版面复杂时可能需数十秒,请稍候…',
|
||
key: PRINT_ROW_LOADING_KEY,
|
||
duration: 0,
|
||
});
|
||
try {
|
||
await executePrint(record, { silentSuccess: true });
|
||
createMessage.success('已通过 PrintDot 提交打印');
|
||
} catch (e: unknown) {
|
||
createMessage.error(e instanceof Error ? e.message : String(e));
|
||
} finally {
|
||
createMessage.destroy(PRINT_ROW_LOADING_KEY);
|
||
printLoading.value = false;
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
const cfg = getPrintDotBridgeConfig();
|
||
setPrintDotBridgeConfig(cfg.wsUrl, '');
|
||
printDotWsUrl.value = cfg.wsUrl || '';
|
||
|
||
try {
|
||
const raw = await initDictOptions(PRINT_DOT_WS_DICT);
|
||
const items = Array.isArray(raw) ? raw : [];
|
||
const values = items
|
||
.map((it: Recordable) => String(it.value ?? it.itemValue ?? '').trim())
|
||
.filter(Boolean);
|
||
const valueSet = new Set(values);
|
||
if (valueSet.size && printDotWsUrl.value && !valueSet.has(String(printDotWsUrl.value).trim())) {
|
||
printDotWsUrl.value = values[0];
|
||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||
} else if (valueSet.size && !printDotWsUrl.value.trim()) {
|
||
printDotWsUrl.value = values[0];
|
||
setPrintDotBridgeConfig(printDotWsUrl.value, '');
|
||
}
|
||
} catch {
|
||
/* 字典未配置时沿用 localStorage */
|
||
}
|
||
|
||
const savedPrinter = localStorage.getItem(PRINT_TEMPLATE_SELECTED_PRINTER_KEY);
|
||
if (savedPrinter) {
|
||
selectedPrinterName.value = savedPrinter;
|
||
}
|
||
await refreshPrinterOptions(false);
|
||
});
|
||
|
||
function handleAdd() {
|
||
openModal(true, { isUpdate: false, showFooter: true });
|
||
}
|
||
function handleEdit(record: Recordable) {
|
||
openModal(true, { record, isUpdate: true, showFooter: true });
|
||
}
|
||
function handleDetail(record: Recordable) {
|
||
openModal(true, { record, isUpdate: true, showFooter: false });
|
||
}
|
||
async function handleDelete(record) {
|
||
await deleteOne({ id: record.id }, reload);
|
||
}
|
||
async function handleEnable(record: Recordable) {
|
||
await setEnable({ id: record.id, enableFlag: 1 });
|
||
createMessage.success('已启用');
|
||
reload();
|
||
}
|
||
async function handleDisable(record: Recordable) {
|
||
await setEnable({ id: record.id, enableFlag: 0 });
|
||
createMessage.success('已停用');
|
||
reload();
|
||
}
|
||
|
||
function getTableAction(record: Recordable) {
|
||
return [
|
||
{
|
||
label: '编辑',
|
||
onClick: handleEdit.bind(null, record),
|
||
auth: 'mes:mes_raw_material_inspect_std:edit',
|
||
},
|
||
{
|
||
label: '打印预览',
|
||
onClick: handlePrintPreview.bind(null, record),
|
||
auth: 'mes:mes_raw_material_inspect_std:edit',
|
||
},
|
||
{
|
||
label: '打印',
|
||
onClick: handlePrintRow.bind(null, record),
|
||
auth: 'mes:mes_raw_material_inspect_std:edit',
|
||
},
|
||
];
|
||
}
|
||
|
||
function getDropDownAction(record: Recordable) {
|
||
const actions: any[] = [
|
||
{ label: '详情', onClick: handleDetail.bind(null, record) },
|
||
{
|
||
label: '删除',
|
||
popConfirm: { title: '是否确认删除', confirm: handleDelete.bind(null, record) },
|
||
auth: 'mes:mes_raw_material_inspect_std:delete',
|
||
},
|
||
];
|
||
if (record.enableFlag !== 1) {
|
||
actions.push({
|
||
label: '启用',
|
||
auth: 'mes:mes_raw_material_inspect_std:enable',
|
||
popConfirm: { title: '启用后将记录当前时间到生效日期', confirm: () => handleEnable(record) },
|
||
});
|
||
} else {
|
||
actions.push({
|
||
label: '停用',
|
||
auth: 'mes:mes_raw_material_inspect_std:enable',
|
||
popConfirm: { title: '停用后将清除生效日期', confirm: () => handleDisable(record) },
|
||
});
|
||
}
|
||
return actions;
|
||
}
|
||
</script>
|