601 lines
15 KiB
Vue
601 lines
15 KiB
Vue
|
<template>
|
||
|
<GiPageLayout>
|
||
|
<div class="certification-manage-container">
|
||
|
<div class="page-header">
|
||
|
<h2 class="page-title">人员资质管理</h2>
|
||
|
<a-button type="primary" @click="showAddModal">
|
||
|
<template #icon>
|
||
|
<icon-plus />
|
||
|
</template>
|
||
|
新增资质
|
||
|
</a-button>
|
||
|
</div>
|
||
|
|
||
|
<!-- 搜索表单 -->
|
||
|
<a-card class="search-card" :bordered="false">
|
||
|
<a-form :model="searchForm" layout="inline">
|
||
|
<a-form-item label="证书名称" field="certificationName">
|
||
|
<a-input
|
||
|
v-model="searchForm.certificationName"
|
||
|
placeholder="请输入证书名称"
|
||
|
allow-clear
|
||
|
style="width: 200px"
|
||
|
/>
|
||
|
</a-form-item>
|
||
|
|
||
|
<a-form-item label="证书类型" field="certificationType">
|
||
|
<a-input
|
||
|
v-model="searchForm.certificationType"
|
||
|
placeholder="请输入证书类型"
|
||
|
allow-clear
|
||
|
style="width: 200px"
|
||
|
/>
|
||
|
</a-form-item>
|
||
|
|
||
|
<a-form-item label="用户姓名" field="userName">
|
||
|
<a-input
|
||
|
v-model="searchForm.userName"
|
||
|
placeholder="请输入用户姓名"
|
||
|
allow-clear
|
||
|
style="width: 200px"
|
||
|
/>
|
||
|
</a-form-item>
|
||
|
|
||
|
<a-form-item>
|
||
|
<a-space>
|
||
|
<a-button type="primary" @click="handleSearch">
|
||
|
<template #icon>
|
||
|
<icon-search />
|
||
|
</template>
|
||
|
搜索
|
||
|
</a-button>
|
||
|
<a-button @click="handleReset">
|
||
|
<template #icon>
|
||
|
<icon-refresh />
|
||
|
</template>
|
||
|
重置
|
||
|
</a-button>
|
||
|
</a-space>
|
||
|
</a-form-item>
|
||
|
</a-form>
|
||
|
</a-card>
|
||
|
|
||
|
<!-- 人员资质表格 -->
|
||
|
<a-card class="table-card" :bordered="false">
|
||
|
<a-table
|
||
|
:columns="columns"
|
||
|
:data="certificationList"
|
||
|
:pagination="paginationConfig"
|
||
|
:loading="loading"
|
||
|
row-key="certificationId"
|
||
|
@page-change="handlePageChange"
|
||
|
>
|
||
|
<template #userName="{ record }">
|
||
|
<span>{{ getUserName(record.userId) }}</span>
|
||
|
</template>
|
||
|
|
||
|
<template #certificationImage="{ record }">
|
||
|
<a-image
|
||
|
v-if="record.certificationImage"
|
||
|
:src="record.certificationImage"
|
||
|
width="60"
|
||
|
height="40"
|
||
|
fit="cover"
|
||
|
show-loader
|
||
|
preview
|
||
|
/>
|
||
|
<span v-else>-</span>
|
||
|
</template>
|
||
|
|
||
|
<template #validityPeriod="{ record }">
|
||
|
<span>{{ record.validityDateBegin }} 至 {{ record.validityDateEnd }}</span>
|
||
|
</template>
|
||
|
|
||
|
<template #actions="{ record }">
|
||
|
<a-space>
|
||
|
<a-button type="primary" size="small" @click="editRecord(record)">
|
||
|
编辑
|
||
|
</a-button>
|
||
|
<a-button type="primary" status="danger" size="small" @click="deleteRecord(record)">
|
||
|
删除
|
||
|
</a-button>
|
||
|
</a-space>
|
||
|
</template>
|
||
|
</a-table>
|
||
|
</a-card>
|
||
|
|
||
|
<!-- 新增/编辑资质信息模态框 -->
|
||
|
<a-modal
|
||
|
v-model:visible="modalVisible"
|
||
|
:title="isEdit ? '编辑资质信息' : '新增资质信息'"
|
||
|
width="700px"
|
||
|
@ok="handleSubmit"
|
||
|
@cancel="handleCancel"
|
||
|
>
|
||
|
<a-form
|
||
|
ref="formRef"
|
||
|
:model="formData"
|
||
|
:rules="formRules"
|
||
|
layout="vertical"
|
||
|
>
|
||
|
<a-row :gutter="16">
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="证书编号" field="certificationCode">
|
||
|
<a-input v-model="formData.certificationCode" placeholder="请输入证书编号" />
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="证书名称" field="certificationName">
|
||
|
<a-input v-model="formData.certificationName" placeholder="请输入证书名称" />
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
</a-row>
|
||
|
|
||
|
<a-row :gutter="16">
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="证书类型" field="certificationType">
|
||
|
<a-input v-model="formData.certificationType" placeholder="请输入证书类型" />
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="持证人" field="userId">
|
||
|
<a-select
|
||
|
v-model="formData.userId"
|
||
|
placeholder="请选择持证人"
|
||
|
:loading="loadingUsers"
|
||
|
allow-search
|
||
|
:filter-option="filterUserOption"
|
||
|
>
|
||
|
<a-option
|
||
|
v-for="user in userList"
|
||
|
:key="user.userId"
|
||
|
:value="user.userId"
|
||
|
:label="user.name"
|
||
|
>
|
||
|
{{ user.name }}({{ user.account }})
|
||
|
</a-option>
|
||
|
</a-select>
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
</a-row>
|
||
|
|
||
|
<a-row :gutter="16">
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="有效期开始" field="validityDateBegin">
|
||
|
<a-date-picker
|
||
|
v-model="formData.validityDateBegin"
|
||
|
placeholder="请选择有效期开始日期"
|
||
|
style="width: 100%"
|
||
|
/>
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
|
||
|
<a-col :span="12">
|
||
|
<a-form-item label="有效期结束" field="validityDateEnd">
|
||
|
<a-date-picker
|
||
|
v-model="formData.validityDateEnd"
|
||
|
placeholder="请选择有效期结束日期"
|
||
|
style="width: 100%"
|
||
|
/>
|
||
|
</a-form-item>
|
||
|
</a-col>
|
||
|
</a-row>
|
||
|
|
||
|
<a-form-item label="证书图片" field="certificationImage">
|
||
|
<a-upload
|
||
|
:custom-request="handleUpload"
|
||
|
:show-file-list="false"
|
||
|
accept="image/*"
|
||
|
:before-upload="beforeUpload"
|
||
|
>
|
||
|
<template #upload-button>
|
||
|
<div class="upload-wrapper">
|
||
|
<a-image
|
||
|
v-if="formData.certificationImage"
|
||
|
:src="formData.certificationImage"
|
||
|
width="200"
|
||
|
height="120"
|
||
|
fit="cover"
|
||
|
show-loader
|
||
|
preview
|
||
|
/>
|
||
|
<div v-else class="upload-placeholder">
|
||
|
<icon-plus />
|
||
|
<div>点击上传证书图片</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
</a-upload>
|
||
|
</a-form-item>
|
||
|
</a-form>
|
||
|
</a-modal>
|
||
|
</div>
|
||
|
</GiPageLayout>
|
||
|
</template>
|
||
|
|
||
|
<script setup lang="ts">
|
||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||
|
import { IconPlus, IconSearch, IconRefresh } from '@arco-design/web-vue/es/icon'
|
||
|
import * as CertificationAPI from '@/apis/employee'
|
||
|
import type { CertificationInfo, CertificationListParams, SimpleUserInfo } from '@/apis/employee'
|
||
|
import { uploadFile } from '@/apis/common/common'
|
||
|
|
||
|
defineOptions({ name: 'HRCertification' })
|
||
|
|
||
|
// 数据状态
|
||
|
const loading = ref(false)
|
||
|
const loadingUsers = ref(false)
|
||
|
const modalVisible = ref(false)
|
||
|
const isEdit = ref(false)
|
||
|
const formRef = ref()
|
||
|
|
||
|
// 列表数据
|
||
|
const certificationList = ref<CertificationInfo[]>([])
|
||
|
const userList = ref<SimpleUserInfo[]>([])
|
||
|
|
||
|
// 搜索表单
|
||
|
const searchForm = reactive<CertificationListParams>({
|
||
|
certificationName: '',
|
||
|
certificationType: '',
|
||
|
userName: ''
|
||
|
})
|
||
|
|
||
|
// 分页配置
|
||
|
const paginationConfig = reactive({
|
||
|
current: 1,
|
||
|
pageSize: 10,
|
||
|
total: 0,
|
||
|
showTotal: true,
|
||
|
showJumper: true
|
||
|
})
|
||
|
|
||
|
// 表单数据
|
||
|
const formData = reactive<CertificationInfo>({
|
||
|
certificationId: '',
|
||
|
certificationCode: '',
|
||
|
certificationImage: '',
|
||
|
certificationName: '',
|
||
|
certificationType: '',
|
||
|
userId: '',
|
||
|
validityDateBegin: '',
|
||
|
validityDateEnd: ''
|
||
|
})
|
||
|
|
||
|
// 表单验证规则
|
||
|
const formRules = {
|
||
|
certificationCode: [
|
||
|
{ required: true, message: '请输入证书编号' }
|
||
|
],
|
||
|
certificationName: [
|
||
|
{ required: true, message: '请输入证书名称' }
|
||
|
],
|
||
|
certificationType: [
|
||
|
{ required: true, message: '请输入证书类型' }
|
||
|
],
|
||
|
userId: [
|
||
|
{ required: true, message: '请选择持证人' }
|
||
|
],
|
||
|
validityDateBegin: [
|
||
|
{ required: true, message: '请选择有效期开始日期' }
|
||
|
],
|
||
|
validityDateEnd: [
|
||
|
{ required: true, message: '请选择有效期结束日期' }
|
||
|
]
|
||
|
}
|
||
|
|
||
|
// 表格列定义
|
||
|
const columns = [
|
||
|
{
|
||
|
title: '证书编号',
|
||
|
dataIndex: 'certificationCode',
|
||
|
width: 150
|
||
|
},
|
||
|
{
|
||
|
title: '证书名称',
|
||
|
dataIndex: 'certificationName',
|
||
|
width: 200
|
||
|
},
|
||
|
{
|
||
|
title: '证书类型',
|
||
|
dataIndex: 'certificationType',
|
||
|
width: 150
|
||
|
},
|
||
|
{
|
||
|
title: '持证人',
|
||
|
dataIndex: 'userId',
|
||
|
slotName: 'userName',
|
||
|
width: 120
|
||
|
},
|
||
|
{
|
||
|
title: '证书图片',
|
||
|
dataIndex: 'certificationImage',
|
||
|
slotName: 'certificationImage',
|
||
|
width: 100
|
||
|
},
|
||
|
{
|
||
|
title: '有效期',
|
||
|
dataIndex: 'validityPeriod',
|
||
|
slotName: 'validityPeriod',
|
||
|
width: 200
|
||
|
},
|
||
|
{
|
||
|
title: '操作',
|
||
|
slotName: 'actions',
|
||
|
width: 150,
|
||
|
fixed: 'right'
|
||
|
}
|
||
|
]
|
||
|
|
||
|
// 获取用户名称
|
||
|
const getUserName = (userId: string) => {
|
||
|
const user = userList.value.find(u => u.userId === userId)
|
||
|
return user ? user.name : '-'
|
||
|
}
|
||
|
|
||
|
// 用户搜索过滤
|
||
|
const filterUserOption = (inputValue: string, option: any) => {
|
||
|
const user = userList.value.find(u => u.userId === option.value)
|
||
|
if (!user) return false
|
||
|
|
||
|
const searchText = inputValue.toLowerCase()
|
||
|
return user.name.toLowerCase().includes(searchText) ||
|
||
|
user.account.toLowerCase().includes(searchText)
|
||
|
}
|
||
|
|
||
|
// 获取用户列表
|
||
|
const getUserList = async () => {
|
||
|
try {
|
||
|
loadingUsers.value = true
|
||
|
const response = await CertificationAPI.getUserList()
|
||
|
|
||
|
console.log('用户列表响应:', response)
|
||
|
|
||
|
if (response.data) {
|
||
|
userList.value = response.data || []
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('获取用户列表失败:', error)
|
||
|
Message.error('获取用户列表失败')
|
||
|
} finally {
|
||
|
loadingUsers.value = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 获取资质信息列表
|
||
|
const getCertificationList = async () => {
|
||
|
try {
|
||
|
loading.value = true
|
||
|
const params: CertificationListParams = {
|
||
|
...searchForm,
|
||
|
current: paginationConfig.current,
|
||
|
size: paginationConfig.pageSize
|
||
|
}
|
||
|
|
||
|
const response = await CertificationAPI.getCertificationList(params)
|
||
|
|
||
|
console.log('资质信息列表响应:', response)
|
||
|
|
||
|
if (response.data) {
|
||
|
certificationList.value = response.data.records || []
|
||
|
paginationConfig.total = response.data.total || 0
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('获取资质信息列表失败:', error)
|
||
|
Message.error('获取资质信息列表失败')
|
||
|
} finally {
|
||
|
loading.value = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 文件上传前检查
|
||
|
const beforeUpload = (file: File) => {
|
||
|
const isImage = file.type.startsWith('image/')
|
||
|
if (!isImage) {
|
||
|
Message.error('只能上传图片文件')
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
const isLt2M = file.size / 1024 / 1024 < 2
|
||
|
if (!isLt2M) {
|
||
|
Message.error('图片大小不能超过 2MB')
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// 文件上传处理
|
||
|
const handleUpload = async (option: any) => {
|
||
|
try {
|
||
|
const uploadFormData = new FormData()
|
||
|
uploadFormData.append('file', option.fileItem.file)
|
||
|
|
||
|
const response = await uploadFile(uploadFormData)
|
||
|
|
||
|
if (response.data && response.data.url) {
|
||
|
formData.certificationImage = response.data.url
|
||
|
Message.success('图片上传成功')
|
||
|
option.onSuccess()
|
||
|
} else {
|
||
|
Message.error('图片上传失败')
|
||
|
option.onError()
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error('图片上传失败:', error)
|
||
|
Message.error('图片上传失败')
|
||
|
option.onError()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 搜索
|
||
|
const handleSearch = () => {
|
||
|
paginationConfig.current = 1
|
||
|
getCertificationList()
|
||
|
}
|
||
|
|
||
|
// 重置搜索
|
||
|
const handleReset = () => {
|
||
|
Object.assign(searchForm, {
|
||
|
certificationName: '',
|
||
|
certificationType: '',
|
||
|
userName: ''
|
||
|
})
|
||
|
paginationConfig.current = 1
|
||
|
getCertificationList()
|
||
|
}
|
||
|
|
||
|
// 分页变化
|
||
|
const handlePageChange = (page: number) => {
|
||
|
paginationConfig.current = page
|
||
|
getCertificationList()
|
||
|
}
|
||
|
|
||
|
// 显示新增模态框
|
||
|
const showAddModal = () => {
|
||
|
isEdit.value = false
|
||
|
resetForm()
|
||
|
modalVisible.value = true
|
||
|
}
|
||
|
|
||
|
// 编辑记录
|
||
|
const editRecord = (record: CertificationInfo) => {
|
||
|
isEdit.value = true
|
||
|
Object.assign(formData, record)
|
||
|
modalVisible.value = true
|
||
|
}
|
||
|
|
||
|
// 删除记录
|
||
|
const deleteRecord = (record: CertificationInfo) => {
|
||
|
Modal.confirm({
|
||
|
title: '确认删除',
|
||
|
content: '确定要删除这条资质信息吗?',
|
||
|
okText: '确认',
|
||
|
cancelText: '取消',
|
||
|
onOk: async () => {
|
||
|
try {
|
||
|
await CertificationAPI.deleteCertification(record.certificationId!)
|
||
|
Message.success('删除成功')
|
||
|
await getCertificationList()
|
||
|
} catch (error) {
|
||
|
console.error('删除失败:', error)
|
||
|
Message.error('删除失败')
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 表单提交
|
||
|
const handleSubmit = async () => {
|
||
|
try {
|
||
|
await formRef.value?.validate()
|
||
|
|
||
|
if (isEdit.value) {
|
||
|
await CertificationAPI.updateCertification(formData.certificationId!, formData)
|
||
|
Message.success('更新成功')
|
||
|
} else {
|
||
|
await CertificationAPI.createCertification(formData)
|
||
|
Message.success('创建成功')
|
||
|
}
|
||
|
|
||
|
modalVisible.value = false
|
||
|
resetForm()
|
||
|
await getCertificationList()
|
||
|
} catch (error) {
|
||
|
console.error('操作失败:', error)
|
||
|
Message.error('操作失败,请重试')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 取消操作
|
||
|
const handleCancel = () => {
|
||
|
modalVisible.value = false
|
||
|
resetForm()
|
||
|
}
|
||
|
|
||
|
// 重置表单
|
||
|
const resetForm = () => {
|
||
|
Object.assign(formData, {
|
||
|
certificationId: '',
|
||
|
certificationCode: '',
|
||
|
certificationImage: '',
|
||
|
certificationName: '',
|
||
|
certificationType: '',
|
||
|
userId: '',
|
||
|
validityDateBegin: '',
|
||
|
validityDateEnd: ''
|
||
|
})
|
||
|
formRef.value?.resetFields()
|
||
|
}
|
||
|
|
||
|
// 初始化
|
||
|
const init = async () => {
|
||
|
await Promise.all([
|
||
|
getUserList(),
|
||
|
getCertificationList()
|
||
|
])
|
||
|
}
|
||
|
|
||
|
// 组件挂载
|
||
|
onMounted(() => {
|
||
|
init()
|
||
|
})
|
||
|
</script>
|
||
|
|
||
|
<style scoped>
|
||
|
.certification-manage-container {
|
||
|
padding: 20px;
|
||
|
}
|
||
|
|
||
|
.page-header {
|
||
|
display: flex;
|
||
|
justify-content: space-between;
|
||
|
align-items: center;
|
||
|
margin-bottom: 20px;
|
||
|
}
|
||
|
|
||
|
.page-title {
|
||
|
margin: 0;
|
||
|
font-size: 18px;
|
||
|
font-weight: 500;
|
||
|
}
|
||
|
|
||
|
.search-card {
|
||
|
margin-bottom: 20px;
|
||
|
}
|
||
|
|
||
|
.table-card {
|
||
|
margin-bottom: 20px;
|
||
|
}
|
||
|
|
||
|
.upload-wrapper {
|
||
|
border: 1px dashed #d9d9d9;
|
||
|
border-radius: 6px;
|
||
|
cursor: pointer;
|
||
|
position: relative;
|
||
|
overflow: hidden;
|
||
|
transition: border-color 0.3s;
|
||
|
}
|
||
|
|
||
|
.upload-wrapper:hover {
|
||
|
border-color: #40a9ff;
|
||
|
}
|
||
|
|
||
|
.upload-placeholder {
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
width: 200px;
|
||
|
height: 120px;
|
||
|
background-color: #fafafa;
|
||
|
color: #999;
|
||
|
}
|
||
|
|
||
|
.upload-placeholder .arco-icon {
|
||
|
font-size: 24px;
|
||
|
margin-bottom: 8px;
|
||
|
}
|
||
|
</style>
|