84 lines
3.8 KiB
TypeScript
84 lines
3.8 KiB
TypeScript
import type ExcelJS from 'exceljs'
|
|
import { SALARY_ROLES } from './performance-salary-roles'
|
|
import { getSalaryTemplate, isSalaryMonth, SALARY_TEMPLATE_BLANK_ROWS } from './performance-salary-templates'
|
|
|
|
const XLSX_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
|
export function salaryExportFileName(month: string) {
|
|
if (!isSalaryMonth(month)) throw new Error('请选择有效月份后重试。')
|
|
return `绩效薪资模板_${month}.xlsx`
|
|
}
|
|
|
|
export async function buildSalaryTemplateWorkbook(month: string) {
|
|
salaryExportFileName(month)
|
|
const { default: Excel } = await import('exceljs')
|
|
const workbook = new Excel.Workbook()
|
|
workbook.creator = '品牌中心'
|
|
const border: Partial<ExcelJS.Borders> = {
|
|
top: { style: 'thin', color: { argb: 'FFE2E8F0' } }, bottom: { style: 'thin', color: { argb: 'FFE2E8F0' } },
|
|
left: { style: 'thin', color: { argb: 'FFE2E8F0' } }, right: { style: 'thin', color: { argb: 'FFE2E8F0' } },
|
|
}
|
|
for (const role of SALARY_ROLES) {
|
|
const template = getSalaryTemplate(role.key, month)
|
|
const sheet = workbook.addWorksheet(role.title)
|
|
const size = template.columns.length
|
|
const headerRow = template.grouped ? 3 : 2
|
|
sheet.mergeCells(1, 1, 1, size)
|
|
const title = sheet.getCell(1, 1)
|
|
title.value = `${month} ${role.title}薪资明细`
|
|
title.font = { name: '微软雅黑', size: 14, bold: true, color: { argb: 'FF101828' } }
|
|
title.alignment = { vertical: 'middle', horizontal: 'left' }
|
|
sheet.getRow(1).height = 34
|
|
if (template.grouped) {
|
|
let start = 1
|
|
for (const group of template.groups) {
|
|
const end = start + group.columns.length - 1
|
|
if (end > start) sheet.mergeCells(2, start, 2, end)
|
|
sheet.getCell(2, start).value = group.label
|
|
start = end + 1
|
|
}
|
|
sheet.getRow(2).height = 28
|
|
}
|
|
template.columns.forEach((column, index) => {
|
|
sheet.getColumn(index + 1).width = Math.round(column.width / 7)
|
|
sheet.getCell(headerRow, index + 1).value = column.label
|
|
})
|
|
sheet.getRow(headerRow).height = 64
|
|
for (let row = 2; row <= headerRow + SALARY_TEMPLATE_BLANK_ROWS; row += 1) {
|
|
if (row > headerRow) sheet.getRow(row).height = 30
|
|
for (let col = 1; col <= size; col += 1) {
|
|
const cell = sheet.getCell(row, col)
|
|
cell.border = border
|
|
cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true }
|
|
cell.font = { name: '微软雅黑', size: 11, color: { argb: 'FF344054' }, bold: row <= headerRow }
|
|
if (row <= headerRow) cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: row < headerRow ? 'FFE6EDF5' : 'FFF8FAFC' } }
|
|
else {
|
|
const kind = template.columns[col - 1].kind
|
|
cell.numFmt = kind === 'money' ? '#,##0.00' : kind === 'percent' ? '0.00%' : kind === 'number' ? '0.##' : '@'
|
|
}
|
|
}
|
|
}
|
|
sheet.views = [{ state: 'frozen', xSplit: 1, ySplit: headerRow, topLeftCell: `B${headerRow + 1}` }]
|
|
sheet.pageSetup = { orientation: 'landscape', printTitlesRow: `1:${headerRow}` }
|
|
sheet.pageSetup.printArea = `A1:${sheet.getColumn(size).letter}${headerRow + SALARY_TEMPLATE_BLANK_ROWS}`
|
|
}
|
|
return workbook
|
|
}
|
|
|
|
export async function saveSalaryTemplateWorkbook(month: string) {
|
|
const workbook = await buildSalaryTemplateWorkbook(month)
|
|
const buffer = await workbook.xlsx.writeBuffer()
|
|
const blob = new Blob([buffer], { type: XLSX_MIME })
|
|
if (typeof URL.createObjectURL !== 'function') throw new Error('当前浏览器无法下载文件,请更换浏览器后重试。')
|
|
const url = URL.createObjectURL(blob)
|
|
const anchor = document.createElement('a')
|
|
anchor.href = url
|
|
anchor.download = salaryExportFileName(month)
|
|
anchor.style.display = 'none'
|
|
document.body.appendChild(anchor)
|
|
try { anchor.click() } finally {
|
|
anchor.remove()
|
|
window.setTimeout(() => URL.revokeObjectURL(url), 1000)
|
|
}
|
|
}
|