UNPKG

fastlion-amis

Version:

一种MIS页面生成工具

538 lines (510 loc) 22.6 kB
import { TableUI, DetailOptionValues, DetailTableValues, PrintSchemaUI, PropertyItem, PropertyUI } from "./types"; import { combineCell, getCellValue } from "../../../../../store/utils/commonTableFunction"; import { isVisible } from "../../../../../utils/helper"; import { RendererEnv } from "../../../../../env"; import { calcFn } from "../../../../../utils/utils"; enum FlowNodeData { seq = '序号', nodeName = '审批节点', nodeType = '签核类型', nodeUser = '审批人', nodeStatus = '审批结果', nodeEndTime = '审批时间', remarkComment = '审批意见', assignComment = '加签意见', attachments = '附件' } interface IFormSchema { type: 'form' body: (IPropertySchema | IFieldSetSchema | IServiceSchema)[] [key: string]: any } interface IPropertySchema { type: 'property' column: number items?: PropertyItem[] title?: string } interface IFieldSetSchema { type: 'fieldSet' title: string body: IPropertySchema[] } interface IServiceSchema { type: 'service' schemaApi: any } interface ITabsSchema { type: 'tabs' tabs: ITabsItemSchema[] } interface ITabsItemSchema { type: 'tabsItem' title: string, body: IServiceSchema[] } interface ICrudSchema { type: 'crud' name: string api: any aliasTitle: string data: object[] columns: object[] } type ISchema = IFormSchema | IServiceSchema | ITabsSchema | ITabsItemSchema | IFieldSetSchema | IPropertySchema | ICrudSchema const createPropertyElement = (ui: PropertyUI, options: DetailOptionValues & DetailTableValues, border: boolean) => { const { column = 3, body: rows, data } = ui const table = document.createElement('table') if (rows.length === 0) return table table.style.borderCollapse = 'collapse' table.style.tableLayout = 'fixed' table.style.border = border ? `${Math.max(2, options.borderWidth ?? 2)}px solid black` : 'none' table.style.wordBreak = options.numberBreak ? 'break-all' : 'break-word' table.style.fontSize = `${options.contentFontSize}px` table.style.fontFamily = options.contentFontFamily table.setAttribute('width', '100%') const tbody = table.createTBody() for (const row of rows) { const tr = document.createElement('tr') for (let index = 0; index < row.length; index++) { const item = row[index] if (item.content.type === 'input-table-field') { const td = document.createElement('td') td.colSpan = column * 2 td.style.padding = '0' td.style.border = 'none' td.style.borderStyle = 'hidden' const tableData = getValue(item.content, data) const label = item.label ?? '' const tableValues = options.detailTableValues const setting = tableValues.find(value => value.name === item.content.name) if (setting?.isPrint === false) { continue } const fieldColumns = item.content.columns?.filter((column: any) => setting ? setting.columns.find(v => v.name === column.name)?.printType === 'all' : true) ?? [] if (item.content.tableLayout === 'horizontal') { if (item.content.showIndex === true) fieldColumns.unshift({ type: 'index', name: 'index', label: '' }) const datas = combineCell(tableData.map((data: any) => ({ ...data, rowSpans: {} })), fieldColumns.filter((column: any) => column.combined)) if (label.length > 0) { const div = document.createElement('div') if (setting) { div.style.fontSize = `${setting.fontSize}px` div.style.fontFamily = setting.fontFamily } div.textContent = label td.appendChild(div) } const fieldTable = document.createElement('table') fieldTable.style.borderStyle = 'hidden' fieldTable.style.borderCollapse = 'collapse' fieldTable.style.fontSize = `${setting?.fontSize ?? 12}px` fieldTable.style.fontFamily = setting?.fontFamily ?? '宋体' fieldTable.setAttribute('width', '100%') //字段表格thead const fieldThead = fieldTable.createTHead() const fieldTr = document.createElement('tr') for (const fieldColumn of fieldColumns) { const fieldTh = document.createElement('th') fieldTh.style.textAlign = fieldColumn.align fieldTh.textContent = fieldColumn.label fieldTr.appendChild(fieldTh) } fieldThead.appendChild(fieldTr) //字段表格tbody const fieldTbody = fieldTable.createTBody() datas.forEach((data, index) => { const fieldTr = document.createElement('tr') for (const fieldColumn of fieldColumns) { const rowspan = data.rowSpans[fieldColumn.name] if (rowspan === 0) continue const fieldTd = document.createElement('td') fieldTd.style.textAlign = fieldColumn.align fieldTd.rowSpan = rowspan || 1 fieldTd.textContent = fieldColumn.type === 'index' ? index + 1 : getValue(fieldColumn, data) fieldTr.appendChild(fieldTd) } fieldTbody.appendChild(fieldTr) }) if (Array.isArray(item.content.affixRow)) { const summaryTr = document.createElement('tr') for (const fieldColumn of fieldColumns) { const target = item.content.affixRow.find((field: any) => field.name === fieldColumn.name) const td = document.createElement('td') if (target) { const value = calcFn(target.formula?.toLocaleLowerCase(), target.name, datas) td.textContent = (target.rule ? target.rule + ':' : '') + getCellValue(value, { ...fieldColumn, pristine: fieldColumn }) } summaryTr.appendChild(td) } fieldTable.appendChild(summaryTr) } td.appendChild(fieldTable) } else if (item.content.tableLayout === 'vertical') { tableData.forEach((data: any, index: number) => { const div = document.createElement('div') if (setting) { div.style.fontSize = `${setting.fontSize}px` div.style.fontFamily = setting.fontFamily } div.style.padding = `${options.rowPadding}px ${options.colPadding}px` div.textContent = label + `${index + 1}` td.appendChild(div) const rows = buildPropertyUI({ type: 'property', column, items: fieldColumns.map((column: any) => ({ label: column.label, span: 1, content: { ...column, value: getValue(column, data) } })) }, data) const table = createPropertyElement({ type: 'property', column, body: rows, data }, options, false) table.style.fontSize = `${setting?.fontSize ?? 12}px` table.style.fontFamily = setting?.fontFamily ?? '宋体' table.style.borderStyle = 'hidden' td.appendChild(table) }) } tr.appendChild(td) } else { const isLastFillCell = item.span == 1 && row.length != column && item.label === row[row.length - 1]?.label const th = document.createElement('th') th.style.width = `${options.labelWidthPercent / column}%` th.textContent = item.label?.replace(/\n|\r|<[^>]*>/g, '') ?? '' const td = document.createElement('td') td.colSpan = isLastFillCell ? column * 2 - (index * 2 + 1) : item.span * 2 - 1 td.textContent = getValue(item.content, data) tr.appendChild(th) tr.appendChild(td) } } tbody.appendChild(tr) } return table } const createTableElement = (ui: TableUI, options: DetailOptionValues & DetailTableValues) => { const { columns, data } = ui.body const setting = options.detailTableValues.find(value => value.name === ui.name) const table = document.createElement('table') if (setting?.isPrint === false) { return table } table.style.borderCollapse = 'collapse' table.style.border = `${Math.max(2, options.borderWidth ?? 2)}px solid black` table.style.wordBreak = options.numberBreak ? 'break-all' : 'break-word' table.style.fontSize = `${setting?.fontSize ?? options.contentFontSize}px` table.style.fontFamily = setting?.fontFamily ?? options.contentFontFamily table.setAttribute('width', '100%') const thead = table.createTHead() const tr = document.createElement('tr') const tableColumns = columns.filter(column => setting ? setting.columns.find(v => v.name === column.name)?.printType === 'all' : true) for (const column of tableColumns) { const th = document.createElement('th') th.textContent = column.label th.style.textAlign = column.align tr.appendChild(th) } thead.appendChild(tr) const tbody = table.createTBody() data.forEach(item => { const tr = document.createElement('tr') for (const column of tableColumns) { const td = document.createElement('td') td.textContent = getValue(column, item) td.style.textAlign = column.align tr.appendChild(td) } tbody.appendChild(tr) }) return table } const createLabelElement = (label: string) => { const labelDiv = document.createElement('div') labelDiv.textContent = label labelDiv.style.backgroundColor = 'gray' labelDiv.style.padding = '6px' labelDiv.style.color = 'white' labelDiv.style.fontSize = 'large' labelDiv.style.marginBottom = '12px' labelDiv.style.width = '20%' labelDiv.style.minWidth = '80px' labelDiv.style.fontWeight = 'bold' return labelDiv } const getValue = (schema: any, data: any) => { const value = data[schema.name] ?? schema.value const types = ['mapping', 'static-mapping', 'number', 'static-number'] if (types.includes(schema.type)) { return getCellValue(value, { ...schema, type: schema.type.replace('static-', ''), pristine: schema }) } if (schema.type === 'lion-upload') { const tempValue = data[schema.name] return tempValue?.info?.map((item: any) => item.name).join(',') } if (schema.type === 'switch') { return value === schema.trueValue ? schema.onText : schema.offText } if (schema.type === 'input-table-field') { return data[schema.name] ?? [] } if (schema.type === 'flow-user') { return value?.userName ?? '' } if (schema.type === 'flow-attachment') { return value?.map((item: any) => item.name).join(',') } return data[schema.name] } export const createHtml = (schema: { form: PrintSchemaUI[], flow?: TableUI }, options: DetailOptionValues & DetailTableValues) => { const body = document.createElement('body') const { form, flow } = schema // console.log(form, flow) if (flow) { const labelDiv = createLabelElement('申请详情') body.appendChild(labelDiv) } form.forEach(item => { if (item.label) { const div = document.createElement('div') div.textContent = item.label div.style.fontSize = `${options.contentFontSize}px` div.style.fontFamily = options.contentFontFamily div.style.marginTop = '6px' div.style.marginBottom = '6px' body.appendChild(div) } if (item.type === 'property') { const table = createPropertyElement(item, options, true) body.appendChild(table) } else if (item.type === 'table') { const table = createTableElement(item, options) body.appendChild(table) } }) if (flow) { const labelDiv = createLabelElement('审批详情') labelDiv.style.marginTop = '20px' body.appendChild(labelDiv) const flowTable = createTableElement(flow, options) body.appendChild(flowTable) } return `<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <style> body { width: 100%; } td, th { border: ${options.lineWidth}px solid; font-weight: normal; padding: ${options.rowPadding}px ${options.colPadding}px; } div { padding: ${options.rowPadding}px ${options.colPadding}px; } </style> ${body.outerHTML} </html>` } export const findLabel = (name: string, object: any): { label: string } | undefined => { if (typeof object == 'object' && object != null) { if (Array.isArray(object)) { for (const item of object) { const target = findLabel(name, item) if (target) return target } } if (Object.prototype.hasOwnProperty.call(object, 'label') && Object.prototype.hasOwnProperty.call(object, 'content')) { const content = object['content'] if (content['name'] === name) { return object } } for (const key in object) { if (Object.prototype.hasOwnProperty.call(object, key)) { const value = object[key]; const target = findLabel(name, value) if (target) return target } } } return undefined } export const getDetailTableValues = async (schema: IFormSchema, formData: object, fetcher: RendererEnv['fetcher']): Promise<DetailTableValues['detailTableValues']> => { const schemas = await computed(schema, formData, fetcher) return schemas.map(schema => { if (schema.type === 'property') { if (Array.isArray(schema.items)) { return schema.items.filter(item => { return item.content.type === 'input-table-field' && Array.isArray(item.content.columns) }).map(item => { return { name: item.content.name, isPrint: true, label: item.label ?? '', fontSize: 12, fontFamily: '宋体', columns: item.content.columns.map((column: any) => { return { name: column.name, title: column.label, align: column.align ?? 'left', printType: 'all' } }) } }) } } else { return { name: schema.name, isPrint: true, label: schema.aliasTitle ?? '', fontSize: 12, fontFamily: '宋体', columns: schema.columns.filter((column: any) => column.type !== 'operation').map((column: any) => { return { name: column.name, title: column.label, align: column.align ?? 'left', printType: 'all' as any } }) } } return [] }).flat() } export const buildPropertyUI = (property: IPropertySchema, data: object) => { const items = property.items?.filter(item => isVisible(item, data)) ?? [] const column = property.column ?? 3 const rows: PropertyItem[][] = []; let row: PropertyItem[] = [] let columnLeft = column; let index = 0; for (const item of items) { index = index + 1; const span = Math.min(item.span || 1, column); columnLeft = columnLeft - span; const rowItem: PropertyItem = { label: item.label, content: item.content, span: span, }; if (columnLeft >= 0) { row.push(rowItem); } else { rows.push(row); columnLeft = column - span; row = [rowItem]; } if (index === items.length) { rows.push(row); } } return rows } const computed = async (node: ISchema | ISchema[], formData: object, fetcher: RendererEnv['fetcher'], title?: string) => { if (Array.isArray(node)) { const result: (IPropertySchema | ICrudSchema)[] = [] for (const item of node) { const schema = await computed(item, formData, fetcher, title) result.push(...schema) } return result } else { if (node.type === 'service') { try { const { data } = await fetcher(node.schemaApi, formData ?? {}) return await computed(data, formData, fetcher, title) } catch (error) { return [] } } if (node.type === 'tabs') { return await computed(node.tabs.map(tab => ({ ...tab, type: 'tabsItem' })), formData, fetcher, title) } if (node.type === 'form') { try { if (node.initApi) { const { data } = await fetcher(node.initApi, formData ?? {}) return await computed(node.body, data, fetcher, title) } return await computed(node.body, formData, fetcher, title) } catch (error) { return await computed(node.body, formData, fetcher, title) } } if (node.type === 'fieldSet' || node.type === 'tabsItem') { return await computed(node.body, formData, fetcher, node.title) } if (node.type === 'property') { if (title) node.title = title return [node] } if (node.type === 'crud') { return [node] } return [] } } export const buildPrintFormSchema = async (formSchema: IFormSchema, formData: object, fetcher: RendererEnv['fetcher']) => { const dealSchema = async (schema: IPropertySchema | ICrudSchema) => { if (schema.type === 'property') { await Promise.all(schema.items?.map(async (item) => { if (item.content.type === 'static-mapping' && item.content.source) { const { ok, data } = await fetcher(item.content.source, formData).catch(() => ({ ok: false, data: null })) ok && (item.content.map = data) } else if (item.content.type === 'input-table-field') { await Promise.all(item.content.columns?.map(async (column: any) => { if (column.type === 'mapping' && column.source) { const { ok, data } = await fetcher(column.source, formData).catch(() => ({ ok: false, data: null })) ok && (column.map = data) } }) ?? []) } }) ?? []) } else if (schema.type === 'crud') { const { ok, data } = await fetcher(schema.api, { perPage: 30, page: 1 }).catch(() => ({ ok: false, data: null })) ok && (schema.data = data.items) await Promise.all(schema.columns?.map(async (column: any) => { if (column.type === 'mapping' && column.source) { const { ok, data } = await fetcher(column.source, formData).catch(() => ({ ok: false, data: null })) ok && (column.map = data) } }) ?? []) } return schema } const schemas = await computed(formSchema, formData, fetcher) return await Promise.all(schemas.map(async schema => { return await dealSchema(schema) })) } export const buildPrintFlowSchema = (printInfo?: { processNodeList: Record<keyof typeof FlowNodeData, any>[] }): TableUI | undefined => { const nodeList = printInfo?.processNodeList if (Array.isArray(nodeList)) { const hasAssignComment = nodeList.some((item) => !!item.assignComment) const columns = Object.entries(FlowNodeData).filter(([name]) => hasAssignComment ? true : name !== 'assignComment').map(value => { const [name, label] = value if (name === 'nodeType') { return { label, name, type: 'mapping', map: { 0: '或签', 1: '会签', 2: '依次审批', 3: '系统签核', 4: '加签' } } } if (name === 'nodeStatus') { return { label, name, type: 'mapping', map: { submit: '提交申请', waiting: '待签核', doing: '签核中', revert: '撤销', refuse: '拒绝', pass: '通过', return: '退回' } } } if (name === 'nodeUser') { return { label, name, type: 'flow-user' } } if (name === 'attachments') { return { label, name, type: 'flow-attachment' } } return { label, name } }) return { type: 'table', name: 'flow-detail', body: { columns, data: nodeList } } } return undefined } export function chunkArray(arr: any[], size: number) { const result = []; for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)); } return result; }