fastlion-amis
Version:
一种MIS页面生成工具
538 lines (510 loc) • 22.6 kB
text/typescript
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;
}