Files
OnboardXFront/src/views/biz/onboarding/employee/index.vue
T
2026-08-27 11:09:33 +08:00

377 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="table-box">
<ProTable
ref="proTable"
title="正式员工档案"
:columns="columns"
:request-api="onboardingTaskApi.employeePage"
>
<template #operation="scope">
<el-button link type="primary" icon="UserFilled" @click="showDetail(scope.row)">
查看完整档案
</el-button>
</template>
</ProTable>
<!-- 员工完整档案抽屉 -->
<el-drawer
v-model="detailVisible"
:title="`员工档案详情 · ${detail?.employee?.employeeName || ''}`"
size="820px"
destroy-on-close
>
<div v-if="detail?.employee" class="employee-drawer-content">
<!-- 头部个人简要信息 -->
<div class="employee-header-card">
<el-avatar :size="64" class="employee-avatar">
{{ detail.employee.employeeName ? detail.employee.employeeName.substring(0, 1) : '员' }}
</el-avatar>
<div class="employee-header-info">
<div class="employee-name-row">
<span class="employee-name">{{ detail.employee.employeeName }}</span>
<el-tag size="small" :type="detail.employee.gender === '女' ? 'danger' : 'primary'" effect="plain">
{{ detail.employee.gender || '未知' }}
</el-tag>
<el-tag size="small" type="success">正式员工</el-tag>
</div>
<div class="employee-meta-row">
<span>员工编号:{{ detail.employee.employeeNo || '-' }}</span>
<span>联系电话:{{ detail.employee.phone }}</span>
<span>入职日期:{{ detail.employee.entryDate || '-' }}</span>
</div>
</div>
</div>
<el-tabs v-model="activeTab" class="employee-tabs">
<!-- 1. 基本人事档案 -->
<el-tab-pane label="基本人事档案" name="basic">
<el-card shadow="never" class="section-card">
<template #header>
<div class="card-header-title">身份与联系信息</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="姓名">{{ detail.employee.employeeName }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.employee.gender || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detail.employee.phone }}</el-descriptions-item>
<el-descriptions-item label="身份证号码">{{ detail.employee.idCardNumber }}</el-descriptions-item>
<el-descriptions-item label="出生日期">{{ detail.employee.birthDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="民族">{{ detail.employee.nation || '-' }}</el-descriptions-item>
<el-descriptions-item label="户口所在地" :span="2">
{{ detail.employee.registeredResidence || '-' }}
</el-descriptions-item>
<el-descriptions-item label="现居通讯地址" :span="2">
{{ detail.employee.contactAddress || '-' }}
</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header>
<div class="card-header-title">学历与就职信息</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="毕业院校">{{ detail.employee.college || '-' }}</el-descriptions-item>
<el-descriptions-item label="最高学历">{{ detail.employee.education || '-' }}</el-descriptions-item>
<el-descriptions-item label="所学专业">{{ detail.employee.major || '-' }}</el-descriptions-item>
<el-descriptions-item label="部门名称">{{ detail.employee.departmentName || '-' }}</el-descriptions-item>
<el-descriptions-item label="合同社保单位">{{ detail.employee.socialInsuranceCompany || '-' }}</el-descriptions-item>
<el-descriptions-item label="合同性质">{{ detail.employee.contractNature || '全职劳动合同' }}</el-descriptions-item>
<el-descriptions-item label="合同开始日期">{{ detail.employee.contractStartDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="合同终止日期">{{ detail.employee.contractEndDate || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="section-card mt-4">
<template #header>
<div class="card-header-title">工资卡账户信息</div>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="银行卡号">{{ detail.employee.bankCardNumber || '-' }}</el-descriptions-item>
<el-descriptions-item label="开户人姓名">{{ detail.employee.bankAccountName || detail.employee.employeeName }}</el-descriptions-item>
<el-descriptions-item label="工资卡开户行" :span="2">{{ detail.employee.bankName || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-tab-pane>
<!-- 2. 归档合同与协议 -->
<el-tab-pane label="归档合同协议" name="contracts">
<el-empty v-if="!detail.contracts?.length" description="暂无已归档的劳动合同" />
<div v-else class="contracts-list">
<div v-for="contract in detail.contracts" :key="contract.id" class="contract-box">
<div class="contract-info-left">
<div class="contract-title font-bold">{{ contract.contractName }}</div>
<div class="contract-meta-text text-secondary">
<span>状态:{{ contract.contractStatus }}</span>
<span v-if="contract.offlineSignedAt">· 线下签署于:{{ formatTime(contract.offlineSignedAt) }}</span>
</div>
</div>
<div class="contract-action-right">
<el-button
v-if="contract.generatedDocxFileId"
type="primary"
plain
size="small"
icon="Download"
@click="download(contract.generatedDocxFileId)"
>
下载合同 DOCX
</el-button>
<el-tag v-else type="info">尚未生成电子文件</el-tag>
</div>
</div>
</div>
</el-tab-pane>
<!-- 3. 入职流转与审核日志 -->
<el-tab-pane label="入职流转时间轴" name="audits">
<el-empty v-if="!detail.audits?.length" description="暂无流转记录" />
<div v-else class="timeline-box">
<el-timeline>
<el-timeline-item
v-for="item in detail.audits"
:key="item.id"
type="primary"
:timestamp="formatTime(item.auditedAt)"
placement="top"
>
<div class="audit-card">
<div class="audit-action-name font-bold">
{{ auditActionText(item.auditAction) }}
<span v-if="item.createUser" class="audit-operator">(操作人:{{ item.createUser }})</span>
</div>
<div v-if="item.auditOpinion" class="audit-opinion">
审核意见:{{ item.auditOpinion }}
</div>
</div>
</el-timeline-item>
</el-timeline>
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<el-button @click="detailVisible = false">关闭</el-button>
</template>
</el-drawer>
</div>
</template>
<script setup lang="ts" name="onboardingEmployee">
import { ref } from "vue";
import { UserFilled, Download } from "@element-plus/icons-vue";
import {
onboardingTaskApi,
type OnboardingEmployee
} from "@/api/modules/biz/onboarding";
import type { ColumnProps } from "@/components/ProTable/interface";
const detailVisible = ref(false);
const activeTab = ref("basic");
const detail = ref<any>();
const columns: ColumnProps<OnboardingEmployee>[] = [
{
prop: "employeeNo",
label: "员工编号",
minWidth: 160
},
{
prop: "employeeName",
label: "姓名",
minWidth: 140,
search: { el: "input" }
},
{
prop: "gender",
label: "性别",
minWidth: 80,
align: "center"
},
{
prop: "phone",
label: "手机号码",
minWidth: 160,
search: { el: "input" }
},
{
prop: "departmentName",
label: "所属部门",
minWidth: 140
},
{
prop: "entryDate",
label: "入职日期",
minWidth: 140
},
{
prop: "createTime",
label: "建档时间",
minWidth: 180,
render: scope => formatTime(scope.row.createTime)
},
{
prop: "operation",
label: "操作",
fixed: "right",
width: 160
}
];
const auditActionText = (action: string) => {
const map: Record<string, string> = {
submit: "候选人提交入职资料",
approve: "人事复核通过并转正建档",
reject: "人事退回补充资料",
archive: "任务归档",
revoke: "任务撤销"
};
return map[action] || action;
};
const formatTime = (timeStr?: string) => {
if (!timeStr) return "-";
return timeStr.replace("T", " ").substring(0, 19);
};
async function showDetail(row: OnboardingEmployee) {
const { data } = await onboardingTaskApi.employeeDetail({
id: row.id
});
detail.value = data;
activeTab.value = "basic";
detailVisible.value = true;
}
function download(fileId: number) {
window.open(`/api/sys/dev/file/download?id=${fileId}`, "_blank");
}
</script>
<style scoped>
:deep(.table-search) {
margin-bottom: 12px !important;
border-radius: 8px;
}
:deep(.table-main) {
border-radius: 8px;
}
.employee-drawer-content {
padding: 0 8px;
}
.employee-header-card {
display: flex;
align-items: center;
gap: 20px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.employee-avatar {
background: #2563eb;
color: #fff;
font-size: 24px;
font-weight: 700;
}
.employee-header-info {
display: flex;
flex-direction: column;
gap: 8px;
}
.employee-name-row {
display: flex;
align-items: center;
gap: 10px;
}
.employee-name {
font-size: 20px;
font-weight: 700;
color: #1e293b;
}
.employee-meta-row {
display: flex;
gap: 16px;
font-size: 13px;
color: #64748b;
}
.section-card {
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.card-header-title {
font-weight: 600;
font-size: 14px;
color: #1e293b;
}
.mt-4 {
margin-top: 16px;
}
.font-bold {
font-weight: 600;
}
.text-secondary {
color: #64748b;
}
/* 合同列表 */
.contracts-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.contract-box {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-radius: 8px;
border: 1px solid #e2e8f0;
background: #f8fafc;
}
.contract-meta-text {
font-size: 13px;
margin-top: 4px;
}
/* 时间轴 */
.timeline-box {
padding: 16px 8px;
}
.audit-card {
background: #f8fafc;
padding: 10px 14px;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.audit-operator {
font-size: 13px;
color: #64748b;
font-weight: normal;
}
.audit-opinion {
font-size: 13px;
color: #dc2626;
margin-top: 4px;
}
</style>