From 2b5a602b49ad21cedfbf9b940bfb877207fbd52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E7=A7=8B=E6=97=AD?= Date: Thu, 22 Jan 2026 11:30:00 +0800 Subject: [PATCH] update generateXlsx --- scripts/generateXlsx.js | 149 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 15 deletions(-) diff --git a/scripts/generateXlsx.js b/scripts/generateXlsx.js index e734772..8167650 100644 --- a/scripts/generateXlsx.js +++ b/scripts/generateXlsx.js @@ -1,43 +1,162 @@ import ExcelJS from 'exceljs'; +import fs from 'fs/promises'; +import path from 'path'; /** - * Generates an XLSX file from the given data. - * @param {Array} data - The data to convert to XLSX. - * @param {string} filePath - The path to save the XLSX file. + * Flattens the nested JSON data into separate arrays for projects, licenses, and buildings. + * @param {Array} data - The nested data from merged_data.json. + * @returns {{projects: Array, licenses: Array, buildings: Array}} */ +function flattenData(data) { + const projects = []; + const licenses = []; + const buildings = []; -async function generateXlsx(data, filePath) { + data.forEach(project => { + // Create a shallow copy to avoid modifying the original object in memory + const projectCopy = { ...project }; + const licensesData = projectCopy['预售许可证']; + // The nested array will not be included in the flattened project data + delete projectCopy['预售许可证']; + projects.push(projectCopy); + + if (licensesData && Array.isArray(licensesData)) { + licensesData.forEach(license => { + const licenseCopy = { ...license }; + const buildingsData = licenseCopy['楼幢']; + // The nested array will not be included in the flattened license data + delete licenseCopy['楼幢']; + + // Add a key to link back to the parent project + licenseCopy['项目名称_key'] = project['项目名称']; + licenses.push(licenseCopy); + + if (buildingsData && Array.isArray(buildingsData)) { + buildingsData.forEach(building => { + // Add keys to link back to the parent license and project + const buildingCopy = { + ...building, + '许可证号_key': license['许可证号'], + '项目名称_key': project['项目名称'], + }; + buildings.push(buildingCopy); + }); + } + }); + } + }); + + return { projects, licenses, buildings }; +} + + +/** + * Adds a worksheet to the workbook with the given data, headers, and styling. + * @param {ExcelJS.Workbook} workbook - The workbook instance. + * @param {string} sheetName - The name for the new worksheet. + * @param {Array} data - The array of data for the sheet. + */ +function addSheet(workbook, sheetName, data) { if (data.length === 0) { - console.log(`没有数据可生成 XLSX 文件 (${filePath})。`); + console.log(`- 注意: 没有数据可用于工作表 '${sheetName}'`); return; } - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet('Data'); + const worksheet = workbook.addWorksheet(sheetName); + // Get all unique keys from all objects to form a complete header + const allKeys = data.reduce((keys, item) => { + if (item) { + Object.keys(item).forEach(key => { + if (!keys.includes(key)) { + keys.push(key); + } + }); + } + return keys; + }, []); - const headers = Object.keys(data[0]); - worksheet.columns = headers.map(key => ({ + worksheet.columns = allKeys.map(key => ({ header: key, key: key, - width: key.includes('地址') || key.includes('链接') ? 40 : 20 + width: key.includes('地址') || key.includes('链接') || key.includes('简介') ? 50 : 25 })); + worksheet.addRows(data); + // Style header row worksheet.getRow(1).eachCell(cell => { cell.font = { bold: true }; cell.fill = { type: 'pattern', pattern: 'solid', - fgColor: { argb: 'FFDDDDDD' } + fgColor: { argb: 'FFD3D3D3' } }; - cell.alignment = { vertical: 'middle', horizontal: 'center' }; + cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }; }); + + // Add autofilter to the header row worksheet.autoFilter = { from: 'A1', to: { row: 1, - column: headers.length + column: allKeys.length } }; - await workbook.xlsx.writeFile(filePath); - console.log(`已生成 ${filePath} 文件。`); } + + +/** + * Generates a multi-sheet XLSX file from the nested real estate data. + * @param {Array} data - The nested data from merged_data.json. + * @param {string} filePath - The path to save the XLSX file. + */ +async function generateXlsx(data, filePath) { + if (!data || data.length === 0) { + console.log(`没有数据可生成 XLSX 文件 (${filePath})。`); + return; + } + + console.log('正在将数据处理成多个工作表...'); + const { projects, licenses, buildings } = flattenData(data); + console.log(`- 项目: ${projects.length} 条`); + console.log(`- 许可证: ${licenses.length} 条`); + console.log(`- 楼幢: ${buildings.length} 条`); + + const workbook = new ExcelJS.Workbook(); + workbook.creator = 'Gemini Assistant'; + workbook.created = new Date(); + workbook.modified = new Date(); + + console.log('正在创建 Excel 工作表...'); + addSheet(workbook, '项目', projects); + addSheet(workbook, '预售许可证', licenses); + addSheet(workbook, '楼幢', buildings); + + await workbook.xlsx.writeFile(filePath); + console.log(`✅ 成功生成多工作表 Excel 文件: ${filePath}`); +} + +/** + * Main function to read data and trigger XLSX generation. + */ +async function run() { + console.log('🚀 开始生成 Excel 分析文件...'); + const dataPath = path.join(process.cwd(), 'data', 'merged_data.json'); + const outputPath = path.join(process.cwd(), '普宁房产数据分析.xlsx'); + + try { + await fs.access(dataPath); + console.log(`读取数据源: ${dataPath}`); + const jsonData = JSON.parse(await fs.readFile(dataPath, 'utf-8')); + await generateXlsx(jsonData, outputPath); + } catch (error) { + if (error.code === 'ENOENT') { + console.error(`❌ 错误: 未找到数据文件 '${path.basename(dataPath)}'。`); + console.error('请先运行 `npm start` 来生成完整的数据文件。'); + } else { + console.error('❌ 生成 XLSX 文件时发生错误:', error); + } + process.exit(1); + } +} + +run();