import { YAXisComponentOption } from 'echarts'; import { difference, omit } from 'lodash'; import { barChartConfigType, boxplotChartConfigType, cartesianAxesChartType, chartConfigType, multidimensionalChartConfigType, normalChartConfigType, quantileRadarChartConfigType, seasonalChartConfigType, structuralChartConfigType, } from '@dq-next/types/chart'; import { chartTypeEnum, structuralOffsetUnitEnum, timeConfigEnum, tempConfigEnum, echartSeriesTypeEnum, } from '@dq-next/types/enums/chartEnum'; import { formatToDate } from '../dateUtil'; import { SelectedQuotaItem } from '@dq-next/types/quota'; import { ImgConfigType, TemplateItem, TextConfigType, pageSettingType, } from '@dq-next/types/template'; import { SQLTableConfigType, TableConfigType } from '@dq-next/types/table'; import { reportInfoType } from '@dq-next/types/report'; import { getUniqueField, UuidUtil } from '..'; import { SourceTypeEnum } from '@dq-next/types/enums/quotaEnum'; import { Ref } from 'vue'; interface chartTemplateModel { config: chartConfigType; category_id: number; } enum quotaDataPastUnitTypeEnum { day = '日', last = '期', month = '月', year = '年', } export function pingChart() { // 图表类型对应 const chart_types_map = { series: 'normal', //数据序列 season: 'seasonal', //季节性序列 curve: 'structural', //曲线结构 polar_quantile: 'quantile', //雷达图(分位数) polar: 'radar', //雷达图 pie: 'pie', // 饼图 column: 'bar', //柱状图 boxplot: 'boxplot', // 箱线图 datatable: '', // '数据表格' }; // 线形类型对应 const line_types_map = { line: 'line', spline: 'smoothLine', area: 'area', column: 'bar', bar: 'bar', pie: 'line', treemap: 'line', scatter: 'scatter', }; return function transform(options) { const rows = options['rows']; const o: chartTemplateModel = { category_id: options['category_id'], config: { name: options['template_name'], type: chart_types_map[options['option_template_type']], title: options['chart_title'], quotaList: [], seriesSetting: [], colorSchemeId: 22, selfColorScheme: '', valueFormatter: { normalized: options['option_normalize'], afterDot: 2, sortMonth: [], }, showLastest: true, showHighest: false, timeConfig: { startDate: '2015-01-01', endDate: formatToDate(), type: timeConfigEnum.default, }, yAxis: [], xAxis: [ { min: undefined, max: undefined, inverse: false, name: '下1', offset: 0, axisLine: { show: true, lineStyle: { color: '#999999', }, }, position: 'bottom', axisLabel: { formatter: '{yyyy}/{M}/{d}', }, }, ], http: true, fixData: [], } as unknown as chartConfigType, }; if (o.config.type == 'seasonal') { // 季节性 const b = options['season_begin_month'] ? +options['season_begin_month'] : 1; o.config.timeConfig.startMonth = b; const all = new Array(12).map((_, i) => i + 1); const l = options['season_months'] && options['season_months'].length > 0 ? options['season_months'] : new Array(12).map((_, i) => i + 1); o.config.timeConfig.sortMonth = difference(all, l); } else if (o.config.type == 'structural') { // 结构曲线 (o.config as structuralChartConfigType).structuralOffset = '30,15,7,1,0'; (o.config as structuralChartConfigType).structuralOffsetUnit = structuralOffsetUnitEnum.natureDay; } else if (o.config.type == 'quantileRadar') { // 分位数 const l = options['quantile_years'] && options['quantile_years'].length > 0 ? options['quantile_years'] : [1, 3, 5, 10]; (o.config as quantileRadarChartConfigType).quantileOffset = l.join(','); } else if (['radar', 'bar'].includes(o.config.type)) { o.config.timeConfig.pastValue = 3; o.config.timeConfig.pastUnit = quotaDataPastUnitTypeEnum.last; } else if (o.config.type === 'pie') { o.config.timeConfig.pastValue = 1; o.config.timeConfig.pastUnit = quotaDataPastUnitTypeEnum.last; } let yaxis_list: YAXisComponentOption[] = []; for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (Reflect.has(row, 'formula')) { Reflect.deleteProperty(row, 'formula'); } //Y轴处理 if (row['yaxis'] == 1 || row['yaxis'] == 2) { yaxis_list = yaxis_list.length > 0 ? yaxis_list : [ { position: 'left', name: '左1', max: undefined, min: undefined, offset: 0, inverse: false, axisLabel: { formatter: '{value}' }, }, ]; yaxis_list.push({ position: 'right', name: '右1', min: !row['ymin'] ? undefined : +row['ymin'], max: !row['ymax'] ? undefined : +row['ymax'], offset: 0, inverse: row['yaxis'] == 2, axisLabel: { formatter: '{value}' }, }); } if (row.id && row.id > 100000000) { Reflect.deleteProperty(row, 'id'); } o.config.quotaList?.push({ name: row['name'], sourceCode: row['source_code'], sourceType: row['source_type'], dateLast: row['date_first'], frequency: row['frequency'], unit: row['unit'], shortName: row['shortName'], id: row.id, timeLastUpdate: '', }); o.config.seriesSetting.push({ seriesType: line_types_map[row['chart_type']], yAxisIndex: row['yaxis'] == 1 || row['yaxis'] == 2 ? yaxis_list.length - 1 : 0, size: 2, name: row['name'], }); } //Y轴设置 (o.config as normalChartConfigType | seasonalChartConfigType | barChartConfigType).yAxis = yaxis_list.length > 0 ? yaxis_list : [ { inverse: false, offset: 0, position: 'left', axisLabel: { formatter: '{value}' }, }, ]; return o.config; }; } export function parseRecentString(recent: string) { const [num, unit] = recent.split('|'); const units = [ { label: quotaDataPastUnitTypeEnum.year, value: 'years' }, { label: quotaDataPastUnitTypeEnum.month, value: 'months' }, { label: quotaDataPastUnitTypeEnum.day, value: 'days' }, ]; let pastValue = 0, pastUnit: quotaDataPastUnitTypeEnum = quotaDataPastUnitTypeEnum.last; if (parseInt(num) !== 0) { pastValue = parseInt(num); pastUnit = units.find((u) => u.value === unit)!.label; } return { pastValue, pastUnit, }; } export function huiChart(options) { const o = options.config; if (o.type === 'seasonallunar') { o.type = 'seasonalLunar'; } const yaxisLRindex = { left: 1, right: 1, }; const config: chartConfigType = { title: o.title, name: o.title, type: o.type, quotaList: [], colorSchemeId: o.colorsId ?? 22, selfColorScheme: o.colors ?? '', showLastest: o.textRect.showLastest, showHighest: o.textRect.showHighest, timeConfig: { startDate: o.startDate, endDate: o.endDate, type: o.timeType, sortMonth: [], sortYear: [], pastUnit: quotaDataPastUnitTypeEnum.last, pastValue: 0, startMonth: 1, }, dimensionInfo: { list: [], sortBy: '', direction: 'ASC', }, valueFormatter: { afterDot: o.decimal || 2, normalized: o.normalized, }, structuralOffset: '', structuralOffsetUnit: structuralOffsetUnitEnum.natureDay, seriesSetting: [], dataSetting: { autoRedirectMonthStart: true, }, yAxis: o.multiY ? o.yAxis.map((y) => { return { ...y, min: (y.min ?? '').length === 0 ? undefined : parseFloat(y.min), max: (y.max ?? '').length === 0 ? undefined : parseFloat(y.max), axisLabel: { formatter: '{value}' }, name: y.position === 'left' ? `左${yaxisLRindex.left++}` : `右${yaxisLRindex.right++}`, axisLine: { show: true, lineStyle: { color: '#999999', }, }, }; }) : [ { inverse: false, offset: 0, name: '', position: 'left', axisLabel: { formatter: '{value}' }, axisLine: { show: true, lineStyle: { color: '#999999', }, }, }, ], xAxis: [ { min: undefined, max: undefined, inverse: false, name: '下1', offset: 0, axisLine: { show: true, lineStyle: { color: '#999999', }, }, position: 'bottom', axisLabel: {}, }, ], http: o.http ?? true, fixData: o.fixData || [], }; if (o.timeType === 'recent') { const { pastUnit, pastValue } = parseRecentString(o.recent); config.timeConfig.pastUnit = pastUnit; config.timeConfig.pastValue = pastValue; } if (o.lastMulti.multi) { config.timeConfig.pastValue = o.lastMulti.number; } if (o.type === chartTypeEnum.pie) { config.timeConfig.pastValue = 1; config.timeConfig.pastUnit = quotaDataPastUnitTypeEnum.last; } if (o.type === 'barformula') { (config as multidimensionalChartConfigType).type = chartTypeEnum.multidimensional; (config as multidimensionalChartConfigType).dimensionInfo.list = o.formulas; (config as multidimensionalChartConfigType).dimensionInfo.sortBy = o.formulaSort ? o.formulaSortIndex : o.formulas[0].name; (config as multidimensionalChartConfigType).dimensionInfo.direction = o.formulaSortType === 'down' ? 'DESC' : 'ASC'; // @ts-ignore (config as multidimensionalChartConfigType).xAxis[0].axisLabel.formatter = '{value}'; } if (o.type === 'boxplot') { // @ts-ignore (config as boxplotChartConfigType).xAxis[0].axisLabel.formatter = '{value}'; } if ([chartTypeEnum.structural, chartTypeEnum.seasonalLunar, chartTypeEnum.bar].includes(o.type)) { // @ts-ignore (config as cartesianAxesChartType).xAxis[0].axisLabel!.formatter = '{value}'; } if ([chartTypeEnum.structural].includes(o.type)) { (config as structuralChartConfigType).structuralOffsetUnit = structuralOffsetUnitEnum.natureDay; (config as structuralChartConfigType).structuralOffset = o.structXLabel.ifStruct ? o.structXLabel.list.map((item) => item.value).join(',') : '30,15,7,1,0'; (config as structuralChartConfigType).seriesSetting.push({ name: '-0D', label: true, seriesType: echartSeriesTypeEnum.line, }); } if (o.sortMonth.ifSortMonth) { const all = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; const l = o.sortMonth.list; config.timeConfig.startMonth = l[0]; config.timeConfig.sortMonth = difference(all, l); } if (o.quantile.ifQuantile) { (o.config as quantileRadarChartConfigType).quantileOffset = o.quantile.list .map((item) => item.value) .join(','); } config.quotaList = o.rows.map((row) => { const r = omit(row, ['show', 'seriesCfg']) as SelectedQuotaItem; r.selected = row.show || row.selected; if (r.sourceType === SourceTypeEnum.formula && !Reflect.has(r, 'id')) { // @ts-ignore r.id = UuidUtil.buildShortUUID('formula'); } config.seriesSetting.push({ name: row.seriesCfg.title, label: false, xAxisIndex: 0, yAxisIndex: row.seriesCfg.yAxisIndex ?? 0, size: row.seriesCfg.lineWidth ?? 2, seriesType: row.seriesCfg.type, }); return r; }); return config; } export function proChart(options) { return options.config; } export function hotTable(options) { return options.config; } export function SQLTable(options) { return options.config; } export function specialChart(options) { return options.config; } // 旧版表格转换 export function oldDataTable(options) { const config = options.config; const res: TableConfigType = { title: config.title, mergeCells: [], sheetData: [], autoDateCells: [], http: config.http ?? true, fixData: config.fixData || [], conditionalFormatting: [], hiddenColumns: [], customStyle: [], }; const sheetData: (string | null)[][] = []; for (let n = 0; n < config.tables.length; n++) { const table = config.tables[n]; const dataNum = table.dataNum; const dateArray: string[] = []; for (let i = 0; i < dataNum; i++) { dateArray.push(`=TDATE("${i - dataNum + 1}d")`); res.autoDateCells.push({ row: 1, col: 2 + i }); } if (n === 0) { sheetData.push(['类别', 'ID', '指标名称', ...dateArray]); } else { sheetData[0] = [...sheetData[0], ...['类别', 'ID', '指标名称', ...dateArray]]; } const dataList: any = table.dataList; for (let j = 0; j < dataList.length; j++) { const item: string[] = []; item.push(dataList[j]['category']); item.push(String(dataList[j]['id'])); item.push(`=IDXNAME(${['B', 'H'][n]}${2 + j})`); for (let m = 0; m < dataNum; m++) { item.push( `=IDX(${['B', 'H'][n]}${2 + j},${String.fromCharCode(m + ['D', 'J'][n].charCodeAt(0))}1)`, ); } if (n === 0) { sheetData.push(item); } else { sheetData[j + 1] = [...(sheetData[j + 1] ?? []), ...item]; } } const changeStatus: boolean = table.showChange || table.showPercent; if (changeStatus) { sheetData[0].push('涨跌变化'); for (let v = 1; v < sheetData.length; v++) { sheetData[v].push( `=DIFF(${String.fromCharCode(dataNum - 1 + 'C'.charCodeAt(0))}${ v + 1 },${String.fromCharCode(dataNum + 'C'.charCodeAt(0))}${v + 1})`, ); } } } const mergeCells: any[] = []; for (let i = 0; i < sheetData[0].length; i++) { let index = -1; if (sheetData[0][i] === '类别') { index = i; let name: Nullable = null; for (let j = 1; j < sheetData.length; j++) { if (name !== sheetData[j][index]) { name = sheetData[j][index]; } else { sheetData[j][index] = null; } } const namePos: number[] = []; for (let j = 1; j < sheetData.length; j++) { if (null !== sheetData[j][index]) { namePos.push(j); } } namePos.push(sheetData.length); if (namePos.length > 1) { for (let k = namePos.length; k > 0; k--) { const rowspan = namePos[k] - namePos[k - 1]; if (rowspan > 1) { mergeCells.push({ row: namePos[k - 1], col: index, colspan: 1, rowspan }); } } } } } res.sheetData = sheetData; res.mergeCells = mergeCells; return res; } // 旧SQL表格转换函数 export function oldSQLTable(options) { const config = options.config; const res: SQLTableConfigType = { title: config.title, database: config.database, tableName: config.tableName, dateColumn: config.dateColumn, columns: [], date: '', sortColumn: config.sortColumn, options: config.options, style: '', http: config.http ?? true, fixData: config.fixData || [], }; const style: Recordable[] = []; config.columns .filter((col) => !col.defaultHidden) .forEach((col) => { res.columns.push({ label: col.title, value: col.dataIndex }); style.push({ key: col.dataIndex, cls: 'min-w-100px' }); }); res.style = JSON.stringify(style).replace(/\"(\w+)\":/g, '$1:'); return res; } // 自动转换版本 export function autoVersionTransfer(item: TemplateItem & { options: string }) { const { pingChart, huiChart, proChart, oldDataTable, oldSQLTable, hotTable, SQLTable, specialChart, } = useVersionTransfer(); const json = JSON.parse(item.options); if ( Reflect.has(json, 'config') && Reflect.has(json['config'], 'lastMulti') && json.config.version !== 2 ) { item.version = tempConfigEnum.Chart; item.config = huiChart(json); item.name = item.config.name!; } else if (Reflect.has(json, 'option_template_type')) { item.version = tempConfigEnum.Chart; item.config = pingChart(json); } else if (Reflect.has(json, 'config') && Reflect.has(json['config'], 'tableNum')) { item.version = tempConfigEnum.HOTTable; item.config = oldDataTable(json); item.name = item.config.title; } else if ( Reflect.has(json, 'config') && Reflect.has(json['config'], 'database') && Reflect.has(json['config'], 'columns') && Reflect.has(json['config']['columns'][0], 'dataIndex') ) { item.version = tempConfigEnum.SQLTable; item.config = oldSQLTable(json); item.name = item.config.title; } else if ( Reflect.has(json, 'config') && Reflect.has(json['config'], 'database') && Reflect.has(json['config'], 'columns') ) { item.version = tempConfigEnum.SQLTable; item.config = SQLTable(json); item.name = item.config.title; } else if (Reflect.has(json, 'config') && Reflect.has(json['config'], 'sheetData')) { item.version = tempConfigEnum.HOTTable; item.config = hotTable(json); item.name = item.config.title; } else if (Reflect.has(json, 'config') && Reflect.has(json['config'], 'specialType')) { item.version = tempConfigEnum.SpecialChart; item.config = specialChart(json); item.name = item.config.title; } else { item.version = tempConfigEnum.Chart; item.config = proChart(json); item.name = item.config.title; } item.categoryId = json.category_id; return item; } export function useVersionTransfer() { return { pingChart: pingChart(), huiChart: huiChart, proChart: proChart, oldDataTable: oldDataTable, oldSQLTable: oldSQLTable, boReport: boReport, hotTable: hotTable, SQLTable: SQLTable, specialChart: specialChart, }; } // 旧文本组件转新文本组件 export function oldText(options: Recordable, type: 'Title' | 'Comment'): TextConfigType { const config = options.config; if (type === 'Title') { const styles = [ { fontsize: 20, fontweight: 'bold' }, { fontsize: 18, fontweight: 'bold' }, { fontsize: 14, fontweight: 'normal' }, ]; const style = styles[config.titleType - 1]; const htmlStr = `
${config.indexStr}${config.text}
`; return { text: htmlStr, }; } else { const text = config.textarea.replaceAll('\n', '
'); const htmlStr = `
${text}
`; return { text: htmlStr, }; } } // 旧版文本组件自动设置大小 function oldTextModSize(options: Recordable, type: 'Title' | 'Comment') { const config = options.config; if (type === 'Title') { return { gridColumn: 30, gridRow: 2, }; } else { return { gridColumn: Math.round((config.percentWidth / 100) * 30), gridRow: Math.round(config.height / 40), }; } } // 自动设置大小 function autoModSize(options: Recordable) { const config = options.config; if (['Title', 'Comment'].includes(options.type)) { return oldTextModSize(options, options.type); } else { return { gridColumn: Math.round((config.percentWidth / 100) * 30), gridRow: Math.round(config.height / 40), }; } } // 旧图片转新图片 export function oldImage(options: Recordable) { const config = options.config; const res: ImgConfigType = { url: config.url, mode: config.imgShow, }; return res; } export const pageDefaultSetting: { [role: string]: pageSettingType } = { DC: { header: { show: true, left: '笃初诚美 慎终宜令', center: '', right: '上海笃诚私募基金管理有限公司', }, footer: { show: true, left: '内部资料 禁止分发', center: '', right: '本报告来自笃数投研平台', }, showElementborder: true, scale: 100, logo: '/dc-logo.png', shortName: '笃诚', }, JY: { header: { show: true, left: '守正思源 · 求和图新', center: '', right: '嘉悦物产集团', }, footer: { show: true, left: '内部资料 禁止分发', center: '', right: '本报告来自笃数投研平台', }, showElementborder: true, scale: 100, logo: '/jy-logo.png', shortName: '嘉悦', }, default: { header: { show: true, left: '', center: '', right: '', }, footer: { show: true, left: '', center: '', right: '', }, showElementborder: true, scale: 100, logo: '', shortName: '', }, }; // 旧报告转新报告 export function boReport(options, uniqStr: Ref, company: 'DC' | 'JY') { const typeMap = { Chart: 'Chart', QuotaTable: 'HOTTable', SQLTable: 'SQLTable', Comment: 'Text', Image: 'Img', Title: 'Text', }; const transformMap = { Chart: huiChart, QuotaTable: oldDataTable, SQLTable: oldSQLTable, Comment: (options) => oldText(options, 'Comment'), Image: oldImage, Title: (options) => oldText(options, 'Title'), }; const report: reportInfoType = { title: options.reportName, page: pageDefaultSetting[company], templateConfigList: [], templateList: [], }; const list = options.template; for (const item of list) { report.templateList.push({ id: item.id, uniqId: getUniqueField(uniqStr), rectInfo: autoModSize(item), version: typeMap[item.type], config: transformMap[item.type]({ config: item.config }), }); } return { report }; } export function autoReportTransfer( options, uniqStr: Ref, company: 'DC' | 'JY', ): { report: reportInfoType } { options.title = options.reportName; if (Reflect.has(options, 'reportset')) { return boReport(options, uniqStr, company); } else { // 重置一下uniqid const templateList = (options as reportInfoType).templateList; templateList.forEach((temp) => { temp.uniqId = getUniqueField(uniqStr); }); return { report: options, }; } }