From 253b6ffcca8e806c02889257dfec6941bf7fc0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=BE=B7=E8=B6=85?= <13143889+he-dechao@user.noreply.gitee.com> Date: Mon, 21 Jul 2025 10:49:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E8=B5=84=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/SalaryDetailModal.vue | 242 +++++++++ .../components/SalaryFormDrawer.vue | 468 ++++++++++++++++++ src/views/salary-management/types.ts | 127 +++++ src/views/salary-management/utils/export.ts | 255 ++++++++++ 4 files changed, 1092 insertions(+) create mode 100644 src/views/salary-management/components/SalaryDetailModal.vue create mode 100644 src/views/salary-management/components/SalaryFormDrawer.vue create mode 100644 src/views/salary-management/types.ts create mode 100644 src/views/salary-management/utils/export.ts diff --git a/src/views/salary-management/components/SalaryDetailModal.vue b/src/views/salary-management/components/SalaryDetailModal.vue new file mode 100644 index 0000000..b4b910b --- /dev/null +++ b/src/views/salary-management/components/SalaryDetailModal.vue @@ -0,0 +1,242 @@ + + + diff --git a/src/views/salary-management/components/SalaryFormDrawer.vue b/src/views/salary-management/components/SalaryFormDrawer.vue new file mode 100644 index 0000000..71b998b --- /dev/null +++ b/src/views/salary-management/components/SalaryFormDrawer.vue @@ -0,0 +1,468 @@ + + + diff --git a/src/views/salary-management/types.ts b/src/views/salary-management/types.ts new file mode 100644 index 0000000..93df2af --- /dev/null +++ b/src/views/salary-management/types.ts @@ -0,0 +1,127 @@ +// 员工类型 +export type EmployeeType = 'intern' | 'fulltime' | 'parttime' + +// 工资单状态 +export type SalaryStatus = 'draft' | 'pending' | 'approved' | 'rejected' | 'paid' + +// 补助类型 +export interface AllowanceItem { + type: string + name: string + amount: number + unit: 'day' | 'project' | 'fixed' + quantity: number + total: number +} + +// 绩效评价项 +export interface PerformanceItem { + id: string + name: string + weight: number + score: number + maxScore: number + amount: number + description?: string +} + +// 工资记录 +export interface SalaryRecord { + id: string + employeeId: string + employeeName: string + employeeType: EmployeeType + idCard: string + phone: string + bankCard: string + bankName: string + + // 项目信息 + projectName: string + projectPeriod: string + workDays: number + offshoreDays: number + + // 薪酬结构 + baseSalary: number + baseSalaryStandard: number + performanceBonus: number + allowances: number + performanceItems: PerformanceItem[] + allowanceItems: AllowanceItem[] + + // 计算结果 + totalPayable: number + deductions: number + netPay: number + + // 状态 + status: SalaryStatus + approvalFlow: ApprovalStep[] + + // 时间 + salaryMonth: string + createdAt: string + updatedAt: string +} + +// 审批步骤 +export interface ApprovalStep { + step: string + role: string + approver: string + status: 'pending' | 'approved' | 'rejected' + comment?: string + date?: string +} + +// 查询参数 +export interface SalaryQuery { + keyword?: string + employeeType?: EmployeeType + status?: SalaryStatus + dateRange?: string[] + projectName?: string +} + +// 实习生专用配置 +export interface InternConfig { + baseSalaryStandard: { + withCertification: number + withoutCertification: number + } + performanceRates: { + construction: number + offshore: number + } + allowances: { + offshoreCert: number + towerWork: { + land: { + bladeInspection: number + lightning: number + } + sea: { + bladeInspection: number + lightning: number + } + } + droneWork: number + meal: number + } +} + +// 工资单创建请求 +export interface SalaryCreateRequest { + employeeId: string + projectId: string + workDays: number + offshoreDays: number + performanceItems: Omit[] + allowanceItems: Omit[] + deductions?: { + type: string + amount: number + reason: string + }[] +} diff --git a/src/views/salary-management/utils/export.ts b/src/views/salary-management/utils/export.ts new file mode 100644 index 0000000..4b5c455 --- /dev/null +++ b/src/views/salary-management/utils/export.ts @@ -0,0 +1,255 @@ +import * as XLSX from 'xlsx' +import type { SalaryRecord } from '../types' + +// 导出工资单到Excel +export const exportSalaryToExcel = (records: SalaryRecord[], filename: string) => { + // 创建工作簿 + const workbook = XLSX.utils.book_new() + + // 主表数据 + const mainData = records.map((record) => ({ + 员工姓名: record.employeeName, + 员工类型: getEmployeeTypeText(record.employeeType), + 身份证号: record.idCard, + 联系电话: record.phone, + 银行卡号: record.bankCard, + 开户银行: record.bankName, + 项目名称: record.projectName, + 项目周期: record.projectPeriod, + 工作天数: record.workDays, + 海上作业天数: record.offshoreDays, + 基本工资: record.baseSalary, + 绩效奖金: record.performanceBonus, + 各类补助: record.allowances, + 应发总额: record.totalPayable, + 扣款金额: record.deductions, + 实发金额: record.netPay, + 状态: getStatusText(record.status), + 创建时间: record.createdAt, + })) + + // 创建主表 + const mainSheet = XLSX.utils.json_to_sheet(mainData) + XLSX.utils.book_append_sheet(workbook, mainSheet, '工资汇总') + + // 绩效明细表 + const performanceData: any[] = [] + records.forEach((record) => { + record.performanceItems.forEach((item) => { + performanceData.push({ + 员工姓名: record.employeeName, + 项目名称: record.projectName, + 评价项: item.name, + 权重: item.weight, + 得分: item.score, + 满分: item.maxScore, + 金额: (item.score / item.maxScore * item.amount).toFixed(2), + }) + }) + }) + + if (performanceData.length > 0) { + const performanceSheet = XLSX.utils.json_to_sheet(performanceData) + XLSX.utils.book_append_sheet(workbook, performanceSheet, '绩效明细') + } + + // 补助明细表 + const allowanceData: any[] = [] + records.forEach((record) => { + record.allowanceItems.forEach((item) => { + allowanceData.push({ + 员工姓名: record.employeeName, + 项目名称: record.projectName, + 补助类型: item.name, + 标准: item.amount, + 单位: getUnitText(item.unit), + 数量: item.quantity, + 小计: item.total, + }) + }) + }) + + if (allowanceData.length > 0) { + const allowanceSheet = XLSX.utils.json_to_sheet(allowanceData) + XLSX.utils.book_append_sheet(workbook, allowanceSheet, '补助明细') + } + + // 导出文件 + XLSX.writeFile(workbook, filename) +} + +// 导出单个工资单详情 +export const exportSingleSalaryToExcel = (record: SalaryRecord) => { + const workbook = XLSX.utils.book_new() + + // 基本信息 + const basicInfo = [{ + 项目: '员工姓名', + 值: record.employeeName, + }, { + 项目: '员工类型', + 值: getEmployeeTypeText(record.employeeType), + }, { + 项目: '身份证号', + 值: record.idCard, + }, { + 项目: '联系电话', + 值: record.phone, + }, { + 项目: '银行卡号', + 值: record.bankCard, + }, { + 项目: '开户银行', + 值: record.bankName, + }, { + 项目: '项目名称', + 值: record.projectName, + }, { + 项目: '项目周期', + 值: record.projectPeriod, + }, { + 项目: '工作天数', + 值: record.workDays, + }, { + 项目: '海上作业天数', + 值: record.offshoreDays, + }] + + const basicSheet = XLSX.utils.json_to_sheet(basicInfo) + XLSX.utils.book_append_sheet(workbook, basicSheet, '基本信息') + + // 薪酬汇总 + const salarySummary = [{ + 项目: '基本工资', + 金额: record.baseSalary, + }, { + 项目: '绩效奖金', + 金额: record.performanceBonus, + }, { + 项目: '各类补助', + 金额: record.allowances, + }, { + 项目: '应发总额', + 金额: record.totalPayable, + }, { + 项目: '扣款金额', + 金额: record.deductions, + }, { + 项目: '实发金额', + 金额: record.netPay, + }] + + const salarySheet = XLSX.utils.json_to_sheet(salarySummary) + XLSX.utils.book_append_sheet(workbook, salarySheet, '薪酬汇总') + + // 绩效明细 + if (record.performanceItems.length > 0) { + const performanceSheet = XLSX.utils.json_to_sheet(record.performanceItems.map((item) => ({ + 评价项: item.name, + 权重: item.weight, + 得分: item.score, + 满分: item.maxScore, + 金额: (item.score / item.maxScore * item.amount).toFixed(2), + }))) + XLSX.utils.book_append_sheet(workbook, performanceSheet, '绩效明细') + } + + // 补助明细 + if (record.allowanceItems.length > 0) { + const allowanceSheet = XLSX.utils.json_to_sheet(record.allowanceItems.map((item) => ({ + 补助类型: item.name, + 标准: item.amount, + 单位: getUnitText(item.unit), + 数量: item.quantity, + 小计: item.total, + }))) + XLSX.utils.book_append_sheet(workbook, allowanceSheet, '补助明细') + } + + // 导出文件 + const filename = `${record.employeeName}_工资单_${record.projectPeriod}.xlsx` + XLSX.writeFile(workbook, filename) +} + +// 导出实习报酬申领表格式 +export const exportInternCompensationForm = (record: SalaryRecord) => { + const workbook = XLSX.utils.book_new() + + // 实习生基本信息 + const basicInfo = [ + ['实习生基本信息', '', '', '', ''], + ['类别', '填写内容', '', '备注', ''], + ['姓名', record.employeeName, '', '', ''], + ['身份证号', record.idCard, '', '', ''], + ['联系电话', record.phone, '', '', ''], + ['银行卡号', record.bankCard, '', '', ''], + ['开户银行', record.bankName, '', '', ''], + ['', '', '', '', ''], + ['实习项目及项目实习相关信息', '', '', '', ''], + ['序号', '项目名称', '在项目时间', '出外业施工作业时间(天)', '备注(时间段)'], + ['1', record.projectName, record.workDays, record.workDays - record.offshoreDays, record.projectPeriod], + ['', '', '', '', ''], + ['薪酬、绩效、补助(一)', '', '', '', ''], + ['项目类别', '计算标准(元/天)或(元/台)', '计酬天数统计(日)', '应发金额', '备注'], + ['基本工资', record.baseSalaryStandard, record.workDays, record.baseSalary, ''], + ['施工绩效', 100, record.workDays - record.offshoreDays, record.performanceBonus, ''], + ['海上作业绩效补助', 30, record.offshoreDays, record.offshoreDays * 30, ''], + ['务餐补助', 45, record.workDays, record.allowances, ''], + ['合计应发金额', '', '', record.totalPayable, ''], + ] + + const sheet = XLSX.utils.aoa_to_sheet(basicInfo) + + // 设置列宽 + const cols = [ + { wch: 20 }, + { wch: 20 }, + { wch: 15 }, + { wch: 25 }, + { wch: 20 }, + ] + sheet['!cols'] = cols + + // 合并单元格 + sheet['!merges'] = [ + { s: { r: 0, c: 0 }, e: { r: 0, c: 4 } }, + { s: { r: 8, c: 0 }, e: { r: 8, c: 4 } }, + { s: { r: 12, c: 0 }, e: { r: 12, c: 4 } }, + ] + + XLSX.utils.book_append_sheet(workbook, sheet, '实习报酬申领表') + + // 导出文件 + const filename = `${record.employeeName}_实习报酬申领表_${record.projectPeriod}.xlsx` + XLSX.writeFile(workbook, filename) +} + +// 辅助函数 +function getEmployeeTypeText(type: string) { + const typeMap = { + intern: '实习生', + fulltime: '正式员工', + parttime: '兼职员工', + } + return typeMap[type as keyof typeof typeMap] || type +} + +function getStatusText(status: string) { + const statusMap = { + draft: '草稿', + pending: '待审批', + approved: '已批准', + rejected: '已拒绝', + paid: '已发放', + } + return statusMap[status as keyof typeof statusMap] || status +} + +function getUnitText(unit: string) { + const unitMap = { + day: '天', + project: '项目', + fixed: '固定', + } + return unitMap[unit as keyof typeof unitMap] || unit +}