h2 DB的一个问题,关于类型--Numeric value out of range

探讨H2数据库1.3.176版本中处理大数值乘法运算时出现的数值溢出问题,并通过调整运算顺序成功解决。

有一个表:

CREATE TABLEASSET(
    ASSET NUMERIC ,
    REAL NUMERIC ,
    TIMESTAMP NUMERIC ,
    TIMESTRING VARCHAR(100)
)

其中的TIMESTAMP NUMERIC 类型,实际存放的是UNIX Time,

可是有一次在做查询时发现一个问题:


SELECT * FROM ASSET where TIMESTAMP = 16461*1000*60*60*24;

59259600000溢出了,怎么回事?

单独试一下

select 16461*1000*60*60*24;

还是溢出,看来16461*1000*60*60*24就不行啊,但是这个是完全没有问题的,再试:

select 1422253516184/1000/60/60/24;

16461  
16461
(1 row, 1 ms)

这个可以,怎么回事?

为什么除可以,乘法就不可以?

再试一下:

select 1422253516184/1422253516184*16461*1000*60*60*24;

1422230400000  
1422230400000
(1 row, 0 ms)


竟然可以了,相信到这里大家也明白了,h2默认使用第一个变量的类型储存结果,越算越大就溢出了,如果初始化一个大类型,则可以解决此问题。


再实际查询一下:

SELECT * FROM ASSET where TIMESTAMP = 1422253516184/1422253516184*16461*1000*60*60*24;

ASSET   REAL   TIMESTAMP   TIMESTRING  
(no rows, 1 ms)


果然OK。


我用的h2是1.3.176,不知道后续版本是否有相同的问题。

