415 lines
12 KiB
Vue
415 lines
12 KiB
Vue
|
|
<template>
|
|||
|
|
<div>
|
|||
|
|
<BasicTable @register="registerTable">
|
|||
|
|
<template #tableTitle>
|
|||
|
|
<a-button type="primary" @click="openCreate" v-auth="'print:bizBind:add'">新增绑定</a-button>
|
|||
|
|
</template>
|
|||
|
|
<template #action="{ record }">
|
|||
|
|
<TableAction
|
|||
|
|
:actions="[
|
|||
|
|
{
|
|||
|
|
label: '编辑',
|
|||
|
|
onClick: () => openEdit(record),
|
|||
|
|
auth: 'print:bizBind:edit',
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: '删除',
|
|||
|
|
color: 'error',
|
|||
|
|
popConfirm: {
|
|||
|
|
title: '确认删除该绑定?',
|
|||
|
|
confirm: () => handleDelete(record),
|
|||
|
|
},
|
|||
|
|
auth: 'print:bizBind:delete',
|
|||
|
|
},
|
|||
|
|
]"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</BasicTable>
|
|||
|
|
|
|||
|
|
<BasicModal
|
|||
|
|
@register="registerModal"
|
|||
|
|
:title="modalTitle"
|
|||
|
|
width="920px"
|
|||
|
|
@ok="submitModal"
|
|||
|
|
:confirm-loading="modalSubmitLoading"
|
|||
|
|
destroy-on-close
|
|||
|
|
>
|
|||
|
|
<a-spin :spinning="tplLoading || parseLoading">
|
|||
|
|
<a-space direction="vertical" style="width: 100%" size="middle">
|
|||
|
|
<a-alert
|
|||
|
|
type="info"
|
|||
|
|
show-icon
|
|||
|
|
message="配置步骤"
|
|||
|
|
description="1)选择业务类型;2)选择已发布的打印模板;3)为模板每个占位字段(bindField)指定对应的业务 JSON 字段;可点击「同名匹配」快速对齐。"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<a-form layout="vertical">
|
|||
|
|
<a-row :gutter="16">
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="业务" required>
|
|||
|
|
<a-select
|
|||
|
|
v-model:value="form.bizCode"
|
|||
|
|
:options="bizSelectOptions"
|
|||
|
|
placeholder="选择业务"
|
|||
|
|
show-search
|
|||
|
|
option-filter-prop="label"
|
|||
|
|
:disabled="isEditMode"
|
|||
|
|
@change="onBizCodeChange"
|
|||
|
|
/>
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="打印模板" required>
|
|||
|
|
<a-select
|
|||
|
|
v-model:value="form.templateId"
|
|||
|
|
:options="tplSelectOptions"
|
|||
|
|
placeholder="选择模板"
|
|||
|
|
show-search
|
|||
|
|
option-filter-prop="label"
|
|||
|
|
@change="onTemplateChange"
|
|||
|
|
/>
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
</a-row>
|
|||
|
|
<a-form-item label="备注">
|
|||
|
|
<a-input v-model:value="form.remark" placeholder="可选" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-form>
|
|||
|
|
|
|||
|
|
<a-space wrap>
|
|||
|
|
<a-button type="primary" ghost @click="reloadTemplateFields" :loading="parseLoading">
|
|||
|
|
解析模板占位字段
|
|||
|
|
</a-button>
|
|||
|
|
<a-button @click="autoMatchFields" :disabled="!bizFields.length || !tplFields.length">
|
|||
|
|
同名自动匹配
|
|||
|
|
</a-button>
|
|||
|
|
</a-space>
|
|||
|
|
|
|||
|
|
<div v-if="tplFields.length">
|
|||
|
|
<div style="margin-bottom: 8px; font-weight: 500">字段映射</div>
|
|||
|
|
<a-table
|
|||
|
|
size="small"
|
|||
|
|
row-key="templateField"
|
|||
|
|
:pagination="false"
|
|||
|
|
:columns="mapTableColumns"
|
|||
|
|
:data-source="mappingRows"
|
|||
|
|
bordered
|
|||
|
|
>
|
|||
|
|
<template #bodyCell="{ column, record }">
|
|||
|
|
<template v-if="column.key === 'bizField'">
|
|||
|
|
<a-select
|
|||
|
|
v-model:value="record.bizField"
|
|||
|
|
:options="bizFieldOptions"
|
|||
|
|
allow-clear
|
|||
|
|
show-search
|
|||
|
|
option-filter-prop="label"
|
|||
|
|
style="width: 100%"
|
|||
|
|
placeholder="选择业务字段"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
</template>
|
|||
|
|
</a-table>
|
|||
|
|
</div>
|
|||
|
|
<a-empty v-else-if="form.templateId && !parseLoading" description="请点击「解析模板占位字段」或切换模板" />
|
|||
|
|
|
|||
|
|
<a-divider />
|
|||
|
|
<div style="font-weight: 500">映射预览(可选)</div>
|
|||
|
|
<a-textarea
|
|||
|
|
v-model:value="previewBizJson"
|
|||
|
|
placeholder='粘贴业务 JSON,例如:{"barcode":"TEST001","materialName":"胶料A"}'
|
|||
|
|
:rows="4"
|
|||
|
|
/>
|
|||
|
|
<a-button type="dashed" @click="runPreview" :loading="previewLoading">生成打印数据预览</a-button>
|
|||
|
|
<pre v-if="previewResult" class="preview-pre">{{ previewResult }}</pre>
|
|||
|
|
</a-space>
|
|||
|
|
</a-spin>
|
|||
|
|
</BasicModal>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts" setup>
|
|||
|
|
import { computed, ref, unref } from 'vue';
|
|||
|
|
import { BasicTable, TableAction, useTable } from '/@/components/Table';
|
|||
|
|
import { BasicModal, useModal } from '/@/components/Modal';
|
|||
|
|
import { useMessage } from '/@/hooks/web/useMessage';
|
|||
|
|
import { columns } from './bizTemplateBind.data';
|
|||
|
|
import * as Api from './bizTemplateBind.api';
|
|||
|
|
import { list as tplList } from '../template/printTemplate.api';
|
|||
|
|
|
|||
|
|
const { createMessage } = useMessage();
|
|||
|
|
|
|||
|
|
interface BizTypeItem {
|
|||
|
|
bizCode: string;
|
|||
|
|
bizName: string;
|
|||
|
|
fields: { fieldKey: string; label: string; description?: string }[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface TplFieldItem {
|
|||
|
|
bindField: string;
|
|||
|
|
elementType?: string;
|
|||
|
|
titleHint?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface MappingRow {
|
|||
|
|
templateField: string;
|
|||
|
|
bizField?: string;
|
|||
|
|
elementType?: string;
|
|||
|
|
titleHint?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const bizTypesRef = ref<BizTypeItem[]>([]);
|
|||
|
|
const tplListRef = ref<{ id: string; templateCode: string; templateName: string }[]>([]);
|
|||
|
|
const tplLoading = ref(false);
|
|||
|
|
const parseLoading = ref(false);
|
|||
|
|
const modalSubmitLoading = ref(false);
|
|||
|
|
const previewLoading = ref(false);
|
|||
|
|
const previewBizJson = ref('');
|
|||
|
|
const previewResult = ref('');
|
|||
|
|
|
|||
|
|
const form = ref({
|
|||
|
|
id: '' as string | undefined,
|
|||
|
|
bizCode: undefined as string | undefined,
|
|||
|
|
bizName: '' as string | undefined,
|
|||
|
|
templateId: undefined as string | undefined,
|
|||
|
|
remark: '' as string | undefined,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const tplFields = ref<TplFieldItem[]>([]);
|
|||
|
|
const bizFields = ref<BizTypeItem['fields']>([]);
|
|||
|
|
const mappingRows = ref<MappingRow[]>([]);
|
|||
|
|
|
|||
|
|
const isEditMode = ref(false);
|
|||
|
|
const modalTitle = computed(() => (unref(isEditMode) ? '编辑业务打印绑定' : '新增业务打印绑定'));
|
|||
|
|
|
|||
|
|
const bizSelectOptions = computed(() =>
|
|||
|
|
unref(bizTypesRef).map((b) => ({
|
|||
|
|
label: `${b.bizName}(${b.bizCode})`,
|
|||
|
|
value: b.bizCode,
|
|||
|
|
})),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const tplSelectOptions = computed(() =>
|
|||
|
|
unref(tplListRef).map((t) => ({
|
|||
|
|
label: `${t.templateName}(${t.templateCode})`,
|
|||
|
|
value: t.id,
|
|||
|
|
})),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const bizFieldOptions = computed(() =>
|
|||
|
|
unref(bizFields).map((f) => ({
|
|||
|
|
label: f.label ? `${f.label}(${f.fieldKey})` : f.fieldKey,
|
|||
|
|
value: f.fieldKey,
|
|||
|
|
})),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const mapTableColumns = [
|
|||
|
|
{ title: '模板占位(bindField)', dataIndex: 'templateField', width: 220 },
|
|||
|
|
{ title: '类型', dataIndex: 'elementType', width: 100 },
|
|||
|
|
{ title: '标题/提示', dataIndex: 'titleHint', ellipsis: true },
|
|||
|
|
{ title: '业务字段', key: 'bizField', width: 260 },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const [registerTable, { reload }] = useTable({
|
|||
|
|
title: '业务打印绑定',
|
|||
|
|
api: Api.list,
|
|||
|
|
columns,
|
|||
|
|
useSearchForm: false,
|
|||
|
|
showTableSetting: true,
|
|||
|
|
bordered: true,
|
|||
|
|
showIndexColumn: true,
|
|||
|
|
// 必须与模板插槽 #action 对应,否则操作列不会渲染(useTable 无 useListPage 的默认 slots)
|
|||
|
|
actionColumn: {
|
|||
|
|
width: 160,
|
|||
|
|
title: '操作',
|
|||
|
|
fixed: 'right',
|
|||
|
|
dataIndex: 'action',
|
|||
|
|
slots: { customRender: 'action' },
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [registerModal, { openModal, closeModal }] = useModal();
|
|||
|
|
|
|||
|
|
async function loadBizTypes() {
|
|||
|
|
const res = await Api.bizTypes();
|
|||
|
|
bizTypesRef.value = res || [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function loadAllTemplates() {
|
|||
|
|
tplLoading.value = true;
|
|||
|
|
try {
|
|||
|
|
const res = await tplList({ pageNo: 1, pageSize: 500 });
|
|||
|
|
tplListRef.value = res?.records ?? [];
|
|||
|
|
} finally {
|
|||
|
|
tplLoading.value = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function onBizCodeChange(code: string) {
|
|||
|
|
const hit = unref(bizTypesRef).find((b) => b.bizCode === code);
|
|||
|
|
bizFields.value = hit?.fields ?? [];
|
|||
|
|
form.value.bizName = hit?.bizName;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function onTemplateChange() {
|
|||
|
|
tplFields.value = [];
|
|||
|
|
mappingRows.value = [];
|
|||
|
|
await reloadTemplateFields();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function reloadTemplateFields() {
|
|||
|
|
const tid = form.value.templateId;
|
|||
|
|
if (!tid) {
|
|||
|
|
tplFields.value = [];
|
|||
|
|
mappingRows.value = [];
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
parseLoading.value = true;
|
|||
|
|
try {
|
|||
|
|
const list = (await Api.parseTemplateFields(tid)) as TplFieldItem[];
|
|||
|
|
tplFields.value = list || [];
|
|||
|
|
rebuildMappingRows();
|
|||
|
|
} finally {
|
|||
|
|
parseLoading.value = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function rebuildMappingRows() {
|
|||
|
|
const saved = unref(savedMappingRef);
|
|||
|
|
mappingRows.value = unref(tplFields).map((t) => {
|
|||
|
|
const templateField = t.bindField;
|
|||
|
|
const hit = saved.find((x) => x.templateField === templateField);
|
|||
|
|
return {
|
|||
|
|
templateField,
|
|||
|
|
bizField: hit?.bizField,
|
|||
|
|
elementType: t.elementType,
|
|||
|
|
titleHint: t.titleHint,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const savedMappingRef = ref<{ templateField: string; bizField?: string }[]>([]);
|
|||
|
|
|
|||
|
|
function autoMatchFields() {
|
|||
|
|
const set = new Map(unref(bizFields).map((f) => [f.fieldKey, f.fieldKey]));
|
|||
|
|
for (const row of unref(mappingRows)) {
|
|||
|
|
if (set.has(row.templateField)) {
|
|||
|
|
row.bizField = row.templateField;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
mappingRows.value = [...unref(mappingRows)];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function buildFieldMappingJson() {
|
|||
|
|
const arr = unref(mappingRows)
|
|||
|
|
.filter((r) => r.bizField)
|
|||
|
|
.map((r) => ({ templateField: r.templateField, bizField: r.bizField }));
|
|||
|
|
return JSON.stringify(arr);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function openCreate() {
|
|||
|
|
isEditMode.value = false;
|
|||
|
|
savedMappingRef.value = [];
|
|||
|
|
form.value = { id: undefined, bizCode: undefined, bizName: undefined, templateId: undefined, remark: undefined };
|
|||
|
|
tplFields.value = [];
|
|||
|
|
bizFields.value = [];
|
|||
|
|
mappingRows.value = [];
|
|||
|
|
previewBizJson.value = '';
|
|||
|
|
previewResult.value = '';
|
|||
|
|
await loadBizTypes();
|
|||
|
|
await loadAllTemplates();
|
|||
|
|
openModal(true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function openEdit(record: Recordable) {
|
|||
|
|
isEditMode.value = true;
|
|||
|
|
await loadBizTypes();
|
|||
|
|
await loadAllTemplates();
|
|||
|
|
form.value = {
|
|||
|
|
id: record.id,
|
|||
|
|
bizCode: record.bizCode,
|
|||
|
|
bizName: record.bizName,
|
|||
|
|
templateId: record.templateId,
|
|||
|
|
remark: record.remark,
|
|||
|
|
};
|
|||
|
|
onBizCodeChange(record.bizCode);
|
|||
|
|
try {
|
|||
|
|
savedMappingRef.value = JSON.parse(record.fieldMappingJson || '[]');
|
|||
|
|
} catch {
|
|||
|
|
savedMappingRef.value = [];
|
|||
|
|
}
|
|||
|
|
previewBizJson.value = '';
|
|||
|
|
previewResult.value = '';
|
|||
|
|
openModal(true);
|
|||
|
|
await reloadTemplateFields();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function submitModal() {
|
|||
|
|
if (!form.value.bizCode) {
|
|||
|
|
createMessage.warning('请选择业务');
|
|||
|
|
return Promise.reject();
|
|||
|
|
}
|
|||
|
|
if (!form.value.templateId) {
|
|||
|
|
createMessage.warning('请选择打印模板');
|
|||
|
|
return Promise.reject();
|
|||
|
|
}
|
|||
|
|
modalSubmitLoading.value = true;
|
|||
|
|
try {
|
|||
|
|
const payload = {
|
|||
|
|
id: form.value.id,
|
|||
|
|
bizCode: form.value.bizCode,
|
|||
|
|
bizName: form.value.bizName,
|
|||
|
|
templateId: form.value.templateId,
|
|||
|
|
remark: form.value.remark,
|
|||
|
|
fieldMappingJson: buildFieldMappingJson(),
|
|||
|
|
};
|
|||
|
|
if (unref(isEditMode)) {
|
|||
|
|
await Api.edit(payload);
|
|||
|
|
} else {
|
|||
|
|
await Api.add(payload);
|
|||
|
|
}
|
|||
|
|
closeModal();
|
|||
|
|
reload();
|
|||
|
|
} finally {
|
|||
|
|
modalSubmitLoading.value = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function handleDelete(record: Recordable) {
|
|||
|
|
await Api.deleteOne({ id: record.id }, () => reload());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function runPreview() {
|
|||
|
|
if (!form.value.bizCode) {
|
|||
|
|
createMessage.warning('请先选择业务;预览读取的是服务器已保存的绑定配置');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
let obj: Record<string, unknown> = {};
|
|||
|
|
try {
|
|||
|
|
obj = previewBizJson.value ? (JSON.parse(previewBizJson.value) as Record<string, unknown>) : {};
|
|||
|
|
} catch {
|
|||
|
|
previewResult.value = '业务 JSON 格式不正确';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
previewLoading.value = true;
|
|||
|
|
try {
|
|||
|
|
const res = await Api.previewMappedData({ bizCode: form.value.bizCode, bizDataJson: obj });
|
|||
|
|
previewResult.value = JSON.stringify(res, null, 2);
|
|||
|
|
} catch (e: unknown) {
|
|||
|
|
previewResult.value = e instanceof Error ? e.message : String(e);
|
|||
|
|
} finally {
|
|||
|
|
previewLoading.value = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.preview-pre {
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
padding: 12px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
max-height: 240px;
|
|||
|
|
overflow: auto;
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
</style>
|