fastlion-amis
Version:
一种MIS页面生成工具
402 lines (385 loc) • 15.7 kB
text/typescript
import { tokenize } from "amis-formula"
import { flatMap } from "lodash"
import uniqWith from "lodash/uniqWith"
import { IColumn } from "../../store/table"
import { getCellValue, CROSS_SPLIT_CHAR, handleSort } from "../../store/utils/commonTableFunction"
import { cartesianProduct } from "../../utils/utils"
import { DATAKEYID } from "../../store/crud"
export const EMPTY_GROUP_VALUE = '空白'
export const INDEX_NAME = 'SF_INDEX'
export const REG_EXP = new RegExp(`\\.|:|${CROSS_SPLIT_CHAR}`, 'g')
const worker = new Worker('./public/worker/dataCross.js')
interface ICrossField {
name: string
titleExpr?: string
order?: 'ASC' | 'DESC'
orderName?: string
}
export interface ICross {
columnFields: ICrossField[]
rowFields: Pick<ICrossField, 'name'>[]
valueFields: string
/** valueField位置 0:表头 1:表体 */
positionType: 0 | 1,
}
interface IColumnType {
type: string
align: string
sortable: boolean
map?: object
fixed?: string
isNumerical?: boolean
quickEdit?: any
}
type IColumnField = ICrossField & IColumn
export interface ICrossColumn extends IColumnType {
label: string
field: string
name: string
groupName: string
groupLabels: string[]
isCountField: boolean
}
const uniqArr = (fields: IColumnField[], datas: any[]) => {
const map = new Map<string, { order: 'asc' | 'desc' }>(fields.map(field => [(field.orderName ?? field.name), { order: (field.order?.toLowerCase() ?? 'asc') as 'asc' | 'desc' }]))
const sortData = handleSort(datas.slice(), fields.map(field => field.orderName ?? field.name), map)
return uniqWith(sortData.map(data => {
const obj: any = {}
for (const field of fields) {
obj[field.name] = data[field.name]
}
return obj
}), (a, b) => {
for (const field of fields) {
if (a[field.name] !== b[field.name]) {
return false
}
}
return true
})
}
export const buildCrossColumn = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], datas: any[], countSum: boolean) => {
const ret: ICrossColumn[] = []
const level = colFields.length + 1
const uniqColArr = uniqArr(colFields, datas)
for (const rowField of rowFields) {
const { type, label, name, align, sortable, map, fixed, pristine } = rowField
const groupName = new Array(level - 1).fill(pristine.groupName ?? name).join(',')
const groupLabels = new Array(level - 1).fill(pristine.groupName ?? name)
ret.push({
...pristine,
type,
label,
name,
field: name,
groupName,
groupLabels,
map,
fixed,
align,
sortable,
isCountField: false
})
}
const product: [any, IColumnField, string[]][] = cartesianProduct(uniqColArr, valueFields).map(([item, field]: [any, IColumnField]) => {
const cols: string[] = []
for (let i = 0; i < colFields.length; i++) {
const field = colFields[i]
if (i == 0) {
cols.push(String(item[field.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
} else {
const vals = []
for (let j = 0; j <= i; j++) {
const preField = colFields[j]
vals.push(String(item[preField.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
}
cols.push(vals.join(CROSS_SPLIT_CHAR))
}
}
return [item, field, cols.concat(`${cols[cols.length - 1]}${CROSS_SPLIT_CHAR}${field.name}`)]
})
for (const [uniqCol, valueField, item] of product) {
const { type, label, name, align, sortable } = valueField
const { quickEdit, ...rest } = valueField.pristine
const has$Expr = colFields.some(field => field.titleExpr && !/\$\{[^\}]*\}/g.test(field.titleExpr))
const groupName = has$Expr ? colFields.map(field => field.titleExpr).join(CROSS_SPLIT_CHAR) : level == 1 ? '' : item.slice(0, level - 1).map(val => val.replaceAll(',', '')).join(',')
const groupLabels = colFields.map(field => {
const val = getCellValue(uniqCol[field.name], field) || EMPTY_GROUP_VALUE
return field.titleExpr ? field.titleExpr.replaceAll(`$\{${field.name}\}`, val) : val
})
ret.push({
...rest,
type,
label: /\$\{[^\}]*\}/g.test(label) ? (tokenize(label, uniqCol) || EMPTY_GROUP_VALUE) : label,
name: item[level - 1],
field: name,
groupName,
groupLabels,
align,
sortable,
quickEdit: quickEdit ? { ...quickEdit, name: item[level - 1] } : undefined,
isNumerical: true,
isCountField: false
})
}
if (countSum) {
for (const valueField of valueFields) {
const { type, label, name, align, sortable, pristine } = valueField
const groupName = new Array(colFields.length).fill('SF_COUNT').join(',')
const groupLabels = new Array(colFields.length).fill('合计')
const rLabel = label.replace(REG_EXP, '')
ret.push({
...pristine,
type,
label: rLabel,
name: `${rLabel}${CROSS_SPLIT_CHAR}${groupName}`,
field: name,
groupName,
groupLabels,
align,
sortable,
isNumerical: false,
isCountField: true
})
}
}
return ret
}
export const buildCrossData = (rowFields: IColumnField[], colFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
return new Promise<any[]>((resolve, reject) => {
worker.postMessage({ rowFields, colFields, crossColumns, datas, regExp: REG_EXP, EMPTY_GROUP_VALUE, CROSS_SPLIT_CHAR })
worker.onmessage = (e: MessageEvent<any[]>) => {
const map = new Map<string, { order: 'asc' | 'desc' }>(rowFields.map(field => [(field.orderName ?? field.name), { order: (field.order?.toLowerCase() ?? 'asc') as 'asc' | 'desc' }]))
const sortData = handleSort(e.data.slice(), rowFields.map(field => field.orderName ?? field.name), map)
resolve(sortData)
}
worker.onmessageerror = (e) => { reject(e.data) }
})
}
export const buildCrossColumn1 = (rowFields: IColumnField[], colFields: IColumnField[], datas: any[]) => {
const ret: ICrossColumn[] = []
const level = colFields.length
// step 1
for (const rowField of rowFields) {
const { type, label, name, align, sortable, map, fixed, pristine } = rowField
ret.push({
...pristine,
type,
label,
name,
field: name,
groupName: new Array(level - 1).fill(name).join(','),
groupLabels: [],
map,
fixed,
align,
sortable,
isCountField: false
})
}
// step 2
ret.push({
label: '指标',
name: INDEX_NAME,
field: INDEX_NAME,
groupName: new Array(level - 1).fill(INDEX_NAME).join(','),
groupLabels: [],
isCountField: false,
type: 'plain',
align: 'left',
sortable: false,
})
// step 3
const uniqColArr = uniqArr(colFields, datas)
for (const item of uniqColArr) {
const cols: string[] = []
for (let i = 0; i < colFields.length; i++) {
const field = colFields[i]
if (i == 0) {
cols.push(String(item[field.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
} else {
const vals = []
for (let j = 0; j <= i; j++) {
const preField = colFields[j]
vals.push(String(item[preField.name] ?? EMPTY_GROUP_VALUE).replace(REG_EXP, ''))
}
cols.push(vals.join(CROSS_SPLIT_CHAR))
}
}
const groupName = level == 1 ? '' : cols.slice(0, level - 1).join(',')
const labels = colFields.map(field => {
const val = getCellValue(item[field.name], field) || EMPTY_GROUP_VALUE
return field.titleExpr ? field.titleExpr.replaceAll(`$\{${field.name}\}`, val) : val
})
ret.push({
label: labels[level - 1],
name: cols[level - 1],
field: '',
groupName,
groupLabels: labels.slice(0, level - 1),
isCountField: false,
type: 'plain',
align: 'right',
sortable: false
})
}
return ret
}
export const buildCrossData1 = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
const uniqRowData = uniqArr(rowFields, datas)
return new Promise<any[]>((resolve, reject) => {
worker.postMessage({ rowFields, colFields, valueFields, crossColumns, datas, uniqRowData, regExp: REG_EXP, EMPTY_GROUP_VALUE, CROSS_SPLIT_CHAR, INDEX_NAME })
worker.onmessage = (e: MessageEvent<any[]>) => { resolve(e.data) }
worker.onmessageerror = (e) => { reject(e.data) }
})
}
export const getChangeRows = (changeDatas: any[], rawDatas: any[], cross: ICross): any[] => {
const { rowFields, columnFields, positionType } = cross
if (positionType === 1) return []
return flatMap(changeDatas, changeData => {
const colFields = Object.keys(changeData).filter(key => key.includes(CROSS_SPLIT_CHAR))
const rowTarget = rawDatas.filter(data => rowFields.every(field => data[field.name] == changeData[field.name]))
const result = []
for (const field of colFields) {
const values = field.split(CROSS_SPLIT_CHAR)
const valueField = values[values.length - 1]
const colTarget = rowTarget.find(data => {
return columnFields.every((field, index) => {
const dValue = data[field.name]
return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (values[index] == EMPTY_GROUP_VALUE ? null : values[index])
})
})
if (colTarget) {
const temp = { ...colTarget }
const index = result.findIndex(item => item[DATAKEYID] === temp[DATAKEYID])
if (index == -1) {
temp[valueField] = changeData[field]
result.push(temp)
} else {
result[index][valueField] = changeData[field]
}
}
}
return result
})
}
// export const buildCrossData = (rowFields: IColumnField[], colFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[], countSum: boolean) => {
// return new Promise<any[]>((resolve, reject) => {
// const ret = datas.reduce((result, current) => {
// const index = result.findIndex((data: any) => rowFields.every(field => data[field.name] == current[field.name]))
// const compareFn = (field: IColumnField, index: number, name: string) => {
// const value = name.split(CROSS_SPLIT_CHAR)[index]
// const dValue = current[field.name]
// return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (value == EMPTY_GROUP_VALUE ? null : value)
// }
// if (index == -1) {
// const target = { ...current }
// for (let i = 0; i < crossColumns.length; i++) {
// const { name, field, isPercentage, calculatedField, isCountField } = crossColumns[i]
// if (i < rowFields.length) {
// target[name] = current[field]
// } else {
// const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
// const condition = colFields.every((field, index) => compareFn(field, index, name))
// if (isRate) {
// target[`Z_${name}`] = condition || isCountField ? +current[field] / 100 * +current[calculatedField] : null
// target[`M_${name}`] = condition || isCountField ? +current[calculatedField] : null
// }
// target[name] = condition || isCountField ? current[field] : null
// }
// }
// result.push(target)
// } else {
// const target = { ...result[index] }
// for (let i = rowFields.length; i < crossColumns.length; i++) {
// const { name, field, isPercentage, calculatedField, isCountField } = crossColumns[i]
// const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
// if (isCountField) {
// if (isRate) {
// target[`Z_${name}`] += +current[field] / 100 * +current[calculatedField]
// target[`M_${name}`] += +current[calculatedField]
// target[name] = (target[`Z_${name}`] == 0 || target[`M_${name}`] == 0) ? null : target[`Z_${name}`] / target[`M_${name}`] * 100
// } else {
// target[name] += +current[field]
// }
// } else {
// const condition = colFields.every((field, index) => compareFn(field, index, name))
// if (condition) {
// if (isRate) {
// if (current[calculatedField] != null && !isNaN(+current[calculatedField])) {
// target[`Z_${name}`] += +current[field] / 100 * +current[calculatedField]
// target[`M_${name}`] += +current[calculatedField]
// target[name] = (target[`Z_${name}`] == 0 || target[`M_${name}`] == 0) ? null : target[`Z_${name}`] / target[`M_${name}`] * 100
// } else {
// target[name] = current[field]
// }
// } else {
// if (current[field] != null) {
// if (isNaN(+current[field])) {
// target[name] = current[field]
// } else {
// target[name] += +current[field]
// }
// }
// }
// }
// }
// }
// result[index] = target
// }
// return result
// }, [])
// resolve(ret)
// })
// }
// export const buildCrossData1 = (rowFields: IColumnField[], colFields: IColumnField[], valueFields: IColumnField[], crossColumns: ICrossColumn[], datas: any[]) => {
// const uniqRowData = uniqArr(rowFields, datas)
// const tempColumns = crossColumns.slice(rowFields.length + 1)
// return flatMap(uniqRowData ?? [], rowData => {
// const ret: any[] = []
// const rowDatas = datas.filter(data => rowFields.every(rowField => data[rowField.name] == rowData[rowField.name]))
// for (let i = 0; i < valueFields.length; i++) {
// const { name, label, pristine } = valueFields[i]
// const { isPercentage, calculatedField } = pristine
// const isRate = isPercentage === true && (calculatedField?.length ?? 0) > 0
// const obj = { ...rowData }
// obj[INDEX_NAME] = label
// for (const tempCol of tempColumns) {
// const items: any[] = rowDatas.filter(data => {
// return colFields.every((colField, index) => {
// const value = tempCol.name.split(CROSS_SPLIT_CHAR)[index]
// const dValue = data[colField.name]
// return (dValue == null ? dValue : String(dValue).replace(REG_EXP, '')) == (value == EMPTY_GROUP_VALUE ? null : value)
// })
// })
// if (items.length > 0) {
// let value = null, zSum = 0, mSum = 0
// for (const item of items) {
// if (item[name] != null) {
// if (isRate) {
// if (item[calculatedField] != null && !isNaN(+item[calculatedField])) {
// zSum += (+item[name] / 100 * + item[calculatedField])
// mSum += +(item[calculatedField])
// value = (zSum == 0 || mSum == 0) ? null : (zSum / mSum * 100)
// } else {
// value = item[name]
// }
// } else {
// if (isNaN(+item[name])) {
// value = item[name]
// } else {
// value += +item[name]
// }
// }
// }
// }
// obj[tempCol.name] = value ? getCellValue(value, valueFields[i]) : null
// } else {
// obj[tempCol.name] = null
// }
// }
// ret.push(obj)
// }
// return ret
// })
// }