-- ************************************************************************** -- -- * videotpg * -- -- *------------------------------------------------------------------------* -- -- * Module : TPG_LIB-RTL * -- -- * File : tpg_lib.vhd * -- -- * Date : 2022-03-16 * -- -- * Rev : 0.2 * -- -- * Author : SS * -- -- *------------------------------------------------------------------------* -- -- * package containing types, constants and addresses * -- -- * for the videotpg core. * -- -- *------------------------------------------------------------------------* -- -- * 0.1 | 2021-07-26 | SS | Initial release * -- -- * 0.2 | 2022-03-16 | SS | Added revision and core level constants * -- -- ************************************************************************** -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; -------------------------------------------------------------------------------- -- TPG_LIB package -------------------------------------------------------------------------------- package tpg_lib is -- TPG control information (axislave -> pattern generator) type TPG_CONTROL_T is record grab_en : std_logic; -- aquisition enable img_width : std_logic_vector(31 downto 0); -- width img_height : std_logic_vector(31 downto 0); -- height offs_x : std_logic_vector(31 downto 0); -- offset x offs_y : std_logic_vector(31 downto 0); -- offset y pixfmt : std_logic_vector(31 downto 0); -- pixel format gap_x : std_logic_vector(31 downto 0); -- Additional gap between lines gap_y : std_logic_vector(31 downto 0); -- Additional gap between frames chunk_ctrl : std_logic_vector(31 downto 0); -- Chunk control register (bit 31: activate extended chunk mode, bit 0: active framecounter chunk (not used)) end record TPG_CONTROL_T; -- TPG status information(pattern generator -> axislave) -- not used right now type TPG_STATUS_T is record -- add custom signals here void : std_logic; -- NOTE: Just a placeholder end record TPG_STATUS_T; -- chunk data array type CHUNK_DATA_ARRAY is array (integer range <>) of std_logic_vector(7 downto 0); -- Assign vector with byte enables function assign_be(orig : std_logic_vector; val : std_logic_vector; be : std_logic_vector) return std_logic_vector; -- Convert natural value to N bit std_logic_vector function aN(addr : natural; n : natural) return std_logic_vector; -- IP core revision and identification (updated automatically from Makefile!) constant REV_MAJOR : integer := 1; constant REV_MINOR : integer := 0; constant REV_SUBMINOR : integer := 4; constant REV_DATE : std_logic_vector(31 downto 0) := x"20250211"; constant REV_VERSION : std_logic_vector(31 downto 0) := std_logic_vector(to_unsigned(REV_MAJOR, 8) & to_unsigned(REV_MINOR, 8) & to_unsigned(REV_SUBMINOR, 16)); constant REV_ID : std_logic_vector(31 downto 0) := x"A5A5000D"; constant CORE_LEVEL : natural := 0; -- Pixel formats constant PIX_MONO8 : std_logic_vector(31 downto 0) := x"01080001"; constant PIX_MONO16 : std_logic_vector(31 downto 0) := x"01100007"; constant PIX_RGB8 : std_logic_vector(31 downto 0) := x"02180014"; constant CHUNKID_IMG : std_logic_vector(31 downto 0) := x"617D18DB"; -- ID of image chunk constant CHUNKID_FC : std_logic_vector(31 downto 0) := x"8C0F1CD8"; -- ID of frame counter chunk -- Default register values constant DEF_CONTROL : std_logic_vector(31 downto 0) := x"00000000"; constant DEF_IMG_WIDTH : std_logic_vector(31 downto 0) := x"00000400"; constant DEF_IMG_HEIGHT : std_logic_vector(31 downto 0) := x"00000400"; constant DEF_OFFS_X : std_logic_vector(31 downto 0) := x"00000000"; constant DEF_OFFS_Y : std_logic_vector(31 downto 0) := x"00000000"; constant DEF_PIXFMT : std_logic_vector(31 downto 0) := PIX_MONO8; constant DEF_GAP_X : std_logic_vector(31 downto 0) := x"000004DF"; constant DEF_GAP_Y : std_logic_vector(31 downto 0) := x"00000090"; constant DEF_GPIO_IN : std_logic_vector(31 downto 0) := x"00000000"; constant DEF_GPIO_OUT : std_logic_vector(31 downto 0) := x"00000000"; constant DEF_CHUNK_CTRL : std_logic_vector(31 downto 0) := x"00000000"; -- AXI4-Lite slave register map -- Register addresses must be equal to the corresponding addresses in the firmware constant ADDR_WIDTH : natural := 8; -- ... common status constant ADDR_GCSR : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"00"; constant ADDR_IMG_WIDTH : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"04"; constant ADDR_IMG_HEIGHT : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"08"; constant ADDR_OFFS_X : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"0C"; constant ADDR_OFFS_Y : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"10"; constant ADDR_PIXFMT : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"18"; constant ADDR_GAP_X : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"1C"; constant ADDR_GAP_Y : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"20"; constant ADDR_GPIO_IN : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"24"; constant ADDR_GPIO_OUT : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"28"; constant ADDR_CHUNK_CTRL : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"2C"; -- Chunk control register address constant ADDR_CHUNKID_IMG : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"30"; -- Image chunk id register address constant ADDR_CHUNKID_FC : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"34"; -- Frame counter chunk id register address constant ADDR_TPG_MODE : std_logic_vector(ADDR_WIDTH - 1 downto 0) := x"38"; end package tpg_lib; -------------------------------------------------------------------------------- -- TPG_LIB package body -------------------------------------------------------------------------------- package body tpg_lib is -- Assign vector with byte enables function assign_be(orig : std_logic_vector; val : std_logic_vector; be : std_logic_vector) return std_logic_vector is variable ret : std_logic_vector(orig'range) := orig; begin for i in 0 to ((orig'length / 8) - 1) loop if be(i) = '1' then ret((i * 8) + 7 downto i * 8) := val((i * 8) + 7 downto i * 8); end if; end loop; return ret; end function assign_be; -- Convert natural value to N bit std_logic_vector function aN(addr : natural; n : natural) return std_logic_vector is begin return std_logic_vector(to_unsigned(addr, n)); end function aN; end package body tpg_lib;以上是tgp.lib,请再次修改module axislave
最新发布
12-09
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>工资条生成工具</title> <!-- 引入SheetJS库用于处理Excel文件 --> <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> <!-- 关键修复:使用支持样式的xlsx-style库替换基础版xlsx库 --> <script src="https://unpkg.com/xlsx-style@0.15.6/dist/xlsx.full.min.js"></script> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: "Microsoft YaHei", "SimHei", Arial, sans-serif; width: 100%; min-height: 100vh; padding: 20px; font-size: 14px; color: #333; line-height: 1.5; } h1 { text-align: center; margin-bottom: 25px; font-size: 24px; color: #2c3e50; } h2 { font-size: 18px; margin: 15px 0; color: #3498db; } .file-upload { border: 2px dashed #95a5a6; padding: 30px 20px; text-align: center; margin-bottom: 30px; cursor: pointer; border-radius: 6px; transition: all 0.3s ease; background-color: #f8f9fa; } .file-upload:hover, .file-upload.active { border-color: #3498db; background-color: #f1f7fc; } .file-upload p { font-size: 16px; color: #7f8c8d; } #fileInput { display: none; } .controls { display: flex; justify-content: center; margin: 20px 0; } .btn { background-color: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.3s ease; display: flex; align-items: center; gap: 8px; } .btn:hover { background-color: #2980b9; } .btn:disabled { background-color: #bdc3c7; cursor: not-allowed; } #dataContainer { width: 100%; overflow-x: auto; margin: 0 auto; } table { width: 100%; min-width: 900px; border-collapse: collapse; table-layout: fixed; } th, td { border: 1px solid #ddd; padding: 12px 10px; text-align: left; word-wrap: break-word; word-break: break-all; height: 45px; } /* 数值列右对齐,方便查看 */ td.numeric { text-align: right; } /* 工资条表头样式 */ .salary-slip-header th { background-color: #ecf0f1; color: #2c3e50; font-weight: bold; position: sticky; top: 0; z-index: 2; } /* 合计行样式 */ .total-row td { background-color: #f8f9fa; font-weight: bold; } /* 签字行样式 */ .signature-row td { background-color: #f9f9f9; } tr:hover { background-color: #f1f7fc; } .message { color: #7f8c8d; text-align: center; padding: 50px 0; font-size: 16px; } .error { color: #e74c3c; } /* 无效数值样式 */ .invalid-value { background-color: #fff3cd; color: #856404; } .date-info { color: #2c3e50; font-size: 15px; margin: 15px 0; padding: 15px 12px; background-color: #f1f9f7; border-left: 3px solid #27ae60; border-radius: 4px; line-height: 1.8; min-height: 45px; } /* 响应式调整 */ @media (max-width: 1200px) { body { padding: 15px; font-size: 13px; } th, td { padding: 10px 8px; height: 40px; } .date-info { padding: 12px 10px; min-height: 40px; } } @media (max-width: 768px) { body { padding: 10px; font-size: 12px; } h1 { font-size: 20px; margin-bottom: 15px; } h2 { font-size: 16px; } .file-upload { padding: 20px 10px; margin-bottom: 20px; } th, td { padding: 8px 6px; height: 36px; } .date-info { padding: 10px 8px; min-height: 36px; } } </style> </head> <body> <h1>工资条生成工具</h1> <!-- 文件上传区域 --> <div class="file-upload" id="dropArea"> <p>点击或拖放工资表Excel文件到这里(支持多次上传更新)</p> <input type="file" id="fileInput" accept=".xlsx, .xls"> </div> <!-- 提取的信息 --> <div id="infoContainer" style="display: none;"> <div class="date-info"> <p><strong>公司名称:</strong><span id="companyName"></span></p> <p><strong>工资月份:</strong><span id="salaryMonth"></span></p> <p><strong>上次更新:</strong><span id="lastUpdated"></span></p> </div> </div> <!-- 控制按钮区域 --> <div class="controls"> <button id="exportBtn" class="btn" disabled> 导出工资条 </button> </div> <!-- 数据展示区域 --> <div id="dataContainer" style="display: none;"> <h2>工资条数据</h2> <table id="dataTable"> <tbody id="dataTableBody"> <!-- 提取的数据将在这里显示,每条数据带独立表头 --> </tbody> </table> </div> <!-- 消息区域 --> <div id="message" class="message">请上传符合格式要求的工资表Excel文件(.xlsx, .xls)</div> <script> // 获取DOM元素 const dropArea = document.getElementById('dropArea'); const fileInput = document.getElementById('fileInput'); const dataContainer = document.getElementById('dataContainer'); const dataTable = document.getElementById('dataTable'); const dataTableBody = document.getElementById('dataTableBody'); const message = document.getElementById('message'); const infoContainer = document.getElementById('infoContainer'); const companyName = document.getElementById('companyName'); const salaryMonth = document.getElementById('salaryMonth'); const lastUpdated = document.getElementById('lastUpdated'); const exportBtn = document.getElementById('exportBtn'); // 存储处理后的数据用于导出 let processedDataForExport = null; let currentDate = ''; // 定义预期的表头结构 const expectedHeaders = [ '序号', '部门', '姓名', '出勤', '基本工资', '岗位工资', '绩效', '浮动津贴', '扣除', '应发工资', '基本养老(个人)', '失业(个人)', '基本医疗(个人)', '大病(个人)', '公积金(个人)', '个税', '实发工资', '签字' ]; // 定义哪些列是数值类型(索引) const numericColumns = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; // 点击上传区域触发文件选择 dropArea.addEventListener('click', () => fileInput.click()); // 文件选择变化时处理 fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { handleFile(e.target.files[0]); // 重置input值,允许重复选择同一文件 fileInput.value = ''; } }); // 拖放功能增强 dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.classList.add('active'); }); dropArea.addEventListener('dragleave', () => { dropArea.classList.remove('active'); }); dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.classList.remove('active'); if (e.dataTransfer.files.length > 0) { handleFile(e.dataTransfer.files[0]); } }); // 导出按钮点击事件 exportBtn.addEventListener('click', exportToExcel); // 处理Excel文件 - 支持多次上传更新 function handleFile(file) { // 检查文件类型 if (!file.name.match(/\.(xlsx|xls)$/)) { showMessage('请上传Excel文件(.xlsx, .xls)', true); return; } showMessage(`正在解析文件: ${file.name}...`); const reader = new FileReader(); reader.onload = function(e) { try { // 读取并解析Excel文件 const data = new Uint8Array(e.target.result); const workbook = XLSX.read(data, { type: 'array' }); // 获取第一个工作表 const firstSheetName = workbook.SheetNames[1]; const worksheet = workbook.Sheets[firstSheetName]; // 转换为JSON const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); if (jsonData.length < 2) { showMessage('文件内容不符合要求,至少需要包含标题行和表头行', true); return; } // 处理数据并更新表格 processData(jsonData); // 更新最后上传时间 updateLastModifiedTime(); } catch (error) { showMessage('解析文件失败: ' + error.message, true); console.error(error); } }; reader.readAsArrayBuffer(file); } // 更新最后修改时间 function updateLastModifiedTime() { const now = new Date(); const formattedTime = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); lastUpdated.textContent = formattedTime; infoContainer.style.display = 'block'; } // 处理数据,提取公司名称和日期并规范化表头 function processData(rawData) { // 提取标题行信息(第一行数据) let title = rawData[0][0] || ''; // 提取公司名称 const companyMatch = title.match(/^(.*?)\s*\d{4}/); const company = companyMatch ? companyMatch[1] : '未知公司'; // 提取日期 const datePattern = /(\d{4})\D*(\d{1,2})\D*月/; const dateMatch = title.match(datePattern); currentDate = dateMatch ? dateMatch[0] : ''; // 更新公司名称和工资月份信息 companyName.textContent = company; salaryMonth.textContent = currentDate; // 提取特定行列数据 const extractedData = extractSpecificData(rawData); // 处理数值数据 const processedData = processNumericData(extractedData); // 保存处理后的数据用于导出 processedDataForExport = processedData; // 启用导出按钮 exportBtn.disabled = false; // 显示处理后的数据(每条数据带独立表头) displayData(processedData, currentDate); } // 提取特定行列数据 function extractSpecificData(rawData) { // 转换Excel列字母为索引(A=0, B=1, C=2...) const columnMap = { 'A': 0, 'B': 1, 'C': 2, 'O': 14, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'AA': 26, 'AB': 27, 'AC': 28, 'AE': 30, 'AG': 32, 'AH': 33 }; // 需要提取的列索引 const columnsToExtract = Object.values(columnMap); // 找到最后一行有数据的行索引 let lastDataRow = -1; for (let i = rawData.length - 1; i >= 0; i--) { const row = rawData[i]; if (row && row.some(cell => cell !== undefined && cell !== null && cell !== '')) { lastDataRow = i; break; } } // 计算行范围 const startRow = 5; // 第6行(0-based索引) const endRow = lastDataRow !== -1 ? lastDataRow - 6 : -1; // 验证行范围有效性 if (lastDataRow === -1) { console.warn('未找到有数据的行'); return []; } if (startRow >= endRow || endRow < 0) { console.warn(`数据行范围无效,开始行(${startRow+1}) >= 结束行(${endRow+1})`); showMessage(`数据行范围无效,开始行(${startRow+1}) >= 结束行(${endRow+1})`, true); return []; } // 提取数据到二维数组 const result = []; for (let i = startRow; i < endRow; i++) { const rowData = rawData[i] || []; const extractedRow = []; columnsToExtract.forEach(colIndex => { extractedRow.push(rowData[colIndex] !== undefined ? rowData[colIndex] : ''); }); result.push(extractedRow); } return result; } // 处理数值数据:校验并保留2位小数(四舍五入),空值不标红 function processNumericData(data) { return data.map(row => { return row.map((cell, index) => { // 检查当前列是否为数值列 if (numericColumns.includes(index)) { // 处理空值情况 if (cell === '' || cell === null || cell === undefined) { return { value: '', valid: true, // 空值视为有效 rawValue: 0 // 用于计算合计的原始数值 }; } // 尝试转换为数值 const num = parseFloat(cell); // 校验数值有效性 if (isNaN(num)) { return { value: cell, valid: false, rawValue: 0 }; } else { const roundedValue = Math.round(num * 100) / 100; return { value: roundedValue, valid: true, rawValue: roundedValue // 存储用于计算合计的数值 }; } } // 非数值列,直接返回原始值 return { value: cell, valid: true, rawValue: 0 }; }); }); } // 计算各列的总和 function calculateTotals(processedData) { const totals = new Array(expectedHeaders.length).fill(0); processedData.forEach(row => { row.forEach((cell, index) => { // 只对数值列进行求和 if (numericColumns.includes(index)) { totals[index] += cell.rawValue; } }); }); // 保留两位小数 return totals.map(total => Math.round(total * 100) / 100); } // 显示解析后的数据 - 每条数据前都添加表头,无间隔,最后添加合计行和签字行 function displayData(processedData, date) { // 清空表格 dataTableBody.innerHTML = ''; // 如果没有提取到数据,显示提示 if (processedData.length === 0) { const emptyRow = document.createElement('tr'); const emptyCell = document.createElement('td'); emptyCell.colSpan = expectedHeaders.length; emptyCell.textContent = '没有提取到符合条件的数据'; emptyRow.appendChild(emptyCell); dataTableBody.appendChild(emptyRow); } else { // 为每条数据添加表头(无间隔) processedData.forEach((row) => { // 添加表头行(每条数据前都添加) const headerRow = document.createElement('tr'); headerRow.className = 'salary-slip-header'; expectedHeaders.forEach((header) => { if (header === '应发工资' || header === '实发工资') { header += `(${date})`; } const th = document.createElement('th'); th.textContent = header; headerRow.appendChild(th); }); dataTableBody.appendChild(headerRow); // 添加数据行 const dataRow = document.createElement('tr'); row.forEach((cell, cellIndex) => { const td = document.createElement('td'); td.textContent = cell.value; // 数值列右对齐 if (numericColumns.includes(cellIndex)) { td.classList.add('numeric'); } // 标记无效数值 if (!cell.valid) { td.classList.add('invalid-value'); } dataRow.appendChild(td); }); dataTableBody.appendChild(dataRow); }); // 计算各列总和 const totals = calculateTotals(processedData); // 添加合计行(倒数第二行) const totalRow = document.createElement('tr'); totalRow.className = 'total-row'; // 前3个单元格合并,内容为"合计" const mergedCell = document.createElement('td'); mergedCell.colSpan = 3; mergedCell.textContent = '合计'; mergedCell.style.textAlign = 'center'; totalRow.appendChild(mergedCell); // 第4个单元格为空 const emptyCell = document.createElement('td'); totalRow.appendChild(emptyCell); // 5-17单元格为各列总和(索引4到16) for (let i = 4; i < expectedHeaders.length; i++) { const td = document.createElement('td'); td.textContent = totals[i]; if (i == expectedHeaders.length - 1) { td.textContent = ''; } if (numericColumns.includes(i)) { td.classList.add('numeric'); } totalRow.appendChild(td); } dataTableBody.appendChild(totalRow); // 添加签字行(最后一行) const signatureRow = document.createElement('tr'); signatureRow.className = 'signature-row'; // 制表 const makerCell = document.createElement('td'); makerCell.colSpan = 4; makerCell.textContent = '制表:'; signatureRow.appendChild(makerCell); // 财务 const financeCell = document.createElement('td'); financeCell.colSpan = 4; financeCell.textContent = '财务:'; signatureRow.appendChild(financeCell); // 审核 const auditCell = document.createElement('td'); auditCell.colSpan = 5; auditCell.textContent = '审核:'; signatureRow.appendChild(auditCell); // 审批 const approveCell = document.createElement('td'); approveCell.colSpan = 5; approveCell.textContent = '审批:'; signatureRow.appendChild(approveCell); dataTableBody.appendChild(signatureRow); } // 显示数据区域,隐藏消息 dataContainer.style.display = 'block'; message.style.display = 'none'; } // 导出数据到Excel function exportToExcel() { if (!processedDataForExport || processedDataForExport.length === 0) { showMessage('没有可导出的数据', true); return; } // 准备导出数据 const exportData = []; // 添加每条记录的表头和数据 processedDataForExport.forEach(row => { // 添加表头行 const headerRow = expectedHeaders.map(header => { if (header === '应发工资' || header === '实发工资') { return `${header}(${currentDate})`; } return header; }); exportData.push(headerRow); // 添加数据行 const dataRow = row.map(cell => cell.value); exportData.push(dataRow); }); // 计算合计 const totals = calculateTotals(processedDataForExport); // 准备合计行 const totalRow = []; // 前3个单元格合并为"合计" totalRow.push("合计"); totalRow.push(null); // 第二个单元格(合并后不需要) totalRow.push(null); // 第三个单元格(合并后不需要) totalRow.push(""); // 第四个单元格为空 // 添加5-17列的合计值 for (let i = 4; i < expectedHeaders.length - 1; i++) { totalRow.push(totals[i]); } exportData.push(totalRow); // 准备签字行 - 只在第一个单元格设置值,其他为空 const signatureRow = new Array(expectedHeaders.length).fill(null); signatureRow[0] = "制表: 财务: 审核: 审批:"; exportData.push(signatureRow); // 创建工作簿和工作表 const ws = XLSX.utils.aoa_to_sheet(exportData); // 设置单元格合并 if (exportData.length > 0) { const totalRowIndex = exportData.length - 2; // 合计行索引 const signatureRowIndex = exportData.length - 1; // 签字行索引 ws['!merges'] = [ // 合计行前3列合并 { s: { r: totalRowIndex, c: 0 }, e: { r: totalRowIndex, c: 2 } }, // 签字行A-R列合并为一个单元格 { s: { r: signatureRowIndex, c: 0 }, e: { r: signatureRowIndex, c: 17 } // 合并到第18列(R列) } ]; } // 设置固定行高 const range = XLSX.utils.decode_range(ws['!ref']); ws['!rows'] = []; // 初始化行配置数组 for (let R = 0; R <= range.e.r; R++) { // 为每一行设置固定高度20 ws['!rows'][R] = { hpt: 30 }; // hpt单位是1/20点,所以20像素需要乘以20 } // 使用自定义列宽,从第一列开始依次应用 const customWidths = [3.92, 4.8, 5, 4.05, 7.43, 7.68, 3.55, 4.93, 4.18, 12.05, 6.93, 6.93, 7.3, 6.93, 7.55, 5.68, 12.18, 9.43 ]; // 为每个表头应用对应的自定义宽度 ws['!cols'] = customWidths.map(width => ({ // 保留一位小数并转换为数字,确保宽度值正确 wch: parseFloat(width.toFixed(2)) })); // 定义包含自动换行的单元格样式 const cellStyle = { font: { sz: 10 // 小一号字体 }, alignment: { wrapText: true // 核心配置:启用自动换行 } }; // 应用样式到所有单元格 for (const cellAddress in ws) { // 跳过非单元格属性(如!cols、!rows等) if (cellAddress.startsWith('!')) continue; if (ws[cellAddress]) { // 合并已有样式与自动换行样式 ws[cellAddress].s = { ...ws[cellAddress].s, ...cellStyle }; } else { // 为空白单元格设置样式 ws[cellAddress] = { s: cellStyle }; } } // 创建工作簿并添加工作表 const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "工资条"); // 生成文件名(包含公司名称和日期) const company = companyName.textContent || "未知公司"; const fileName = `${company}_${currentDate || new Date().toLocaleDateString()}_工资条.xlsx`; // 导出文件 XLSX.writeFile(wb, fileName); } // 显示消息 function showMessage(text, isError = false) { message.textContent = text; message.className = isError ? 'message error' : 'message'; message.style.display = 'block'; dataContainer.style.display = 'none'; infoContainer.style.display = 'none'; } </script> </body> </html> 希望导出后有内容部分加边框,字体为宋体10号字,自动换行,对其方式上下左右居中,奇数行加粗
08-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值