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

243 lines
7.9 KiB
Vue

<template>
<a-modal
:visible="visible"
@update:visible="val => emit('update:visible', val)"
:title="title"
:width="900"
:footer="true"
@cancel="handleCancel"
>
<a-descriptions :data="descriptionsData" bordered :column="2">
<a-descriptions-item label="员工姓名">{{ data?.employeeName }}</a-descriptions-item>
<a-descriptions-item label="员工类型">{{ employeeTypeText }}</a-descriptions-item>
<a-descriptions-item label="身份证号">{{ data?.idCard }}</a-descriptions-item>
<a-descriptions-item label="联系电话">{{ data?.phone }}</a-descriptions-item>
<a-descriptions-item label="银行卡号">{{ data?.bankCard }}</a-descriptions-item>
<a-descriptions-item label="开户银行">{{ data?.bankName }}</a-descriptions-item>
<a-descriptions-item label="项目名称">{{ data?.projectName }}</a-descriptions-item>
<a-descriptions-item label="项目周期">{{ data?.projectPeriod }}</a-descriptions-item>
<a-descriptions-item label="工作天数">{{ data?.workDays }}</a-descriptions-item>
<a-descriptions-item label="海上作业天数">{{ data?.offshoreDays }}</a-descriptions-item>
</a-descriptions>
<a-divider orientation="left">薪酬明细</a-divider>
<a-row :gutter="16">
<a-col :span="12">
<a-statistic
title="基本工资"
:value="data?.baseSalary"
:precision="2"
:value-style="{ color: '#3f8600' }"
/>
</a-col>
<a-col :span="12">
<a-statistic
title="绩效奖金"
:value="data?.performanceBonus"
:precision="2"
:value-style="{ color: '#3f8600' }"
/>
</a-col>
</a-row>
<a-row :gutter="16" style="margin-top: 16px">
<a-col :span="12">
<a-statistic
title="各类补助"
:value="data?.allowances"
:precision="2"
:value-style="{ color: '#3f8600' }"
/>
</a-col>
<a-col :span="12">
<a-statistic
title="应发总额"
:value="data?.totalPayable"
:precision="2"
:value-style="{ color: '#1890ff' }"
/>
</a-col>
</a-row>
<a-row :gutter="16" style="margin-top: 16px">
<a-col :span="12">
<a-statistic
title="扣款金额"
:value="data?.deductions"
:precision="2"
:value-style="{ color: '#ff4d4f' }"
/>
</a-col>
<a-col :span="12">
<a-statistic
title="实发金额"
:value="data?.netPay"
:precision="2"
:value-style="{ color: '#52c41a', fontSize: '24px', fontWeight: 'bold' }"
/>
</a-col>
</a-row>
<a-divider orientation="left">绩效评价</a-divider>
<a-table :data="data?.performanceItems" :columns="performanceColumns" :pagination="false">
<template #score="{ record }">
<a-tag :color="getScoreColor(record.score, record.maxScore)">
{{ record.score }}/{{ record.maxScore }}
</a-tag>
</template>
<template #amount="{ record }">
¥{{ (record.score / record.maxScore * record.amount).toFixed(2) }}
</template>
</a-table>
<a-divider orientation="left">补助明细</a-divider>
<a-table :data="data?.allowanceItems" :columns="allowanceColumns" :pagination="false">
<template #total="{ record }">
¥{{ record.total.toFixed(2) }}
</template>
</a-table>
<a-divider orientation="left">审批流程</a-divider>
<a-steps :current="approvalCurrent" direction="vertical">
<a-step
v-for="step in data?.approvalFlow"
:key="step.step"
:title="step.step"
:description="`${step.role} - ${step.approver}`"
>
<template #icon>
<icon-check-circle v-if="step.status === 'approved'" style="color: #52c41a" />
<icon-close-circle v-else-if="step.status === 'rejected'" style="color: #ff4d4f" />
<icon-clock-circle v-else style="color: #faad14" />
</template>
</a-step>
</a-steps>
<template #footer>
<a-space>
<a-button @click="handleCancel">关闭</a-button>
<a-button type="primary" @click="handleExport">
<template #icon><icon-download /></template>
导出Excel
</a-button>
</a-space>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { SalaryRecord } from '../types'
interface Props {
visible: boolean
data: SalaryRecord | null
}
interface Emits {
(e: 'update:visible', visible: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const title = computed(() => `${props.data?.employeeName} - 工资详情`)
const employeeTypeText = computed(() => {
const typeMap = {
intern: '实习生',
fulltime: '正式员工',
parttime: '兼职员工',
}
return props.data?.employeeType ? typeMap[props.data.employeeType] : ''
})
const approvalCurrent = computed(() => {
if (!props.data?.approvalFlow) return 0
const approvedSteps = props.data.approvalFlow.filter((step) => step.status === 'approved').length
return approvedSteps
})
const descriptionsData = computed(() => [
{ label: '员工姓名', value: props.data?.employeeName },
{ label: '员工类型', value: employeeTypeText.value },
{ label: '身份证号', value: props.data?.idCard },
{ label: '联系电话', value: props.data?.phone },
{ label: '银行卡号', value: props.data?.bankCard },
{ label: '开户银行', value: props.data?.bankName },
{ label: '项目名称', value: props.data?.projectName },
{ label: '项目周期', value: props.data?.projectPeriod },
{ label: '工作天数', value: `${props.data?.workDays}` },
{ label: '海上作业天数', value: `${props.data?.offshoreDays}` },
])
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: 'quantity', width: 100 },
{ title: '小计', dataIndex: 'total', slotName: 'total', width: 100 },
]
const getScoreColor = (score: number, maxScore: number) => {
const ratio = score / maxScore
if (ratio >= 0.9) return 'green'
if (ratio >= 0.7) return 'blue'
if (ratio >= 0.6) return 'orange'
return 'red'
}
const handleCancel = () => {
emit('update:visible', false)
}
const handleExport = () => {
if (!props.data) return
// 构建导出数据
const exportData = {
employee: {
name: props.data.employeeName,
type: employeeTypeText.value,
idCard: props.data.idCard,
phone: props.data.phone,
bankCard: props.data.bankCard,
bankName: props.data.bankName,
},
project: {
name: props.data.projectName,
period: props.data.projectPeriod,
workDays: props.data.workDays,
offshoreDays: props.data.offshoreDays,
},
salary: {
baseSalary: props.data.baseSalary,
performanceBonus: props.data.performanceBonus,
allowances: props.data.allowances,
totalPayable: props.data.totalPayable,
deductions: props.data.deductions,
netPay: props.data.netPay,
},
performanceItems: props.data.performanceItems,
allowanceItems: props.data.allowanceItems,
}
// 触发下载
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${props.data.employeeName}_工资单_${props.data.projectPeriod}.json`
a.click()
URL.revokeObjectURL(url)
}
</script>