469 lines
13 KiB
Vue
469 lines
13 KiB
Vue
<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>
|