feat: 人员分配模块前端实现
前端实现:
- userAssignment.js: API接口封装(56行,5个接口)
- getUserAssignmentList(): 分页查询
- getUserListByDept(): 按部门查询
- assignUser(): 分配用户
- batchAssignUsers(): 批量分配
- removeUserAssignment(): 移除分配
- userAssignment.vue: 人员分配页面(408行)
- 搜索功能:用户名、真实姓名、部门(树选择)、岗位、状态
- 表格展示:用户信息、部门标签、岗位标签、职位、状态
- 批量操作:多选批量分配
- 筛选功能:仅显示未分配用户
- 分配对话框:部门树选择、岗位下拉、职位输入
- 移除分配:确认后移除部门/岗位关联
技术特点:
- 多条件组合搜索
- 表格多选(el-table selection)
- 部门树选择器
- 岗位下拉选择
- 状态标签展示
- 批量分配优化
模块状态:✅ 完整(前端+后端)
This commit is contained in:
parent
25776cc9a4
commit
8ac97bb313
55
fund-admin/src/api/userAssignment.js
Normal file
55
fund-admin/src/api/userAssignment.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import request from '../utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户分配列表(分页)
|
||||||
|
*/
|
||||||
|
export const getUserAssignmentList = (params) => {
|
||||||
|
return request({
|
||||||
|
url: '/sys/api/v1/user-assignment/list',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门ID获取用户列表
|
||||||
|
*/
|
||||||
|
export const getUserListByDept = (deptId) => {
|
||||||
|
return request({
|
||||||
|
url: `/sys/api/v1/user-assignment/list/dept/${deptId}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分配用户到部门和岗位
|
||||||
|
*/
|
||||||
|
export const assignUser = (userId, data) => {
|
||||||
|
return request({
|
||||||
|
url: `/sys/api/v1/user-assignment/${userId}`,
|
||||||
|
method: 'put',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量分配用户
|
||||||
|
*/
|
||||||
|
export const batchAssignUsers = (userIds, deptId, postId) => {
|
||||||
|
return request({
|
||||||
|
url: '/sys/api/v1/user-assignment/batch',
|
||||||
|
method: 'put',
|
||||||
|
data: userIds,
|
||||||
|
params: { deptId, postId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除用户分配
|
||||||
|
*/
|
||||||
|
export const removeUserAssignment = (userId) => {
|
||||||
|
return request({
|
||||||
|
url: `/sys/api/v1/user-assignment/${userId}`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
407
fund-admin/src/views/system/userAssignment.vue
Normal file
407
fund-admin/src/views/system/userAssignment.vue
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
<template>
|
||||||
|
<div class="assignment-container">
|
||||||
|
<el-card>
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<el-form :inline="true" :model="searchForm">
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="searchForm.username" placeholder="请输入用户名" clearable style="width: 150px;" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="真实姓名">
|
||||||
|
<el-input v-model="searchForm.realName" placeholder="请输入真实姓名" clearable style="width: 150px;" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属部门">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="searchForm.deptId"
|
||||||
|
:data="deptTreeOptions"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
clearable
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 200px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="岗位">
|
||||||
|
<el-select v-model="searchForm.postId" placeholder="请选择岗位" clearable style="width: 150px;">
|
||||||
|
<el-option
|
||||||
|
v-for="post in postOptions"
|
||||||
|
:key="post.postId"
|
||||||
|
:label="post.postName"
|
||||||
|
:value="post.postId"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px;">
|
||||||
|
<el-option label="启用" :value="1" />
|
||||||
|
<el-option label="禁用" :value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 操作栏 -->
|
||||||
|
<el-row style="margin-bottom: 15px;">
|
||||||
|
<el-button type="primary" @click="handleBatchAssign" :disabled="selectedUsers.length === 0">
|
||||||
|
批量分配
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="handleShowUnassigned" :type="showUnassigned ? 'primary' : ''">
|
||||||
|
{{ showUnassigned ? '显示全部' : '仅显示未分配' }}
|
||||||
|
</el-button>
|
||||||
|
<span style="margin-left: 15px; color: #909399;">
|
||||||
|
已选择 {{ selectedUsers.length }} 人
|
||||||
|
</span>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
border
|
||||||
|
v-loading="loading"
|
||||||
|
stripe
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column prop="userId" label="用户ID" width="80" />
|
||||||
|
<el-table-column prop="username" label="用户名" width="120" />
|
||||||
|
<el-table-column prop="realName" label="真实姓名" width="120" />
|
||||||
|
<el-table-column prop="gender" label="性别" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.gender === 1 ? '男' : row.gender === 2 ? '女' : '未知' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="phone" label="手机号" width="140" />
|
||||||
|
<el-table-column prop="deptName" label="所属部门" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.deptName && row.deptName !== '-'" type="info">{{ row.deptName }}</el-tag>
|
||||||
|
<el-tag v-else type="warning">未分配</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="postName" label="岗位" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.postName && row.postName !== '-'" type="success">{{ row.postName }}</el-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="position" label="职位" width="150" />
|
||||||
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag v-if="row.status === 1" type="success">启用</el-tag>
|
||||||
|
<el-tag v-else type="danger">禁用</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" size="small" @click="handleAssign(row)">分配</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.deptId || row.postId"
|
||||||
|
type="warning"
|
||||||
|
size="small"
|
||||||
|
@click="handleRemove(row)"
|
||||||
|
>
|
||||||
|
移除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="page.current"
|
||||||
|
v-model:page-size="page.size"
|
||||||
|
:total="page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="fetchData"
|
||||||
|
@current-change="fetchData"
|
||||||
|
style="margin-top: 20px; justify-content: flex-end;"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 分配对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="isBatch ? '批量分配用户' : '分配用户'"
|
||||||
|
width="500px"
|
||||||
|
>
|
||||||
|
<el-form :model="form" label-width="100px">
|
||||||
|
<el-form-item label="选择用户" v-if="isBatch">
|
||||||
|
<el-tag
|
||||||
|
v-for="user in selectedUsers"
|
||||||
|
:key="user.userId"
|
||||||
|
style="margin-right: 5px; margin-bottom: 5px;"
|
||||||
|
>
|
||||||
|
{{ user.realName || user.username }}
|
||||||
|
</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户" v-else>
|
||||||
|
<span>{{ form.userName }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="所属部门">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="form.deptId"
|
||||||
|
:data="deptTreeOptions"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
clearable
|
||||||
|
check-strictly
|
||||||
|
:render-after-expand="false"
|
||||||
|
style="width: 100%;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="岗位">
|
||||||
|
<el-select v-model="form.postId" placeholder="请选择岗位" clearable 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-form-item label="职位">
|
||||||
|
<el-input v-model="form.position" placeholder="请输入职位(可选)" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import {
|
||||||
|
getUserAssignmentList,
|
||||||
|
assignUser,
|
||||||
|
batchAssignUsers,
|
||||||
|
removeUserAssignment
|
||||||
|
} from '../../api/userAssignment'
|
||||||
|
import { getDeptTree } from '../../api/dept'
|
||||||
|
import { getPostListEnabled } from '../../api/post'
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
username: '',
|
||||||
|
realName: '',
|
||||||
|
deptId: null,
|
||||||
|
postId: null,
|
||||||
|
status: null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableData = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const showUnassigned = ref(false)
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const page = reactive({
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选中的用户
|
||||||
|
const selectedUsers = ref([])
|
||||||
|
|
||||||
|
// 对话框
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const isBatch = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive({
|
||||||
|
userId: null,
|
||||||
|
userName: '',
|
||||||
|
deptId: null,
|
||||||
|
postId: null,
|
||||||
|
position: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 部门树选项
|
||||||
|
const deptTreeOptions = ref([])
|
||||||
|
|
||||||
|
// 岗位选项
|
||||||
|
const postOptions = ref([])
|
||||||
|
|
||||||
|
// 加载部门树
|
||||||
|
const loadDeptTree = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getDeptTree()
|
||||||
|
deptTreeOptions.value = convertToTreeSelect(data)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载部门树失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载岗位列表
|
||||||
|
const loadPostOptions = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getPostListEnabled()
|
||||||
|
postOptions.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载岗位列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为TreeSelect格式
|
||||||
|
const convertToTreeSelect = (data) => {
|
||||||
|
return data.map(item => ({
|
||||||
|
value: item.deptId,
|
||||||
|
label: item.deptName,
|
||||||
|
children: item.children ? convertToTreeSelect(item.children) : []
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
const fetchData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
current: page.current,
|
||||||
|
size: page.size,
|
||||||
|
username: searchForm.username || undefined,
|
||||||
|
realName: searchForm.realName || undefined,
|
||||||
|
deptId: searchForm.deptId || undefined,
|
||||||
|
postId: searchForm.postId || undefined,
|
||||||
|
status: searchForm.status !== null && searchForm.status !== '' ? searchForm.status : undefined
|
||||||
|
}
|
||||||
|
const res = await getUserAssignmentList(params)
|
||||||
|
|
||||||
|
let data = res.records
|
||||||
|
|
||||||
|
// 如果显示未分配,过滤掉已分配的
|
||||||
|
if (showUnassigned.value) {
|
||||||
|
data = data.filter(item => !item.deptId && !item.postId)
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData.value = data
|
||||||
|
page.total = res.total
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载数据失败:', error)
|
||||||
|
ElMessage.error(error.message || '加载数据失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
page.current = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.username = ''
|
||||||
|
searchForm.realName = ''
|
||||||
|
searchForm.deptId = null
|
||||||
|
searchForm.postId = null
|
||||||
|
searchForm.status = null
|
||||||
|
showUnassigned.value = false
|
||||||
|
page.current = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示未分配
|
||||||
|
const handleShowUnassigned = () => {
|
||||||
|
showUnassigned.value = !showUnassigned.value
|
||||||
|
page.current = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择变化
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
selectedUsers.value = selection
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个分配
|
||||||
|
const handleAssign = (row) => {
|
||||||
|
isBatch.value = false
|
||||||
|
form.userId = row.userId
|
||||||
|
form.userName = row.realName || row.username
|
||||||
|
form.deptId = row.deptId
|
||||||
|
form.postId = row.postId
|
||||||
|
form.position = row.position
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量分配
|
||||||
|
const handleBatchAssign = () => {
|
||||||
|
if (selectedUsers.value.length === 0) {
|
||||||
|
ElMessage.warning('请选择要分配的用户')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isBatch.value = true
|
||||||
|
form.deptId = null
|
||||||
|
form.postId = null
|
||||||
|
form.position = ''
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交分配
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (isBatch.value) {
|
||||||
|
// 批量分配
|
||||||
|
const userIds = selectedUsers.value.map(u => u.userId)
|
||||||
|
await batchAssignUsers(userIds, form.deptId, form.postId)
|
||||||
|
ElMessage.success(`成功分配 ${userIds.length} 个用户`)
|
||||||
|
} else {
|
||||||
|
// 单个分配
|
||||||
|
await assignUser(form.userId, {
|
||||||
|
deptId: form.deptId,
|
||||||
|
postId: form.postId,
|
||||||
|
position: form.position
|
||||||
|
})
|
||||||
|
ElMessage.success('分配成功')
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
selectedUsers.value = []
|
||||||
|
await fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分配失败:', error)
|
||||||
|
ElMessage.error(error.message || '分配失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除分配
|
||||||
|
const handleRemove = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定要移除 ${row.realName || row.username} 的部门/岗位分配吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await removeUserAssignment(row.userId)
|
||||||
|
ElMessage.success('移除分配成功')
|
||||||
|
await fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('移除分配失败:', error)
|
||||||
|
ElMessage.error(error.message || '移除分配失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadDeptTree()
|
||||||
|
loadPostOptions()
|
||||||
|
fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.assignment-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user