93 lines
4.6 KiB
TypeScript
93 lines
4.6 KiB
TypeScript
import type ExcelJS from 'exceljs'
|
|
import { SALARY_ROLES } from './performance-salary-roles'
|
|
import { getSalaryTemplate, getPositionSalaryTemplate, isSalaryMonth, SALARY_TEMPLATE_BLANK_ROWS } from './performance-salary-templates'
|
|
import type { SalaryPosition } from './salary-position-contract'
|
|
|
|
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, positions?: SalaryPosition[]) {
|
|
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' } },
|
|
}
|
|
const templates = SALARY_ROLES.flatMap(role => {
|
|
const configured = positions?.filter(position => position.salaryRole === role.key) ?? []
|
|
return configured.length ? configured.map(position => getPositionSalaryTemplate(position, month)) : [getSalaryTemplate(role.key, month)]
|
|
})
|
|
const used = new Set<string>()
|
|
for (const template of templates) {
|
|
const baseName = template.title.replace(/[\\/*?:\[\]]/g, ' ').replace(/^'+|'+$/g, '').trim().slice(0, 26) || '岗位'
|
|
let sheetName = baseName, counter = 1
|
|
while (used.has(sheetName.toLocaleLowerCase())) sheetName = `${baseName}_${++counter}`
|
|
used.add(sheetName.toLocaleLowerCase())
|
|
const sheet = workbook.addWorksheet(sheetName)
|
|
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} ${template.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' : template.columns[col - 1].key.endsWith('Score') ? '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, positions?: SalaryPosition[]) {
|
|
const workbook = await buildSalaryTemplateWorkbook(month, positions)
|
|
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)
|
|
}
|
|
}
|