Industrial-image-management.../src/views/salary-management/components/SalaryFormDrawer.vue

469 lines
13 KiB
Vue
Raw Normal View History

2025-07-21 10:49:05 +08:00
<template>
<a-drawer
:visible="visible"
@update:visible="val => emit('update:visible', val)"
:title="title"
:width="800"
:footer="true"
@cancel="handleCancel"
>
<a-form ref="formRef" :model="formData" :rules="rules" layout="vertical">
<!-- 基本信息 -->
<a-card title="基本信息" :bordered="false">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="员工姓名" field="employeeName">
<a-input v-model="formData.employeeName" placeholder="请输入员工姓名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="员工类型" field="employeeType">
<a-select v-model="formData.employeeType" placeholder="请选择员工类型">
<a-option value="intern">实习生</a-option>
<a-option value="fulltime">正式员工</a-option>
<a-option value="parttime">兼职员工</a-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="身份证号" field="idCard">
<a-input v-model="formData.idCard" placeholder="请输入身份证号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系电话" field="phone">
<a-input v-model="formData.phone" placeholder="请输入联系电话" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="银行卡号" field="bankCard">
<a-input v-model="formData.bankCard" placeholder="请输入银行卡号" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="开户银行" field="bankName">
<a-input v-model="formData.bankName" placeholder="请输入开户银行" />
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 项目信息 -->
<a-card title="项目信息" :bordered="false" style="margin-top: 16px">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="项目名称" field="projectName">
<a-input v-model="formData.projectName" placeholder="请输入项目名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="项目周期" field="projectPeriod">
<a-input v-model="formData.projectPeriod" placeholder="如2025-07" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="工作天数" field="workDays">
<a-input-number
v-model="formData.workDays"
:min="0"
:precision="0"
placeholder="请输入工作天数"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="海上作业天数" field="offshoreDays">
<a-input-number
v-model="formData.offshoreDays"
:min="0"
:precision="0"
placeholder="请输入海上作业天数"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
<!-- 薪酬计算 -->
<a-card title="薪酬计算" :bordered="false" style="margin-top: 16px">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="基本工资标准" field="baseSalaryStandard">
<a-input-number
v-model="formData.baseSalaryStandard"
:min="0"
:precision="2"
placeholder="请输入基本工资标准"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="基本工资" field="baseSalary">
<a-input-number
v-model="formData.baseSalary"
:min="0"
:precision="2"
placeholder="自动计算"
style="width: 100%"
readonly
/>
</a-form-item>
</a-col>
</a-row>
<!-- 绩效评价 -->
<a-divider orientation="left">绩效评价</a-divider>
<a-table :data="formData.performanceItems" :columns="performanceColumns" :pagination="false">
<template #score="{ record, rowIndex }">
<a-input-number
v-model="record.score"
:min="0"
:max="record.maxScore"
:precision="0"
@change="calculatePerformance"
/>
</template>
<template #amount="{ record }">
{{ record.amount }}
</template>
</a-table>
<!-- 补助明细 -->
<a-divider orientation="left">补助明细</a-divider>
<a-table :data="formData.allowanceItems" :columns="allowanceColumns" :pagination="false">
<template #quantity="{ record, rowIndex }">
<a-input-number
v-model="record.quantity"
:min="0"
:precision="0"
@change="calculateAllowances"
/>
</template>
<template #total="{ record }">
{{ record.total }}
</template>
</a-table>
<!-- 计算结果 -->
<a-divider orientation="left">计算结果</a-divider>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="应发总额">
<a-input-number
v-model="formData.totalPayable"
:min="0"
:precision="2"
readonly
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="扣款金额">
<a-input-number
v-model="formData.deductions"
:min="0"
:precision="2"
placeholder="请输入扣款金额"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="实发金额">
<a-input-number
v-model="formData.netPay"
:min="0"
:precision="2"
readonly
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
</a-card>
</a-form>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSubmit">保存</a-button>
</a-space>
</template>
</a-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import type { SalaryRecord, AllowanceItem, PerformanceItem } from '../types'
interface Props {
visible: boolean
data: SalaryRecord | null
mode: 'add' | 'edit'
}
interface Emits {
(e: 'update:visible', visible: boolean): void
(e: 'success'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const formRef = ref()
const formData = reactive<SalaryRecord>({
id: '',
employeeId: '',
employeeName: '',
employeeType: 'intern',
idCard: '',
phone: '',
bankCard: '',
bankName: '',
projectName: '',
projectPeriod: '',
workDays: 0,
offshoreDays: 0,
baseSalary: 0,
baseSalaryStandard: 3500,
performanceBonus: 0,
allowances: 0,
performanceItems: [],
allowanceItems: [],
totalPayable: 0,
deductions: 0,
netPay: 0,
status: 'draft',
approvalFlow: [],
salaryMonth: '',
createdAt: '',
updatedAt: '',
})
const performanceColumns = [
{ title: '评价项', dataIndex: 'name', width: 200 },
{ title: '权重', dataIndex: 'weight', width: 80 },
{ title: '得分', dataIndex: 'score', slotName: 'score', width: 100 },
{ title: '满分', dataIndex: 'maxScore', width: 80 },
{ title: '金额', dataIndex: 'amount', slotName: 'amount', width: 100 },
]
const allowanceColumns = [
{ title: '补助类型', dataIndex: 'name', width: 200 },
{ title: '标准', dataIndex: 'amount', width: 100 },
{ title: '单位', dataIndex: 'unit', width: 80 },
{ title: '数量', dataIndex: 'quantity', slotName: 'quantity', width: 100 },
{ title: '小计', dataIndex: 'total', slotName: 'total', width: 100 },
]
const title = computed(() => (props.mode === 'add' ? '新建工资单' : '编辑工资单'))
const rules = {
employeeName: [{ required: true, message: '请输入员工姓名' }],
employeeType: [{ required: true, message: '请选择员工类型' }],
idCard: [{ required: true, message: '请输入身份证号' }],
phone: [{ required: true, message: '请输入联系电话' }],
projectName: [{ required: true, message: '请输入项目名称' }],
workDays: [{ required: true, message: '请输入工作天数' }],
}
// 初始化实习生默认配置
const initInternConfig = () => {
formData.performanceItems = [
{
id: '1',
name: '平时沟通态度',
weight: 30,
score: 0,
maxScore: 100,
amount: 200,
},
{
id: '2',
name: '积极主动性',
weight: 50,
score: 0,
maxScore: 100,
amount: 200,
},
{
id: '3',
name: '成长性心态',
weight: 20,
score: 0,
maxScore: 100,
amount: 100,
},
]
formData.allowanceItems = [
{
type: 'construction',
name: '施工绩效',
amount: 100,
unit: 'day',
quantity: 0,
total: 0,
},
{
type: 'offshore',
name: '海上作业补助',
amount: 30,
unit: 'day',
quantity: 0,
total: 0,
},
{
type: 'meal',
name: '务餐补助',
amount: 45,
unit: 'day',
quantity: 0,
total: 0,
},
]
}
// 计算基本工资
const calculateBaseSalary = () => {
formData.baseSalary = (formData.baseSalaryStandard / 30) * formData.workDays
calculateTotal()
}
// 计算绩效奖金
const calculatePerformance = () => {
formData.performanceBonus = formData.performanceItems.reduce(
(sum, item) => sum + (item.score / item.maxScore) * item.amount,
0,
)
calculateTotal()
}
// 计算补助
const calculateAllowances = () => {
formData.allowanceItems.forEach((item) => {
item.total = item.amount * item.quantity
})
formData.allowances = formData.allowanceItems.reduce((sum, item) => sum + item.total, 0)
calculateTotal()
}
// 计算总额
const calculateTotal = () => {
formData.totalPayable = formData.baseSalary + formData.performanceBonus + formData.allowances
formData.netPay = formData.totalPayable - formData.deductions
}
// 监听工作天数变化
watch(
() => formData.workDays,
() => {
calculateBaseSalary()
// 更新务餐补助数量
const mealItem = formData.allowanceItems.find((item) => item.type === 'meal')
if (mealItem) {
mealItem.quantity = formData.workDays
calculateAllowances()
}
},
)
// 监听海上作业天数变化
watch(
() => formData.offshoreDays,
() => {
// 更新海上作业补助数量
const offshoreItem = formData.allowanceItems.find((item) => item.type === 'offshore')
if (offshoreItem) {
offshoreItem.quantity = formData.offshoreDays
calculateAllowances()
}
},
)
// 监听扣款变化
watch(() => formData.deductions, calculateTotal)
const handleCancel = () => {
emit('update:visible', false)
formRef.value?.resetFields()
}
const handleSubmit = async () => {
try {
await formRef.value?.validate()
// 这里调用API保存数据
Message.success('保存成功')
emit('success')
} catch (error) {
console.error('表单验证失败:', error)
}
}
// 监听数据变化
watch(
() => props.data,
(newData) => {
if (newData) {
Object.assign(formData, newData)
} else {
// 重置表单
Object.assign(formData, {
id: '',
employeeId: '',
employeeName: '',
employeeType: 'intern',
idCard: '',
phone: '',
bankCard: '',
bankName: '',
projectName: '',
projectPeriod: '',
workDays: 0,
offshoreDays: 0,
baseSalary: 0,
baseSalaryStandard: 3500,
performanceBonus: 0,
allowances: 0,
performanceItems: [],
allowanceItems: [],
totalPayable: 0,
deductions: 0,
netPay: 0,
status: 'draft',
approvalFlow: [],
salaryMonth: '',
createdAt: '',
updatedAt: '',
})
initInternConfig()
}
},
{ immediate: true },
)
// 监听员工类型变化
watch(
() => formData.employeeType,
(newType) => {
if (newType === 'intern') {
initInternConfig()
} else {
// 其他员工类型的配置
formData.performanceItems = []
formData.allowanceItems = []
}
},
)
</script>