增加人员管理模块

This commit is contained in:
BianLzhaoMin 2026-01-27 10:09:23 +08:00
parent 43f7a53627
commit 49cc1d8afa
5 changed files with 639 additions and 4 deletions

View File

@ -3,7 +3,7 @@ import request from '@/utils/request'
// 人员管理- 查询列表 // 人员管理- 查询列表
export function listPersonAPI(query) { export function listPersonAPI(query) {
return request({ return request({
url: '/personnel/getPersonnelList', url: '/worker/getWorkerList',
method: 'GET', method: 'GET',
params: query, params: query,
}) })
@ -12,7 +12,7 @@ export function listPersonAPI(query) {
// 人员管理- 新增 // 人员管理- 新增
export function addPersonAPI(data) { export function addPersonAPI(data) {
return request({ return request({
url: '/personnel/addPersonnel', url: '/worker/addWorker',
method: 'POST', method: 'POST',
data, data,
}) })
@ -21,7 +21,7 @@ export function addPersonAPI(data) {
// 人员管理- 修改 // 人员管理- 修改
export function updatePersonAPI(data) { export function updatePersonAPI(data) {
return request({ return request({
url: '/personnel/updatePersonnel', url: '/worker/updateWorker',
method: 'POST', method: 'POST',
data, data,
}) })
@ -30,7 +30,7 @@ export function updatePersonAPI(data) {
// 人员管理- 删除 // 人员管理- 删除
export function delPersonAPI(data) { export function delPersonAPI(data) {
return request({ return request({
url: `/personnel/delPersonnel`, url: `/worker/delWorker`,
method: 'POST', method: 'POST',
data, data,
}) })

View File

@ -0,0 +1,162 @@
<template>
<el-form
size="large"
label-width="auto"
:model="formData"
ref="formRef"
:rules="rules"
class="add-and-edit-form"
>
<el-form-item label="人员所属部门" prop="inspectionStationId">
<!-- <el-select
filter
clearable
style="width: 100%"
v-model="formData.inspectionStationId"
placeholder="请选择人员所属部门"
>
<el-option
v-for="item in inspectionStationOptions"
:key="item.id"
:value="item.id"
:label="item.value"
/>
</el-select> -->
<el-tree-select
clearable
check-strictly
value-key="id"
:data="enabledDeptOptions"
v-model="formData.inspectionStationId"
placeholder="请选择归属部门"
:props="{ value: 'id', label: 'label', children: 'children' }"
/>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input
clearable
maxlength="20"
show-word-limit
placeholder="请输入姓名"
v-model.trim="formData.name"
/>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-radio-group v-model="formData.sex">
<el-radio-button :label="1">男性 </el-radio-button>
<el-radio-button :label="0">女性 </el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input
clearable
maxlength="11"
show-word-limit
placeholder="请输入电话"
v-model.trim="formData.phone"
/>
</el-form-item>
</el-form>
</template>
<script setup name="AddAndEditForm">
import { ref, onMounted } from 'vue'
import { deptTreeSelect } from '@/api/system/user'
const props = defineProps({
formData: {
type: Object,
default: () => ({}),
},
inspectionStationOptions: {
type: Array,
default: () => [],
},
positionOptions: {
type: Array,
default: () => [],
},
natureOptions: {
type: Array,
default: () => [],
},
categoryOptions: {
type: Array,
default: () => [],
},
})
const deptOptions = ref(undefined)
const enabledDeptOptions = ref(undefined)
const formRef = ref(null)
const rules = {
inspectionStationId: [{ required: true, message: '请选择人员所属', trigger: 'change' }],
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
sex: [{ required: true, message: '请选择性别', trigger: 'change' }],
phone: [
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur',
},
],
positionId: [{ required: true, message: '请选择岗位', trigger: 'change' }],
personnelNatureId: [{ required: true, message: '请选择人员性质', trigger: 'change' }],
personnelClassificationId: [{ required: true, message: '请选择人员分类', trigger: 'change' }],
}
/** 查询部门下拉树结构 */
const getDeptTree = () => {
deptTreeSelect().then((response) => {
deptOptions.value = response.data
enabledDeptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
})
}
/** 过滤禁用的部门 */
const filterDisabledDept = (deptList) => {
return deptList.filter((dept) => {
if (dept.disabled) {
return false
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children)
}
return true
})
}
onMounted(() => {
getDeptTree()
})
//
defineExpose({
validate: () => formRef.value.validate(),
resetFields: () => formRef.value.resetFields(),
clearValidate: () => formRef.value.clearValidate(),
})
</script>
<style lang="scss" scoped>
.add-and-edit-form {
padding: 10px 20px;
:deep(.el-radio-button__inner) {
border-radius: 20px;
margin-right: 10px;
border: 1px solid #dcdfe6 !important;
box-shadow: none !important;
}
:deep(.el-radio-button:first-child .el-radio-button__inner) {
border-left: 1px solid #dcdfe6 !important;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
background-color: var(--el-color-primary-light-9);
color: var(--el-color-primary);
border-color: var(--el-color-primary) !important;
}
}
</style>

View File

@ -0,0 +1,76 @@
import { reactive } from 'vue'
import CryptoUtil from "../../../api/crypto.js";
// 根据下拉数据构建搜索表单配置
// 注意:这里不直接发请求,只依赖调用方传入的 options避免在模块顶层使用组合式 API
export const buildFormColumns = (
positionOptions = [],
natureOptions = [],
categoryOptions = [],
) => [
{
type: 'input',
prop: 'name',
placeholder: '请输入姓名',
},
{
type: 'select',
prop: 'sex',
placeholder: '请选择性别',
options: [
{
label: '男',
value: 1,
},
{
label: '女',
value: 0,
},
],
},
{
type: 'select',
prop: 'positionId',
placeholder: '请选择部门',
options: positionOptions.map((item) => ({
label: item.value,
value: item.id,
})),
},
]
export const tableColumns = [
{
prop: 'inspectionStationName',
label: '人员所属部门',
},
{
prop: 'name',
label: '姓名',
},
{
prop: 'sex',
label: '性别',
formatter: (row) => (row.sex == 1 ? '男' : '女'),
},
{
prop: 'phone',
label: '电话',
formatter: (row) => (CryptoUtil.decrypt(row.phone)),
},
]
export const dialogConfig = reactive({
outerVisible: false,
outerTitle: '新增人员',
outerWidth: '640px', // 根据图片缩小宽度更美观
minHeight: '400px',
maxHeight: '80vh',
})
export default {
tableColumns,
dialogConfig,
buildFormColumns,
}

View File

@ -0,0 +1,397 @@
<template>
<div class="app-container">
<!-- 人员管理 -->
<ComTable
ref="comTableRef"
:form-columns="formColumns"
:table-columns="tableColumns"
:load-data="listPersonAPI"
:show-toolbar="true"
:show-action="true"
:action-columns="actionColumns"
>
<template #toolbar>
<ComButton
type="primary"
icon="Plus"
@click="onHandleAdd"
v-hasPermi="['person:person:add']"
>
新建人员
</ComButton>
<ComButton
pain
type="info"
icon="Upload"
@click="onHandleImport"
v-hasPermi="['person:person:import']"
>
导入人员
</ComButton>
</template>
<template #longTermSecondment="{ row }">
<el-switch
active-value="1"
inactive-value="0"
:model-value="getLongTermSecondmentValue(row)"
@change="onHandleLongTermSecondmentChange($event, row)"
/>
</template>
</ComTable>
<ComDialog :dialog-config="dialogConfig" @closeDialogOuter="onCloseDialogOuter">
<template #outerContent>
<!-- 将表单抽离到独立组件中保持 index.vue 简洁 -->
<AddAndEditForm
ref="formRef"
:form-data="addAndEditForm"
:inspection-station-options="allPositionAndInspectionStationOptions"
:position-options="positionOptions"
:nature-options="natureOptions"
:category-options="categoryOptions"
/>
<el-row class="common-btn-row">
<ComButton plain type="info" @click="onHandleCancel">取消</ComButton>
<ComButton @click="onHandleSave">保存</ComButton>
</el-row>
</template>
</ComDialog>
<!-- 人员导入对话框 -->
<ComDialog :dialog-config="uploadDialogConfig" @closeDialogOuter="onCloseUploadDialog">
<template #outerContent>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<span>仅允许导入xlsxlsx格式文件</span>
<el-link
type="primary"
:underline="false"
style="font-size: 12px; vertical-align: baseline"
@click="onHandleDownloadTemplate"
>
下载模板
</el-link>
</div>
</template>
</el-upload>
<el-row class="common-btn-row">
<ComButton plain type="info" @click="onCloseUploadDialog">取消</ComButton>
<ComButton type="primary" @click="submitFileForm">确定</ComButton>
</el-row>
</template>
</ComDialog>
</div>
</template>
<script setup name="Person">
import { ref, nextTick, getCurrentInstance, computed } from 'vue'
import {
listPersonAPI,
addPersonAPI,
updatePersonAPI,
delPersonAPI,
} from '@/api/personManage/person.js'
import {
getInspectionStationSelectAPI,
getPersonNatureAndCategoryAndPositionSelectAPI,
} from '@/api/common.js'
import { useOptions } from '@/hooks/useOptions'
import { bus, BUS_EVENTS } from '@/utils/bus'
import { getToken } from '@/utils/auth'
import config from './config'
import ComTable from '@/components/ComTable/index.vue'
import ComButton from '@/components/ComButton/index.vue'
import ComDialog from '@/components/ComDialog/index.vue'
import AddAndEditForm from './addAndEditForm.vue'
import CryptoUtil from '../../../api/crypto.js'
const { tableColumns, dialogConfig, buildFormColumns } = config
const { proxy } = getCurrentInstance()
const formRef = ref(null)
const comTableRef = ref(null)
const editId = ref(null)
const uploadRef = ref(null)
// 使 Hook
// const { options: inspectionStationOptions } = useOptions(
// 'inspectionStationOptions',
// getInspectionStationSelectAPI,
// {},
// )
//
const uploadDialogConfig = reactive({
outerVisible: false,
outerTitle: '人员导入',
outerWidth: '400px',
minHeight: '300px',
maxHeight: '70vh',
})
//
const upload = reactive({
//
isUploading: false,
//
updateSupport: 0,
//
headers: { Authorization: 'Bearer ' + getToken() },
//
url: import.meta.env.VITE_APP_BASE_API + '/personnel/importPersonnel',
selectedFile: null,
})
const { options: allPositionAndInspectionStationOptions } = useOptions(
'allPositionAndInspectionStationOptions',
getInspectionStationSelectAPI,
{},
)
const { options: positionOptions } = useOptions(
'positionOptions',
getPersonNatureAndCategoryAndPositionSelectAPI,
{ category: 2 },
)
const { options: natureOptions } = useOptions(
'personNatureOptions',
getPersonNatureAndCategoryAndPositionSelectAPI,
{ category: 1 },
)
const { options: categoryOptions } = useOptions(
'personCategoryOptions',
getPersonNatureAndCategoryAndPositionSelectAPI,
{ category: 0 },
)
//
const formColumns = computed(() =>
buildFormColumns(positionOptions.value, natureOptions.value, categoryOptions.value),
)
// 1.
const getInitFormData = () => ({
inspectionStationId: null, //
name: '', //
sex: 1, // 1 0
phone: '', //
positionId: null, //
personnelNatureId: null, //
personnelClassificationId: null, //
longTermSecondment: 0, //
})
const addAndEditForm = ref(getInitFormData())
const actionColumns = [
{
label: '编辑',
type: 'primary',
link: true,
permission: ['person:person:edit'], // 使 v-hasPermi
handler: (row) => {
editId.value = row.id
dialogConfig.outerTitle = '编辑人员'
dialogConfig.outerVisible = true
// 2. 使 nextTick
nextTick(() => {
const {
inspectionStationId,
name,
phone,
sex,
positionId,
personnelNatureId,
personnelClassificationId,
longTermSecondment,
} = row
Object.assign(addAndEditForm.value, {
inspectionStationId: inspectionStationId + '',
name,
phone: CryptoUtil.decrypt(phone),
sex: sex * 1,
positionId: positionId + '',
personnelNatureId: personnelNatureId + '',
personnelClassificationId: personnelClassificationId + '',
longTermSecondment,
})
})
},
},
{
label: '删除',
type: 'danger',
link: true,
permission: ['person:person:remove'], // 使 v-hasPermi
handler: (row) => {
proxy.$modal.confirm('是否确认删除该人员?').then(async () => {
const result = await delPersonAPI({ id: row.id })
if (result.code === 200) {
proxy.$modal.msgSuccess('删除成功')
comTableRef.value?.refresh()
bus.emit(BUS_EVENTS.REFRESH_OPTIONS, 'personnelCommonOptions')
}
})
},
},
]
//
const onHandleAdd = () => {
editId.value = null
dialogConfig.outerTitle = '新增人员'
dialogConfig.outerVisible = true
addAndEditForm.value = getInitFormData()
nextTick(() => {
formRef.value?.clearValidate()
})
}
//
const onHandleCancel = () => {
dialogConfig.outerVisible = false
}
//
const onHandleSave = async () => {
try {
// 3. validate
await formRef.value.validate()
const API = editId.value ? updatePersonAPI : addPersonAPI
const params = JSON.parse(JSON.stringify(addAndEditForm.value))
editId.value ? (params.id = editId.value) : null
const result = await API(params)
if (result.code === 200) {
proxy.$modal.msgSuccess(editId.value ? '编辑成功' : '新增成功')
dialogConfig.outerVisible = false
comTableRef.value?.refresh()
bus.emit(BUS_EVENTS.REFRESH_OPTIONS, 'personnelCommonOptions')
}
} catch (error) {
return Promise.reject(error)
}
}
//
const onCloseDialogOuter = (visible) => {
dialogConfig.outerVisible = visible
if (!visible) {
formRef.value?.resetFields()
}
}
// longTermSecondment
const getLongTermSecondmentValue = (row) => {
const value = row.longTermSecondment
if (value === null || value === undefined) {
return '0'
}
//
return String(value)
}
//
const onHandleLongTermSecondmentChange = (value, row) => {
// row switch
row.longTermSecondment = value
// '1' -> 1, '0' -> 0
const params = {
id: row.id,
longTermSecondment: value === '1' ? 1 : 0,
}
updatePersonAPI(params).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('更新成功')
comTableRef.value?.refresh()
}
})
}
//
const onHandleImport = () => {
uploadDialogConfig.outerTitle = '人员导入'
uploadDialogConfig.outerVisible = true
upload.selectedFile = null
upload.updateSupport = 0
}
//
const onCloseUploadDialog = () => {
uploadDialogConfig.outerVisible = false
upload.selectedFile = null
upload.isUploading = false
if (uploadRef.value) {
uploadRef.value.clearFiles()
}
}
//
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true
}
//
const handleFileChange = (file, fileList) => {
upload.selectedFile = file
}
//
const handleFileRemove = (file, fileList) => {
upload.selectedFile = null
}
//
const handleFileSuccess = (response, file, fileList) => {
uploadDialogConfig.outerVisible = false
upload.isUploading = false
uploadRef.value?.handleRemove(file)
proxy.$alert(
"<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" +
response.msg +
'</div>',
'导入结果',
{ dangerouslyUseHTMLString: true },
)
comTableRef.value?.refresh()
}
//
const submitFileForm = () => {
const file = upload.selectedFile
if (
!file ||
file.length === 0 ||
(!file.name.toLowerCase().endsWith('.xls') && !file.name.toLowerCase().endsWith('.xlsx'))
) {
proxy.$modal.msgError('请选择后缀为 "xls"或"xlsx"的文件。')
return
}
uploadRef.value?.submit()
}
//
const onHandleDownloadTemplate = () => {
proxy.download('/personnel/downloadPersonnelExcel', {}, `人员模板.xlsx`)
}
</script>