mirror of
https://gitee.com/yudaocode/yudao-ui-admin-vben.git
synced 2025-12-30 10:32:25 +00:00
feat: [bpm][antd] 流程打印自定义模板
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import type { MentionItem } from '../modules/tinymce-plugin';
|
||||
|
||||
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
// @ts-ignore - tinymce vue 声明文件按项目依赖提供
|
||||
import Editor from '@tinymce/tinymce-vue';
|
||||
|
||||
import { setupTinyPlugins } from './tinymce-plugin';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
formFields?: Array<{ field: string; title: string }>;
|
||||
}>(),
|
||||
{
|
||||
formFields: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
/** TinyMCE 自托管:https://www.jianshu.com/p/59a9c3802443 */
|
||||
const tinymceScriptSrc = `${import.meta.env.VITE_BASE}tinymce/tinymce.min.js`;
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
closable: true,
|
||||
footer: false,
|
||||
title: '自定义模板',
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
try {
|
||||
const { template } = modalApi.getData<{
|
||||
template: string;
|
||||
}>();
|
||||
if (template !== undefined) {
|
||||
valueHtml.value = template;
|
||||
}
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handleConfirm = () => {
|
||||
/** 通过 setData 传递确认的数据,在父组件的 onConfirm 中获取 */
|
||||
modalApi.setData({ confirmedTemplate: valueHtml.value as string });
|
||||
modalApi.onConfirm();
|
||||
modalApi.close();
|
||||
};
|
||||
|
||||
// 提供给 @ 自动补全的字段(默认 + 表单字段)
|
||||
const mentionList = computed<MentionItem[]>(() => {
|
||||
const base: MentionItem[] = [
|
||||
{ id: 'startUser', name: '发起人' },
|
||||
{ id: 'startUserDept', name: '发起人部门' },
|
||||
{ id: 'processName', name: '流程名称' },
|
||||
{ id: 'processNum', name: '流程编号' },
|
||||
{ id: 'startTime', name: '发起时间' },
|
||||
{ id: 'endTime', name: '结束时间' },
|
||||
{ id: 'processStatus', name: '流程状态' },
|
||||
{ id: 'printUser', name: '打印人' },
|
||||
{ id: 'printTime', name: '打印时间' },
|
||||
];
|
||||
|
||||
const extras: MentionItem[] = (props.formFields || []).map((it: any) => ({
|
||||
id: it.field,
|
||||
name: `[表单]${it.title}`,
|
||||
}));
|
||||
return [...base, ...extras];
|
||||
});
|
||||
|
||||
// 编辑器
|
||||
const valueHtml = ref<string>('');
|
||||
const editorRef = shallowRef<any>();
|
||||
|
||||
const tinyInit = {
|
||||
height: 400,
|
||||
width: 'auto',
|
||||
menubar: false,
|
||||
plugins: 'link importcss table code preview autoresize lists ',
|
||||
toolbar:
|
||||
'undo redo | styles fontsize | bold italic underline | alignleft aligncenter alignright | link table | processrecord code preview',
|
||||
language: 'zh_CN',
|
||||
branding: false,
|
||||
statusbar: true,
|
||||
content_style:
|
||||
'body { font-family:Helvetica,Arial,sans-serif; font-size:16px }',
|
||||
setup(editor: any) {
|
||||
editorRef.value = editor;
|
||||
// 在编辑器 setup 时注册自定义插件
|
||||
setupTinyPlugins(editor, () => mentionList.value);
|
||||
},
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editorRef.value) {
|
||||
editorRef.value.destroy?.();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-3/4">
|
||||
<div class="mb-3">
|
||||
<a-alert
|
||||
message="输入 @ 可选择插入流程选项和表单选项"
|
||||
type="info"
|
||||
show-icon
|
||||
/>
|
||||
</div>
|
||||
<Editor
|
||||
v-model="valueHtml"
|
||||
:init="tinyInit"
|
||||
:tinymce-script-src="tinymceScriptSrc"
|
||||
license-key="gpl"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<a-button @click="modalApi.onCancel()">取 消</a-button>
|
||||
<a-button type="primary" @click="handleConfirm">确 定</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, provide, ref, watch } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import {
|
||||
BpmAutoApproveType,
|
||||
BpmModelFormType,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Col,
|
||||
Form,
|
||||
@@ -32,6 +34,8 @@ import {
|
||||
parseFormFields,
|
||||
} from '#/views/bpm/components/simple-process-design';
|
||||
|
||||
import PrintTemplate from './custom-print-template.vue';
|
||||
|
||||
const modelData = defineModel<any>();
|
||||
|
||||
/** 自定义 ID 流程编码 */
|
||||
@@ -147,9 +151,9 @@ function handleTaskAfterTriggerEnableChange(val: boolean | number | string) {
|
||||
}
|
||||
|
||||
/** 表单字段 */
|
||||
const formField = ref<Array<{ field: string; title: string }>>([]);
|
||||
const formFields = ref<Array<{ field: string; title: string }>>([]);
|
||||
const formFieldOptions4Title = computed(() => {
|
||||
const cloneFormField = formField.value.map((item) => {
|
||||
const cloneFormField = formFields.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field,
|
||||
@@ -171,7 +175,7 @@ const formFieldOptions4Title = computed(() => {
|
||||
return cloneFormField;
|
||||
});
|
||||
const formFieldOptions4Summary = computed(() => {
|
||||
return formField.value.map((item) => {
|
||||
return formFields.value.map((item) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.field,
|
||||
@@ -192,6 +196,12 @@ function initData() {
|
||||
length: 5,
|
||||
};
|
||||
}
|
||||
if (!modelData.value.printTemplateSetting) {
|
||||
modelData.value.printTemplateSetting = {
|
||||
enable: false,
|
||||
template: '',
|
||||
};
|
||||
}
|
||||
if (!modelData.value.autoApprovalType) {
|
||||
modelData.value.autoApprovalType = BpmAutoApproveType.NONE;
|
||||
}
|
||||
@@ -237,9 +247,9 @@ watch(
|
||||
parseFormFields(JSON.parse(fieldStr), result);
|
||||
});
|
||||
}
|
||||
formField.value = result;
|
||||
formFields.value = result;
|
||||
} else {
|
||||
formField.value = [];
|
||||
formFields.value = [];
|
||||
unParsedFormFields.value = [];
|
||||
}
|
||||
},
|
||||
@@ -252,6 +262,86 @@ async function validate() {
|
||||
await formRef.value?.validate();
|
||||
}
|
||||
|
||||
/** 自定义打印模板模态框 */
|
||||
const [PrintTemplateModal, printTemplateModalApi] = useVbenModal({
|
||||
connectedComponent: PrintTemplate,
|
||||
destroyOnClose: true,
|
||||
onConfirm() {
|
||||
/** 从 modalApi 获取确认的数据 */
|
||||
const { confirmedTemplate } = printTemplateModalApi.getData<{
|
||||
confirmedTemplate: string;
|
||||
}>();
|
||||
if (confirmedTemplate !== undefined) {
|
||||
modelData.value.printTemplateSetting.template = confirmedTemplate;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** 弹出自定义打印模板弹窗 */
|
||||
const openPrintTemplateModal = () => {
|
||||
printTemplateModalApi
|
||||
.setData({ template: modelData.value.printTemplateSetting.template })
|
||||
.open();
|
||||
};
|
||||
|
||||
/** 默认的打印模板, 目前自定义模板没有引入自定义样式。 看后续是否需要 */
|
||||
const defaultTemplate = `<p style="text-align: center;font-size: 1.25rem;"><strong><span data-w-e-type="mention" data-value="流程名称" data-info="%7B%22id%22%3A%22processName%22%7D">@流程名称</span></strong></p>
|
||||
<p style="text-align: right;">打印人员:<span data-w-e-type="mention" data-info="%7B%22id%22%3A%22printUser%22%7D">@打印人</span></p>
|
||||
<p style="text-align: left;">流程编号:<span data-w-e-type="mention" data-value="流程编号" data-info="%7B%22id%22%3A%22processNum%22%7D">@流程编号</span></p>
|
||||
<p> </p>
|
||||
<table style="width: 100%; height: 72.2159px;">
|
||||
<tbody>
|
||||
<tr style="height: 36.108px;">
|
||||
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">发起人</td>
|
||||
<td style="width: 30.5551%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="发起人" data-info="%7B%22id%22%3A%22startUser%22%7D">@发起人</span></td>
|
||||
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">发起时间</td>
|
||||
<td style="width: 26.0284%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="发起时间" data-info="%7B%22id%22%3A%22startTime%22%7D">@发起时间</span></td>
|
||||
</tr>
|
||||
<tr style="height: 36.108px;">
|
||||
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">所属部门</td>
|
||||
<td style="width: 30.5551%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-w-e-is-void="" data-w-e-is-inline="" data-value="发起人部门" data-info="%7B%22id%22%3A%22startUserDept%22%7D">@发起人部门</span></td>
|
||||
<td style="width: 21.7532%; border: 1px solid;" colspan="1" rowspan="1" width="auto">流程状态</td>
|
||||
<td style="width: 26.0284%; border: 1px solid;" colspan="1" rowspan="1" width="auto"><span data-w-e-type="mention" data-value="流程状态" data-info="%7B%22id%22%3A%22processStatus%22%7D">@流程状态</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p> </p>
|
||||
<div contenteditable="false" data-w-e-type="process-record" data-w-e-is-void="">
|
||||
<table class="process-record-table" style="width: 100%; border-collapse: collapse; border: 1px solid;">
|
||||
<tr>
|
||||
<td style="width: 100%; border: 1px solid; text-align: center;" colspan="2">流程记录</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 25%; border: 1px solid;">节点</td>
|
||||
<td style="width: 75%; border: 1px solid;">操作</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p> </p>`;
|
||||
|
||||
const handlePrintTemplateEnableChange = (checked: any) => {
|
||||
const val = !!checked;
|
||||
if (val && !modelData.value.printTemplateSetting.template) {
|
||||
modelData.value.printTemplateSetting.template = defaultTemplate;
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义打印模板开关
|
||||
const printTemplateEnable = computed<boolean>({
|
||||
get() {
|
||||
return !!modelData.value?.printTemplateSetting?.enable;
|
||||
},
|
||||
set(val: boolean) {
|
||||
if (!modelData.value.printTemplateSetting) {
|
||||
modelData.value.printTemplateSetting = {
|
||||
enable: false,
|
||||
template: '',
|
||||
};
|
||||
}
|
||||
modelData.value.printTemplateSetting.enable = !!val;
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({ initData, validate });
|
||||
</script>
|
||||
<template>
|
||||
@@ -515,6 +605,27 @@ defineExpose({ initData, validate });
|
||||
</Col>
|
||||
</Row>
|
||||
</FormItem>
|
||||
<!-- TODO @jason:这里有个 “自定义打印模板” -->
|
||||
<FormItem class="mb-5" label="自定义打印模板">
|
||||
<div class="flex w-full flex-col">
|
||||
<div class="flex items-center">
|
||||
<Switch
|
||||
v-model:checked="printTemplateEnable"
|
||||
@change="handlePrintTemplateEnableChange"
|
||||
/>
|
||||
<Button
|
||||
v-if="printTemplateEnable"
|
||||
class="ml-2 flex items-center"
|
||||
type="link"
|
||||
@click="openPrintTemplateModal"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="lucide:pencil" />
|
||||
</template>
|
||||
编辑模板
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
<PrintTemplateModal :form-fields="formFields" />
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/** TinyMCE 自定义功能:
|
||||
* - processrecord 按钮:插入流程记录占位元素
|
||||
* - @ 自动补全:插入 mention 占位元素
|
||||
*/
|
||||
|
||||
// @ts-ignore TinyMCE 全局或通过打包器提供
|
||||
import type { Editor } from 'tinymce';
|
||||
|
||||
export interface MentionItem {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** 在编辑器 setup 回调中注册流程记录按钮和 @ 自动补全 */
|
||||
export function setupTinyPlugins(
|
||||
editor: Editor,
|
||||
getMentionList: () => MentionItem[],
|
||||
) {
|
||||
// 按钮:流程记录
|
||||
editor.ui.registry.addButton('processrecord', {
|
||||
text: '流程记录',
|
||||
tooltip: '插入流程记录占位',
|
||||
onAction: () => {
|
||||
// 流程记录占位显示, 仅用于显示。process-print.vue 组件中会替换掉
|
||||
editor.insertContent(
|
||||
[
|
||||
'<div data-w-e-type="process-record" data-w-e-is-void contenteditable="false">',
|
||||
'<table class="process-record-table" style="width: 100%; border-collapse: collapse; border: 1px solid;">',
|
||||
'<tr><td style="width: 100%; border: 1px solid; text-align: center;" colspan="2">流程记录</td></tr>',
|
||||
'<tr>',
|
||||
'<td style="width: 25%; border: 1px solid;">节点</td>',
|
||||
'<td style="width: 75%; border: 1px solid;">操作</td>',
|
||||
'</tr>',
|
||||
'</table>',
|
||||
'</div>',
|
||||
].join(''),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// @ 自动补全
|
||||
editor.ui.registry.addAutocompleter('bpmMention', {
|
||||
trigger: '@',
|
||||
minChars: 0,
|
||||
columns: 1,
|
||||
fetch: (
|
||||
pattern: string,
|
||||
_maxResults: number,
|
||||
_fetchOptions: Record<string, any>,
|
||||
) => {
|
||||
const list = getMentionList();
|
||||
const keyword = (pattern || '').toLowerCase().trim();
|
||||
const data = list
|
||||
.filter((i) => i.name.toLowerCase().includes(keyword))
|
||||
.map((i) => ({
|
||||
value: i.id,
|
||||
text: i.name,
|
||||
}));
|
||||
return Promise.resolve(data);
|
||||
},
|
||||
onAction: (
|
||||
autocompleteApi: any,
|
||||
rng: Range,
|
||||
value: string,
|
||||
_meta: Record<string, any>,
|
||||
) => {
|
||||
const list = getMentionList();
|
||||
const item = list.find((i) => i.id === value);
|
||||
const name = item ? item.name : value;
|
||||
const info = encodeURIComponent(JSON.stringify({ id: value }));
|
||||
editor.selection.setRng(rng);
|
||||
editor.insertContent(
|
||||
`<span data-w-e-type="mention" data-info="${info}">@${name}</span>`,
|
||||
);
|
||||
autocompleteApi.hide();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import { registerComponent } from '#/utils';
|
||||
|
||||
import ProcessInstanceBpmnViewer from './modules/bpm-viewer.vue';
|
||||
import ProcessInstanceOperationButton from './modules/operation-button.vue';
|
||||
import ProcessssPrint from './modules/processs-print.vue';
|
||||
import ProcessssPrint from './modules/process-print.vue';
|
||||
import ProcessInstanceSimpleViewer from './modules/simple-bpm-viewer.vue';
|
||||
import BpmProcessInstanceTaskList from './modules/task-list.vue';
|
||||
import ProcessInstanceTimeline from './modules/time-line.vue';
|
||||
|
||||
@@ -143,7 +143,7 @@ function initPrintDataMap() {
|
||||
printDataMap.value.printTime = printTime.value;
|
||||
}
|
||||
|
||||
/** 获取打印模板 HTML (TODO 需求实现配置打印模板) */
|
||||
/** 获取打印模板 HTML */
|
||||
function getPrintTemplateHTML() {
|
||||
if (!printData.value?.printTemplateHtml) return '';
|
||||
|
||||
@@ -153,16 +153,6 @@ function getPrintTemplateHTML() {
|
||||
'text/html',
|
||||
);
|
||||
|
||||
// table 添加 border
|
||||
const tables = doc.querySelectorAll('table');
|
||||
tables.forEach((item) => {
|
||||
item.setAttribute('border', '1');
|
||||
item.setAttribute(
|
||||
'style',
|
||||
`${item.getAttribute('style') || ''}border-collapse:collapse;`,
|
||||
);
|
||||
});
|
||||
|
||||
// 替换 mentions
|
||||
const mentions = doc.querySelectorAll('[data-w-e-type="mention"]');
|
||||
mentions.forEach((item) => {
|
||||
@@ -181,26 +171,23 @@ function getPrintTemplateHTML() {
|
||||
|
||||
if (processRecords.length > 0) {
|
||||
// 构建流程记录 html
|
||||
processRecordTable.setAttribute('border', '1');
|
||||
processRecordTable.setAttribute(
|
||||
'style',
|
||||
'width:100%;border-collapse:collapse;',
|
||||
);
|
||||
processRecordTable.setAttribute('class', 'w-full border-collapse');
|
||||
|
||||
const headTr = document.createElement('tr');
|
||||
const headTd = document.createElement('td');
|
||||
headTd.setAttribute('colspan', '2');
|
||||
headTd.setAttribute('width', 'auto');
|
||||
headTd.setAttribute('style', 'text-align: center;');
|
||||
headTd.innerHTML = '流程节点';
|
||||
headTd.setAttribute('class', 'border border-black p-1.5 text-center');
|
||||
headTd.innerHTML = '流程记录';
|
||||
headTr.append(headTd);
|
||||
processRecordTable.append(headTr);
|
||||
|
||||
printData.value?.tasks.forEach((item) => {
|
||||
const tr = document.createElement('tr');
|
||||
const td1 = document.createElement('td');
|
||||
td1.setAttribute('class', 'border border-black p-1.5');
|
||||
td1.innerHTML = item.name;
|
||||
const td2 = document.createElement('td');
|
||||
td2.setAttribute('class', 'border border-black p-1.5');
|
||||
td2.innerHTML = item.description;
|
||||
tr.append(td1);
|
||||
tr.append(td2);
|
||||
@@ -229,35 +216,34 @@ function getPrintTemplateHTML() {
|
||||
<h2 class="mb-3 text-center text-xl font-bold">
|
||||
{{ printData.processInstance.name }}
|
||||
</h2>
|
||||
<div class="mb-2 text-right text-sm">
|
||||
{{ `打印人员: ${userName}` }}
|
||||
</div>
|
||||
<div class="mb-2 flex justify-between text-sm">
|
||||
<div>
|
||||
{{ `流程编号: ${printData.processInstance.id}` }}
|
||||
</div>
|
||||
<div>{{ `打印时间: ${printTime}` }}</div>
|
||||
<div>
|
||||
{{ `打印人员: ${userName}` }}
|
||||
</div>
|
||||
</div>
|
||||
<table class="mt-3 w-full border-collapse border border-gray-400">
|
||||
<table class="mt-3 w-full border-collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">发起人</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">
|
||||
<td class="w-1/4 border border-black p-1.5">发起人</td>
|
||||
<td class="w-1/4 border border-black p-1.5">
|
||||
{{ printData.processInstance.startUser?.nickname }}
|
||||
</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">发起时间</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">
|
||||
<!-- TODO @jason:这里会告警呢 -->
|
||||
<td class="w-1/4 border border-black p-1.5">发起时间</td>
|
||||
<td class="w-1/4 border border-black p-1.5">
|
||||
<!-- TODO @jason:这里会告警呢 TODO @芋艿 我这边不会有警告呀 -->
|
||||
{{ formatDate(printData.processInstance.startTime) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">所属部门</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">
|
||||
<td class="w-1/4 border border-black p-1.5">所属部门</td>
|
||||
<td class="w-1/4 border border-black p-1.5">
|
||||
{{ printData.processInstance.startUser?.deptName }}
|
||||
</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">流程状态</td>
|
||||
<td class="w-1/4 border border-gray-400 p-1.5">
|
||||
<td class="w-1/4 border border-black p-1.5">流程状态</td>
|
||||
<td class="w-1/4 border border-black p-1.5">
|
||||
{{
|
||||
getDictLabel(
|
||||
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
@@ -268,33 +254,33 @@ function getPrintTemplateHTML() {
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="w-full border border-gray-400 p-1.5 text-center"
|
||||
class="w-full border border-black p-1.5 text-center"
|
||||
colspan="4"
|
||||
>
|
||||
<h4>表单内容</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in formFields" :key="item.id">
|
||||
<td class="w-1/5 border border-gray-400 p-1.5">
|
||||
<td class="w-1/5 border border-black p-1.5">
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="w-4/5 border border-gray-400 p-1.5" colspan="3">
|
||||
<td class="w-4/5 border border-black p-1.5" colspan="3">
|
||||
<div v-html="item.html"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="w-full border border-gray-400 p-1.5 text-center"
|
||||
class="w-full border border-black p-1.5 text-center"
|
||||
colspan="4"
|
||||
>
|
||||
<h4>流程节点</h4>
|
||||
<h4>流程记录</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="item in printData.tasks" :key="item.id">
|
||||
<td class="w-1/5 border border-gray-400 p-1.5">
|
||||
<td class="w-1/5 border border-black p-1.5">
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="w-4/5 border border-gray-400 p-1.5" colspan="3">
|
||||
<td class="w-4/5 border border-black p-1.5" colspan="3">
|
||||
{{ item.description }}
|
||||
<div v-if="item.signPicUrl && item.signPicUrl.length > 0">
|
||||
<img class="h-10 w-[90px]" :src="item.signPicUrl" alt="" />
|
||||
Reference in New Issue
Block a user