前端第一版

This commit is contained in:
fuenmao
2026-08-24 16:36:28 +08:00
parent 3c52039b5b
commit c6509a3545
11 changed files with 5413 additions and 1 deletions
+371
View File
@@ -0,0 +1,371 @@
<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: "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>