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>
|