494 lines
21 KiB
TypeScript
494 lines
21 KiB
TypeScript
import * as XLSX from 'xlsx';
|
||
import type { ReportData, SunburstNode, RegionData, DurationStats, FunnelItem, BarChartCompareItem } from '../types/data';
|
||
|
||
/**
|
||
* Excel Sheets Structure:
|
||
* 1. 基础信息: Key-Value pairs for cover page
|
||
* 2. 地图展示: Columns [省份, 数值]
|
||
* 3. 地区统计: Columns [地区, 采集数量, 转换率, 成功率, 采集时长]
|
||
* 4. 时长分布: Columns [地区, 0-5分钟, 5-10分钟, 10-20分钟, 20分钟以上]
|
||
* 5. 页面漏损: Columns [环节, 占比]
|
||
* 6. 企业全景洞察: Columns [一级行业, 二级行业, 数值]
|
||
* 7. 劣质客户洞察: Columns [一级行业, 二级行业, 数值]
|
||
* 8. 优质客户洞察: Columns [一级行业, 二级行业, 数值]
|
||
* 9. 风险占比: Columns [指标类型, 标签, 数值, 全量大盘数值]
|
||
* 10. 出口退税分布: Columns [指标类型, 标签, 本机构数值, 全量客户数值]
|
||
* 11. 关联企业分布: Columns [指标类型, 标签, 本机构数值, 全量客户数值]
|
||
* 12. 风险扫描分布: Columns [标签, 本机构数值, 全量客户数值]
|
||
* 13. 图表描述: Columns [页面模块, 指标类型, 描述, 填写说明]
|
||
*/
|
||
|
||
interface MapRow {
|
||
省份: string;
|
||
数值: number;
|
||
}
|
||
|
||
interface BaseInfoRow {
|
||
字段: string;
|
||
数值: string | number;
|
||
}
|
||
|
||
interface RegionRow {
|
||
地区: string;
|
||
采集数量: number;
|
||
转换率: string;
|
||
成功率: string;
|
||
采集时长: string | number;
|
||
}
|
||
|
||
interface DurationRow {
|
||
地区: string;
|
||
'0-5分钟': string;
|
||
'5-10分钟': string;
|
||
'10-20分钟': string;
|
||
'20分钟以上': string;
|
||
}
|
||
|
||
interface FunnelRow {
|
||
环节: string;
|
||
占比: number;
|
||
}
|
||
|
||
interface PortraitRow {
|
||
一级行业: string;
|
||
二级行业: string;
|
||
数值: number;
|
||
}
|
||
|
||
interface RiskPieRow {
|
||
指标类型: 'invoiceRisk' | 'highTech' | 'importExport' | 'prepaidTax';
|
||
标签: string;
|
||
数值: number;
|
||
全量大盘数值: number;
|
||
描述?: string;
|
||
}
|
||
|
||
interface ExportTaxRow {
|
||
指标类型: 'exportFreeTaxRebate' | 'exportTaxRebate';
|
||
标签: string;
|
||
本机构数值: number;
|
||
全量客户数值: number;
|
||
描述?: string;
|
||
}
|
||
|
||
interface AssociationRow {
|
||
指标类型: 'loginPersonAssociation' | 'legalPersonAssociation';
|
||
标签: string;
|
||
本机构数值: number;
|
||
全量客户数值: number;
|
||
描述?: string;
|
||
}
|
||
|
||
interface RiskScanningRow {
|
||
指标类型?: 'riskScanning';
|
||
标签: string;
|
||
本机构数值: number;
|
||
全量客户数值: number;
|
||
描述?: string;
|
||
}
|
||
|
||
interface BarChartDescRow {
|
||
指标类型: 'exportFreeTaxRebate' | 'exportTaxRebate' | 'loginPersonAssociation' | 'legalPersonAssociation' | 'riskScanning';
|
||
描述: string;
|
||
}
|
||
|
||
import { defaultReportData } from '../data/defaultData';
|
||
|
||
type ChartDescriptionKey = keyof NonNullable<ReportData['chartDescriptions']>;
|
||
|
||
const chartDescriptionKeys: ChartDescriptionKey[] = [
|
||
'exportFreeTaxRebate',
|
||
'exportTaxRebate',
|
||
'loginPersonAssociation',
|
||
'legalPersonAssociation',
|
||
'riskScanning',
|
||
];
|
||
|
||
const ensurePercent = (val: unknown): string => {
|
||
if (val === undefined || val === null) return '0%';
|
||
if (typeof val === 'number') {
|
||
return `${(val * 100).toFixed(2)}%`;
|
||
}
|
||
const str = String(val).trim();
|
||
if (!str) return '0%';
|
||
if (str.includes('%')) return str;
|
||
const num = parseFloat(str);
|
||
if (!isNaN(num)) {
|
||
return `${(num * 100).toFixed(2)}%`;
|
||
}
|
||
return str.endsWith('%') ? str : `${str}%`;
|
||
};
|
||
|
||
const hasBarChartLabel = (row: { 标签?: unknown }): boolean => {
|
||
return String(row['标签'] ?? '').trim().length > 0;
|
||
};
|
||
|
||
const isChartDescriptionKey = (value: unknown): value is ChartDescriptionKey => {
|
||
return chartDescriptionKeys.includes(value as ChartDescriptionKey);
|
||
};
|
||
|
||
const setChartDescription = (
|
||
reportData: ReportData,
|
||
key: unknown,
|
||
description: unknown
|
||
): void => {
|
||
if (!isChartDescriptionKey(key)) return;
|
||
const text = String(description ?? '').trim();
|
||
if (!text) return;
|
||
if (!reportData.chartDescriptions) reportData.chartDescriptions = {};
|
||
reportData.chartDescriptions[key] = text;
|
||
};
|
||
|
||
const formatNumber = (val: unknown): number => {
|
||
if (val === undefined || val === null) return 0;
|
||
const num = parseFloat(String(val));
|
||
if (isNaN(num)) return 0;
|
||
return Math.round(num * 100) / 100;
|
||
};
|
||
|
||
export const parseExcelReportData = async (file: File): Promise<ReportData> => {
|
||
return new Promise((resolve, reject) => {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
try {
|
||
const data = new Uint8Array(e.target?.result as ArrayBuffer);
|
||
const workbook = XLSX.read(data, { type: 'array' });
|
||
|
||
// Use default as base to ensure all keys exist
|
||
const reportData = JSON.parse(JSON.stringify(defaultReportData)) as ReportData;
|
||
|
||
// 1. 基础信息
|
||
const baseSheet = workbook.Sheets['基础信息'];
|
||
if (baseSheet) {
|
||
const baseRows = XLSX.utils.sheet_to_json<BaseInfoRow>(baseSheet, { raw: true });
|
||
const baseObj: Record<string, unknown> = {};
|
||
baseRows.forEach(row => {
|
||
baseObj[row['字段']] = row['数值'];
|
||
});
|
||
reportData.companyName = String(baseObj['公司名称'] || '未知企业');
|
||
reportData.stats = {
|
||
successRate: ensurePercent(baseObj['采集成功率']),
|
||
collectedCount: parseInt(String(baseObj['累计采集户数'])) || 0,
|
||
growthRate: ensurePercent(baseObj['月增长率']),
|
||
fiveMinRate: ensurePercent(baseObj['5分钟内完成比例']),
|
||
avgDuration: formatNumber(baseObj['平均采集时长']),
|
||
};
|
||
reportData.portraitDescriptions = {
|
||
all: String(baseObj['企业全景洞察描述'] || ''),
|
||
poor: String(baseObj['风险客户洞察描述'] || ''),
|
||
good: String(baseObj['优质客户洞察描述'] || ''),
|
||
};
|
||
}
|
||
|
||
// 2. 地图展示
|
||
const mapSheet = workbook.Sheets['地图展示'];
|
||
if (mapSheet) {
|
||
reportData.mapData = XLSX.utils.sheet_to_json<MapRow>(mapSheet).map((row) => ({
|
||
name: row['省份'],
|
||
value: row['数值'] || 0,
|
||
}));
|
||
}
|
||
|
||
// 3. 地区统计
|
||
const regionSheet = workbook.Sheets['地区统计'];
|
||
if (regionSheet) {
|
||
reportData.regionStatistics = XLSX.utils.sheet_to_json<RegionRow>(regionSheet, { raw: true }).map((row): RegionData => ({
|
||
region: row['地区'],
|
||
count: parseInt(String(row['采集数量'])) || 0,
|
||
conversion: ensurePercent(row['转换率']),
|
||
success: ensurePercent(row['成功率']),
|
||
duration: String(formatNumber(row['采集时长'] || '0')),
|
||
}));
|
||
}
|
||
|
||
// 3. 时长分布
|
||
const durationSheet = workbook.Sheets['时长分布'];
|
||
if (durationSheet) {
|
||
reportData.durationStatistics = XLSX.utils.sheet_to_json<DurationRow>(durationSheet, { raw: true }).map((row): DurationStats => ({
|
||
region: row['地区'],
|
||
p0_5: ensurePercent(row['0-5分钟']),
|
||
p5_10: ensurePercent(row['5-10分钟']),
|
||
p10_20: ensurePercent(row['10-20分钟']),
|
||
p20plus: ensurePercent(row['20分钟以上']),
|
||
}));
|
||
}
|
||
|
||
// 4. 页面漏损
|
||
const funnelSheet = workbook.Sheets['页面漏损'];
|
||
if (funnelSheet) {
|
||
reportData.funnelData = XLSX.utils.sheet_to_json<FunnelRow>(funnelSheet).map((row): FunnelItem => ({
|
||
label: row['环节'],
|
||
value: row['占比'] * 100 || 0,
|
||
}));
|
||
}
|
||
|
||
// 5. 企业全景洞察
|
||
const BLUE_PALETTE = ['#1B65B5', '#2386EE', '#52A3FF', '#85C1FF', '#B8DEFF'];
|
||
|
||
const buildSunburst = (rows: PortraitRow[]) => {
|
||
const categories: Record<string, SunburstNode> = {};
|
||
rows.forEach(row => {
|
||
const l1 = row['一级行业'];
|
||
const l2 = row['二级行业'];
|
||
const val = row['数值'] || 0;
|
||
if (!categories[l1]) {
|
||
categories[l1] = { name: l1, children: [] };
|
||
}
|
||
categories[l1].children?.push({ name: l2, value: val });
|
||
});
|
||
return Object.values(categories).map((cat, idx) => {
|
||
const color = BLUE_PALETTE[idx % BLUE_PALETTE.length];
|
||
return {
|
||
...cat,
|
||
itemStyle: { color },
|
||
children: cat.children?.map(child => ({ ...child, itemStyle: { color } }))
|
||
};
|
||
});
|
||
};
|
||
|
||
const allSheet = workbook.Sheets['企业全景洞察'];
|
||
if (allSheet) {
|
||
reportData.sunburstData = buildSunburst(XLSX.utils.sheet_to_json<PortraitRow>(allSheet));
|
||
}
|
||
|
||
const poorSheet = workbook.Sheets['劣质客户洞察'];
|
||
if (poorSheet) {
|
||
reportData.poorCustomerData = buildSunburst(XLSX.utils.sheet_to_json<PortraitRow>(poorSheet));
|
||
}
|
||
|
||
const goodSheet = workbook.Sheets['优质客户洞察'];
|
||
if (goodSheet) {
|
||
reportData.goodCustomerData = buildSunburst(XLSX.utils.sheet_to_json<PortraitRow>(goodSheet));
|
||
}
|
||
|
||
// 6. 风险占比
|
||
const riskPieSheet = workbook.Sheets['风险占比'];
|
||
if (riskPieSheet) {
|
||
const rows = XLSX.utils.sheet_to_json<RiskPieRow>(riskPieSheet);
|
||
rows.forEach(row => {
|
||
const key = row['指标类型'];
|
||
if (key) {
|
||
reportData.riskIndicators[key] = {
|
||
label: row['标签'],
|
||
value: row['数值'] || 0,
|
||
marketValue: row['全量大盘数值'] || 0,
|
||
description: row['描述'] || '',
|
||
};
|
||
}
|
||
});
|
||
}
|
||
|
||
// 7. 出口退税分布
|
||
const exportTaxSheet = workbook.Sheets['出口退税分布'];
|
||
if (exportTaxSheet) {
|
||
const rows = XLSX.utils.sheet_to_json<ExportTaxRow>(exportTaxSheet);
|
||
const groups: Record<string, BarChartCompareItem[]> = {};
|
||
rows.forEach(row => {
|
||
const cat = row['指标类型'];
|
||
setChartDescription(reportData, cat, row['描述']);
|
||
if (cat && hasBarChartLabel(row)) {
|
||
if (!groups[cat]) groups[cat] = [];
|
||
groups[cat].push({
|
||
label: String(row['标签']).trim(),
|
||
customerValue: formatNumber(row['本机构数值']),
|
||
marketValue: formatNumber(row['全量客户数值']),
|
||
});
|
||
}
|
||
});
|
||
if (groups['exportFreeTaxRebate']) reportData.exportFreeTaxRebate = groups['exportFreeTaxRebate'];
|
||
if (groups['exportTaxRebate']) reportData.exportTaxRebate = groups['exportTaxRebate'];
|
||
}
|
||
|
||
// 8. 关联企业分布
|
||
const associationSheet = workbook.Sheets['关联企业分布'];
|
||
if (associationSheet) {
|
||
const rows = XLSX.utils.sheet_to_json<AssociationRow>(associationSheet);
|
||
const groups: Record<string, BarChartCompareItem[]> = {};
|
||
rows.forEach(row => {
|
||
const cat = row['指标类型'];
|
||
setChartDescription(reportData, cat, row['描述']);
|
||
if (cat && hasBarChartLabel(row)) {
|
||
if (!groups[cat]) groups[cat] = [];
|
||
groups[cat].push({
|
||
label: String(row['标签']).trim(),
|
||
customerValue: formatNumber(row['本机构数值']),
|
||
marketValue: formatNumber(row['全量客户数值']),
|
||
});
|
||
}
|
||
});
|
||
if (groups['loginPersonAssociation']) reportData.loginPersonAssociation = groups['loginPersonAssociation'];
|
||
if (groups['legalPersonAssociation']) reportData.legalPersonAssociation = groups['legalPersonAssociation'];
|
||
}
|
||
|
||
// 9. 风险扫描分布
|
||
const riskScanningSheet = workbook.Sheets['风险扫描分布'];
|
||
if (riskScanningSheet) {
|
||
const rows = XLSX.utils.sheet_to_json<RiskScanningRow>(riskScanningSheet);
|
||
rows.forEach(row => {
|
||
setChartDescription(reportData, row['指标类型'], row['描述']);
|
||
});
|
||
reportData.riskScanning = rows.filter(hasBarChartLabel).map((row): BarChartCompareItem => ({
|
||
label: String(row['标签']).trim(),
|
||
customerValue: formatNumber(row['本机构数值']),
|
||
marketValue: formatNumber(row['全量客户数值']),
|
||
}));
|
||
}
|
||
|
||
// 10. 图表描述(新模板优先)
|
||
const chartDescriptionSheet = workbook.Sheets['图表描述'];
|
||
if (chartDescriptionSheet) {
|
||
const rows = XLSX.utils.sheet_to_json<BarChartDescRow>(chartDescriptionSheet);
|
||
rows.forEach(row => {
|
||
setChartDescription(reportData, row['指标类型'], row['描述']);
|
||
});
|
||
}
|
||
|
||
resolve(reportData);
|
||
} catch (err) {
|
||
reject(err);
|
||
}
|
||
};
|
||
reader.onerror = reject;
|
||
reader.readAsArrayBuffer(file);
|
||
});
|
||
};
|
||
|
||
export const generateExcelTemplate = () => {
|
||
const wb = XLSX.utils.book_new();
|
||
|
||
// Helper to ensure cells are treated as strings to avoid auto-formatting
|
||
const toSheet = (data: Record<string, unknown>[]) => {
|
||
const ws = XLSX.utils.json_to_sheet(data);
|
||
// Force all cells to be strings to avoid Excel's "helpful" formatting
|
||
Object.keys(ws).forEach(key => {
|
||
if (key[0] === '!') return;
|
||
const cell = ws[key];
|
||
if (cell.v !== undefined) {
|
||
cell.t = 's'; // Set type to string
|
||
cell.v = String(cell.v);
|
||
}
|
||
});
|
||
return ws;
|
||
};
|
||
|
||
// 1. 基础信息
|
||
const baseWS = toSheet([
|
||
{ '字段': '公司名称', '数值': '某某科技公司' },
|
||
{ '字段': '采集成功率', '数值': '99.99%' },
|
||
{ '字段': '累计采集户数', '数值': '1200' },
|
||
{ '字段': '月增长率', '数值': '12%' },
|
||
{ '字段': '5分钟内完成比例', '数值': '95%' },
|
||
{ '字段': '平均采集时长', '数值': '3.5' },
|
||
{ '字段': '企业全景洞察描述', '数值': '基于行业分布与企业特征,全面展示企业画像数据。' },
|
||
{ '字段': '风险客户洞察描述', '数值': '识别可能存在合作风险的客户群体,帮助您提前制定应对策略。' },
|
||
{ '字段': '优质客户洞察描述', '数值': '展示高价值优质客户特征,帮助您优化服务策略。' },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, baseWS, '基础信息');
|
||
|
||
// 2. 地图展示
|
||
const mapWS = XLSX.utils.json_to_sheet([
|
||
{ '省份': '广东省', '数值': 9058 },
|
||
{ '省份': '江苏省', '数值': 4712 },
|
||
{ '省份': '四川省', '数值': 4560 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, mapWS, '地图展示');
|
||
|
||
// 3. 地区统计
|
||
const regionWS = toSheet([
|
||
{ '地区': '广东省', '采集数量': '5000', '转换率': '85.5%', '成功率': '99.9%', '采集时长': '5.2' },
|
||
{ '地区': '江苏省', '采集数量': '4000', '转换率': '88.2%', '成功率': '99.5%', '采集时长': '4.8' },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, regionWS, '地区统计');
|
||
|
||
// 3. 时长分布
|
||
const durationWS = toSheet([
|
||
{ '地区': '广东省', '0-5分钟': '70%', '5-10分钟': '20%', '10-20分钟': '5%', '20分钟以上': '5%' },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, durationWS, '时长分布');
|
||
|
||
// 4. 页面漏损
|
||
const funnelWS = XLSX.utils.json_to_sheet([ // Numbers are fine for charts
|
||
{ '环节': '登录税局', '占比': 100 },
|
||
{ '环节': '签署授权', '占比': 85 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, funnelWS, '页面漏损');
|
||
|
||
// 5. 企业全景洞察
|
||
const allWS = XLSX.utils.json_to_sheet([
|
||
{ '一级行业': '制造业', '二级行业': '通用设备', '数值': 150 },
|
||
{ '一级行业': '制造业', '二级行业': '金属加工', '数值': 100 },
|
||
{ '一级行业': '批发业', '二级行业': '食品批发', '数值': 200 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, allWS, '企业全景洞察');
|
||
|
||
// 6. 劣质客户洞察
|
||
const poorWS = XLSX.utils.json_to_sheet([
|
||
{ '一级行业': '制造业', '二级行业': '通用设备', '数值': 30 },
|
||
{ '一级行业': '制造业', '二级行业': '金属加工', '数值': 20 },
|
||
{ '一级行业': '批发业', '二级行业': '食品批发', '数值': 50 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, poorWS, '劣质客户洞察');
|
||
|
||
// 7. 优质客户洞察
|
||
const goodWS = XLSX.utils.json_to_sheet([
|
||
{ '一级行业': '制造业', '二级行业': '通用设备', '数值': 120 },
|
||
{ '一级行业': '制造业', '二级行业': '金属加工', '数值': 80 },
|
||
{ '一级行业': '批发业', '二级行业': '食品批发', '数值': 150 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, goodWS, '优质客户洞察');
|
||
|
||
// 9. 风险占比
|
||
const riskPieWS = toSheet([
|
||
{ '指标类型': 'invoiceRisk', '标签': '发票高风险记录', '数值': '0.5', '全量大盘数值': '0.3', '描述': '统计近1年有效开票记录中,存在发票高风险的企业占比情况。' },
|
||
{ '指标类型': 'highTech', '标签': '高新技术企业', '数值': '5.2', '全量大盘数值': '4.8', '描述': '统计近1年有效开票记录中,被认定为高新技术企业的占比情况。' },
|
||
{ '指标类型': 'importExport', '标签': '进出口企业', '数值': '2.1', '全量大盘数值': '1.8', '描述': '统计近1年有效开票记录中,具有进出口资质的企业占比情况。' },
|
||
{ '指标类型': 'prepaidTax', '标签': '预缴税款企业', '数值': '15.0', '全量大盘数值': '12.5', '描述': '统计近1年有效开票记录中,存在预缴税款行为的企业占比情况。' },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, riskPieWS, '风险占比');
|
||
|
||
// 10. 出口退税分布
|
||
const exportTaxWS = XLSX.utils.json_to_sheet([
|
||
{ '指标类型': 'exportFreeTaxRebate', '标签': '出口免抵退税额0到100万', '本机构数值': 71.43, '全量客户数值': 75 },
|
||
{ '指标类型': 'exportFreeTaxRebate', '标签': '出口免抵退税额100到500万', '本机构数值': 16.67, '全量客户数值': 18 },
|
||
{ '指标类型': 'exportFreeTaxRebate', '标签': '出口免抵退税额500到1000万', '本机构数值': 0, '全量客户数值': 2 },
|
||
{ '指标类型': 'exportFreeTaxRebate', '标签': '出口免抵退税额1000万以上', '本机构数值': 11.9, '全量客户数值': 5 },
|
||
{ '指标类型': 'exportTaxRebate', '标签': '出口退税额0到100万', '本机构数值': 68.33, '全量客户数值': 70 },
|
||
{ '指标类型': 'exportTaxRebate', '标签': '出口退税额100到500万', '本机构数值': 21.67, '全量客户数值': 20 },
|
||
{ '指标类型': 'exportTaxRebate', '标签': '出口退税额500到1000万', '本机构数值': 8.33, '全量客户数值': 7 },
|
||
{ '指标类型': 'exportTaxRebate', '标签': '出口退税额1000万以上', '本机构数值': 1.67, '全量客户数值': 3 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, exportTaxWS, '出口退税分布');
|
||
|
||
// 11. 关联企业分布
|
||
const associationWS = XLSX.utils.json_to_sheet([
|
||
{ '指标类型': 'loginPersonAssociation', '标签': '登录人关联企业数量: 0~3', '本机构数值': 90.06, '全量客户数值': 92 },
|
||
{ '指标类型': 'loginPersonAssociation', '标签': '登录人关联企业数量: 4~10', '本机构数值': 8.58, '全量客户数值': 7 },
|
||
{ '指标类型': 'loginPersonAssociation', '标签': '登录人关联企业数量: 10+', '本机构数值': 1.36, '全量客户数值': 1 },
|
||
{ '指标类型': 'legalPersonAssociation', '标签': '法人代表关联企业数量: 0~3', '本机构数值': 92.91, '全量客户数值': 90 },
|
||
{ '指标类型': 'legalPersonAssociation', '标签': '法人代表关联企业数量: 4~10', '本机构数值': 6.49, '全量客户数值': 8 },
|
||
{ '指标类型': 'legalPersonAssociation', '标签': '法人代表关联企业数量: 10+', '本机构数值': 0.61, '全量客户数值': 2 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, associationWS, '关联企业分布');
|
||
|
||
// 12. 风险扫描分布
|
||
const riskScanningWS = XLSX.utils.json_to_sheet([
|
||
{ '标签': '无风险', '本机构数值': 77.58, '全量客户数值': 75 },
|
||
{ '标签': '低风险', '本机构数值': 12.41, '全量客户数值': 13 },
|
||
{ '标签': '中风险', '本机构数值': 10.01, '全量客户数值': 9 },
|
||
{ '标签': '高风险', '本机构数值': 0, '全量客户数值': 3 },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, riskScanningWS, '风险扫描分布');
|
||
|
||
// 13. 图表描述
|
||
const chartDescriptionWS = toSheet([
|
||
{ '页面模块': '不同出口免抵退税金额企业分布', '指标类型': 'exportFreeTaxRebate', '描述': '近1年有效开票金额最大月份占比', '填写说明': '请修改“描述”列;“指标类型”请勿修改。' },
|
||
{ '页面模块': '不同出口退税金额企业分布', '指标类型': 'exportTaxRebate', '描述': '近1年有效开票金额最大月份占比', '填写说明': '请修改“描述”列;“指标类型”请勿修改。' },
|
||
{ '页面模块': '登录人关联企业数量分布', '指标类型': 'loginPersonAssociation', '描述': '近1年有效开票金额最大月份占比', '填写说明': '请修改“描述”列;“指标类型”请勿修改。' },
|
||
{ '页面模块': '法定代表人关联企业数量分布', '指标类型': 'legalPersonAssociation', '描述': '近1年有效开票金额最大月份占比', '填写说明': '请修改“描述”列;“指标类型”请勿修改。' },
|
||
{ '页面模块': '企业风险扫描结果分布', '指标类型': 'riskScanning', '描述': '近1年有效开票金额最大月份占比', '填写说明': '请修改“描述”列;“指标类型”请勿修改。' },
|
||
]);
|
||
XLSX.utils.book_append_sheet(wb, chartDescriptionWS, '图表描述');
|
||
|
||
XLSX.writeFile(wb, '报告数据导入模板.xlsx');
|
||
};
|