整合前端
This commit is contained in:
478
ruoyi-ui/src/views/hrm/employee/index.vue
Normal file
478
ruoyi-ui/src/views/hrm/employee/index.vue
Normal file
@@ -0,0 +1,478 @@
|
||||
<template>
|
||||
<div class="employee-page">
|
||||
<div class="main-layout">
|
||||
<!-- Left: Department Tree for Filtering -->
|
||||
<el-card shadow="never" class="dept-tree-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="header-title">部门列表</span>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="loadDeptTree">刷新</el-button>
|
||||
</div>
|
||||
<div class="tree-wrapper">
|
||||
<el-tree v-loading="deptLoading" :data="deptTree" :props="{ label: 'deptName', children: 'children' }"
|
||||
node-key="deptId" default-expand-all highlight-current :expand-on-click-node="false"
|
||||
@node-click="handleDeptNodeClick">
|
||||
<span slot-scope="{ node }" class="tree-node-label">
|
||||
<span>
|
||||
{{ node.label }}
|
||||
</span>
|
||||
<span v-if="node.data.leader" class="leader-tag">
|
||||
({{ node.data.leader }})
|
||||
</span>
|
||||
<span v-else class="leader-tag" style="color: red;" @click="goDept">
|
||||
请配置负责人
|
||||
</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Right: Employee List -->
|
||||
<el-card shadow="never" class="employee-list-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span class="header-title">员工档案</span>
|
||||
<div class="header-actions">
|
||||
<el-input v-model="query.empName" placeholder="姓名/工号" size="small" clearable style="width: 180px;"
|
||||
@keyup.enter.native="loadEmployeeList" />
|
||||
<el-select v-model="query.postId" size="small" placeholder="岗位" clearable filterable style="width: 160px;"
|
||||
@change="loadEmployeeList">
|
||||
<el-option v-for="post in postOptions" :key="post.postId" :label="post.postName" :value="post.postId" />
|
||||
</el-select>
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width: 120px;"
|
||||
@change="loadEmployeeList">
|
||||
<el-option label="在职" value="onboard" />
|
||||
<el-option label="离职" value="leave" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="loadEmployeeList">查询</el-button>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增员工</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="employeeList" v-loading="empLoading" stripe height="calc(100vh - 220px)">
|
||||
<el-table-column prop="empNo" label="工号" min-width="110" />
|
||||
<el-table-column prop="empName" label="姓名" min-width="120" />
|
||||
<el-table-column prop="deptName" label="部门" min-width="140" />
|
||||
<el-table-column prop="postName" label="岗位" min-width="140" />
|
||||
<el-table-column prop="mobile" label="手机号" min-width="130" />
|
||||
<el-table-column prop="status" label="状态" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'onboard' ? 'success' : 'info'" size="small">{{ scope.row.status ===
|
||||
'onboard' ? '在职' : '离职' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<el-pagination :current-page="query.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="query.pageSize"
|
||||
:total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
|
||||
@current-change="handlePageChange" />
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="700px" append-to-body>
|
||||
<el-form ref="empFormRef" :model="empForm" :rules="empRules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="empName">
|
||||
<el-input v-model="empForm.empName" placeholder="请输入员工姓名" @blur="handleEmpNameBlur" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工号" prop="empNo">
|
||||
<el-input v-model="empForm.empNo" placeholder="请输入工号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主部门" prop="deptId">
|
||||
<el-cascader v-model="empForm.deptId" :options="deptTree"
|
||||
:props="{ label: 'deptName', value: 'deptId', children: 'children', emitPath: false, checkStrictly: true }"
|
||||
clearable filterable placeholder="请选择主部门" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="主岗位" prop="postId">
|
||||
<el-select v-model="empForm.postId" placeholder="请选择主岗位" filterable style="width: 100%">
|
||||
<el-option v-for="post in postOptions" :key="post.postId" :label="post.postName" :value="post.postId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="empForm.mobile" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="empForm.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="empForm.status">
|
||||
<el-radio label="onboard">在职</el-radio>
|
||||
<el-radio label="leave">离职</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="关联系统用户">
|
||||
<el-switch v-model="empForm.linkUser" @change="handleLinkUserChange" />
|
||||
<span class="form-hint" style="margin-left: 8px;">是否关联 sys_user(该员工也是系统用户)</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16" v-if="empForm.linkUser">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="选择用户" prop="userId"
|
||||
:rules="empForm.linkUser ? [{ required: true, message: '请选择系统用户', trigger: 'change' }] : []">
|
||||
<el-select v-model="empForm.userId" placeholder="请选择系统用户" filterable remote :remote-method="searchUsers"
|
||||
:loading="userLoading" style="width: 100%" clearable>
|
||||
<el-option v-for="user in userOptions" :key="user.userId"
|
||||
:label="`${user.userName} (${user.nickName || ''})`" :value="user.userId" />
|
||||
</el-select>
|
||||
<div class="form-hint" v-if="!userSearchHint">
|
||||
<span v-if="empForm.empName">已根据姓名"{{ empForm.empName }}"自动搜索,</span>
|
||||
输入用户名或昵称进行搜索
|
||||
</div>
|
||||
<div class="form-hint" v-else style="color: #909399;">
|
||||
{{ userSearchHint }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDept } from '@/api/system/dept'
|
||||
import { listPost } from '@/api/system/post'
|
||||
import { selectUser } from '@/api/system/user'
|
||||
import { listEmployee, addEmployee, updateEmployee, delEmployee } from '@/api/hrm/employee'
|
||||
|
||||
export default {
|
||||
name: 'HrmEmployee',
|
||||
data() {
|
||||
return {
|
||||
deptLoading: false,
|
||||
empLoading: false,
|
||||
deptTree: [],
|
||||
postOptions: [],
|
||||
userOptions: [],
|
||||
userLoading: false,
|
||||
userSearchHint: '',
|
||||
employeeList: [],
|
||||
total: 0,
|
||||
query: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
empName: '',
|
||||
deptId: null,
|
||||
postId: null,
|
||||
status: 'onboard'
|
||||
},
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增员工',
|
||||
submitting: false,
|
||||
empForm: {},
|
||||
empRules: {
|
||||
empName: [{ required: true, message: '请输入员工姓名', trigger: 'blur' }],
|
||||
empNo: [{ required: true, message: '请输入工号', trigger: 'blur' }],
|
||||
deptId: [{ required: true, message: '请选择主部门', trigger: 'change' }],
|
||||
postId: [{ required: true, message: '请选择主岗位', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDeptTree()
|
||||
this.loadPostOptions()
|
||||
this.loadEmployeeList()
|
||||
},
|
||||
methods: {
|
||||
loadDeptTree() {
|
||||
this.deptLoading = true
|
||||
listDept({}).then(res => {
|
||||
this.deptTree = this.buildTree(res.data || [])
|
||||
}).finally(() => {
|
||||
this.deptLoading = false
|
||||
})
|
||||
},
|
||||
goDept() {
|
||||
this.$router.push('/renshi/dept')
|
||||
},
|
||||
buildTree(list) {
|
||||
const map = {}
|
||||
const roots = []
|
||||
list.forEach(item => { map[item.deptId] = { ...item, children: [] } })
|
||||
list.forEach(item => {
|
||||
const node = map[item.deptId]
|
||||
if (item.parentId && map[item.parentId]) {
|
||||
map[item.parentId].children.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
return roots
|
||||
},
|
||||
loadPostOptions() {
|
||||
listPost({ pageNum: 1, pageSize: 1000 }).then(res => {
|
||||
this.postOptions = res.rows || []
|
||||
})
|
||||
},
|
||||
loadEmployeeList() {
|
||||
this.empLoading = true
|
||||
listEmployee(this.query).then(res => {
|
||||
this.employeeList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => {
|
||||
this.empLoading = false
|
||||
})
|
||||
},
|
||||
handleDeptNodeClick(data) {
|
||||
this.query.deptId = data.deptId
|
||||
this.query.pageNum = 1
|
||||
this.loadEmployeeList()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.query.pageSize = val
|
||||
this.loadEmployeeList()
|
||||
},
|
||||
handlePageChange(val) {
|
||||
this.query.pageNum = val
|
||||
this.loadEmployeeList()
|
||||
},
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增员工'
|
||||
this.empForm = {
|
||||
empName: '',
|
||||
empNo: '',
|
||||
deptId: this.query.deptId || undefined,
|
||||
postId: undefined,
|
||||
status: 'onboard',
|
||||
linkUser: false,
|
||||
userId: undefined
|
||||
}
|
||||
this.userOptions = []
|
||||
this.userSearchHint = ''
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.empFormRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑员工'
|
||||
this.empForm = {
|
||||
...row,
|
||||
linkUser: !!row.userId,
|
||||
userId: row.userId || undefined
|
||||
}
|
||||
this.userSearchHint = ''
|
||||
if (this.empForm.linkUser && this.empForm.userId) {
|
||||
this.searchUsers('')
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.empFormRef?.clearValidate() })
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除员工 "${row.empName}" 吗?`, '提示', { type: 'warning' }).then(() => {
|
||||
delEmployee(row.empId).then(() => {
|
||||
this.$message.success('删除成功')
|
||||
this.loadEmployeeList()
|
||||
})
|
||||
})
|
||||
},
|
||||
handleLinkUserChange(val) {
|
||||
if (!val) {
|
||||
this.empForm.userId = undefined
|
||||
this.userOptions = []
|
||||
this.userSearchHint = ''
|
||||
} else if (this.empForm.empName) {
|
||||
// 如果开启了关联用户且已有姓名,自动搜索
|
||||
this.handleEmpNameBlur()
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.empFormRef?.clearValidate()
|
||||
})
|
||||
},
|
||||
handleEmpNameBlur() {
|
||||
// 当姓名输入完成且开启了关联用户时,自动用姓名搜索用户
|
||||
if (this.empForm.linkUser && this.empForm.empName && !this.empForm.userId) {
|
||||
this.searchUsersByNickName(this.empForm.empName)
|
||||
}
|
||||
},
|
||||
searchUsersByNickName(nickName) {
|
||||
if (!nickName) return
|
||||
this.userLoading = true
|
||||
this.userSearchHint = ''
|
||||
selectUser({ nickName: nickName, pageNum: 1, pageSize: 10 }).then(res => {
|
||||
const users = res.rows || []
|
||||
if (users.length > 0) {
|
||||
// 精确匹配优先
|
||||
const exactMatch = users.find(u => u.nickName === nickName)
|
||||
if (exactMatch) {
|
||||
this.userOptions = [exactMatch]
|
||||
this.empForm.userId = exactMatch.userId
|
||||
this.userSearchHint = `已自动匹配到用户:${exactMatch.userName} (${exactMatch.nickName})`
|
||||
} else {
|
||||
// 模糊匹配
|
||||
this.userOptions = users
|
||||
if (users.length === 1) {
|
||||
this.empForm.userId = users[0].userId
|
||||
this.userSearchHint = `已自动匹配到用户:${users[0].userName} (${users[0].nickName})`
|
||||
} else {
|
||||
this.userSearchHint = `找到 ${users.length} 个匹配的用户,请选择`
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.userOptions = []
|
||||
this.userSearchHint = `未找到昵称为"${nickName}"的用户,请手动输入用户名或昵称进行搜索`
|
||||
}
|
||||
}).catch(() => {
|
||||
this.userOptions = []
|
||||
this.userSearchHint = '搜索失败,请手动输入用户名或昵称进行搜索'
|
||||
}).finally(() => {
|
||||
this.userLoading = false
|
||||
})
|
||||
},
|
||||
searchUsers(query) {
|
||||
if (!query && !this.empForm.userId) {
|
||||
this.userOptions = []
|
||||
this.userSearchHint = ''
|
||||
return
|
||||
}
|
||||
this.userLoading = true
|
||||
this.userSearchHint = ''
|
||||
selectUser({ userName: query, nickName: query, pageNum: 1, pageSize: 20 }).then(res => {
|
||||
this.userOptions = res.rows || []
|
||||
if (this.userOptions.length === 0) {
|
||||
this.userSearchHint = '未找到匹配的用户,请尝试其他关键词'
|
||||
} else if (this.userOptions.length === 1) {
|
||||
// 如果只有一个结果,自动选中
|
||||
if (!this.empForm.userId) {
|
||||
this.empForm.userId = this.userOptions[0].userId
|
||||
this.userSearchHint = `已自动匹配到用户:${this.userOptions[0].userName} (${this.userOptions[0].nickName || ''})`
|
||||
}
|
||||
}
|
||||
// 如果编辑时已有 userId,确保在选项中
|
||||
if (this.empForm.userId && !this.userOptions.find(u => u.userId === this.empForm.userId)) {
|
||||
// 尝试单独查询该用户
|
||||
selectUser({ userId: this.empForm.userId }).then(res2 => {
|
||||
if (res2.rows && res2.rows.length > 0) {
|
||||
this.userOptions.unshift(res2.rows[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
this.userOptions = []
|
||||
this.userSearchHint = '搜索失败,请重试'
|
||||
}).finally(() => {
|
||||
this.userLoading = false
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.empFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
// 如果不关联用户,清空 userId
|
||||
if (!this.empForm.linkUser) {
|
||||
this.empForm.userId = undefined
|
||||
}
|
||||
this.submitting = true
|
||||
const api = this.empForm.empId ? updateEmployee : addEmployee
|
||||
api(this.empForm).then(() => {
|
||||
this.$message.success(this.empForm.empId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
// 新增后重置到第一页并刷新
|
||||
if (!this.empForm.empId) {
|
||||
this.query.pageNum = 1
|
||||
}
|
||||
this.loadEmployeeList()
|
||||
}).catch(() => {
|
||||
// 错误已在 request 拦截器中处理
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.employee-page {
|
||||
padding: 20px;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dept-tree-card,
|
||||
.employee-list-card {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-wrapper {
|
||||
height: calc(100vh - 160px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tree-node-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user