增加组织机构功能

This commit is contained in:
jacky 2024-04-22 12:12:03 +08:00
parent 5ea1499f49
commit e477f62031
8 changed files with 763 additions and 4 deletions

View File

@ -12,7 +12,7 @@ export const getCategoryTree = () => {
// @Summary 新增分类
// @Produce application/json
// @Param menu Object
// @Param Object
// @Router /category/add [post]
export const addCategory = (data) => {
return service({
@ -36,7 +36,7 @@ export const deleteCategory = (data) => {
// @Summary 修改分类
// @Produce application/json
// @Param menu Object
// @Param Object
// @Router /category/update [put]
export const updateCategory = (data) => {
return service({

63
src/api/organize.js Normal file
View File

@ -0,0 +1,63 @@
import service from '@/utils/request'
// @Summary 获取部门树
// @Produce application/json
// @Router /organize/getTree [get]
export const getOrganizeTree = () => {
return service({
url: '/organize/getTree',
method: 'get',
})
}
// @Tags 部门
// @Summary 根据id获取部门
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param id int true "根据id获取"
// @Success 200 {string} json "{"success":true,"data":{},"msg":"获取成功"}"
// @Router /organize/get [get]
export const getOrganizeById = (params) => {
return service({
url: '/organize/get',
method: 'get',
params
})
}
// @Summary 新增部门
// @Produce application/json
// @Param Object
// @Router /organize/add [post]
export const addOrganize = (data) => {
return service({
url: '/organize/add',
method: 'post',
data
})
}
// @Summary 删除部门
// @Produce application/json
// @Param ID int
// @Router /organize/delete [delete]
export const deleteOrganize = (data) => {
return service({
url: '/organize/delete',
method: 'delete',
data
})
}
// @Summary 修改部门
// @Produce application/json
// @Param Object
// @Router /organize/update [put]
export const updateOrganize = (data) => {
return service({
url: '/organize/update',
method: 'put',
data
})
}

View File

@ -28,7 +28,7 @@ export const captcha = () => {
// @Router /base/resige [post]
export const register = (data) => {
return service({
url: '/user/admin_register',
url: '/user/adminRegister',
method: 'post',
data: data
})

View File

@ -28,7 +28,7 @@
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle" style="max-width: 400px;">
<el-form v-if="dialogFormVisible" ref="categoryForm" :inline="true" :model="form" :rules="rules"
label-position="top" label-width="85px">
<el-form-item label="分类名称" prop="path" style="width:90%">
<el-form-item label="分类名称" prop="title" style="width:90%">
<el-input v-model="form.title" autocomplete="off" />
</el-form-item>
<el-form-item label="父节点ID" style="width:90%">

View File

@ -0,0 +1,119 @@
<template>
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle" style="max-width: 400px;">
<el-form v-if="dialogFormVisible" ref="elFormRef" :inline="true" :model="form" :rules="rules" label-position="top"
label-width="85px">
<el-form-item label="部门名称" prop="name" style="width:90%">
<el-input v-model="form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="父节点ID" style="width:90%">
<el-cascader v-model="form.parentId" style="width:100%" :disabled="true" :options="props.organizeOptions"
:props="{ checkStrictly: true, label: 'name', value: 'ID', disabled: 'disabled', emitPath: false }"
:show-all-levels="false" filterable />
</el-form-item>
<el-form-item label="排序" prop="sort" style="width:90%">
<el-input-number v-model.number="form.sort" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
addOrganize,
updateOrganize,
getOrganizeById,
} from '@/api/organize'
defineOptions({
name: 'OrganizeEdit',
})
const props = defineProps({
organizeOptions: {
type: Object,
required: true
}
})
const emit = defineEmits(['on-save', 'on-close'])
// ------ form ------
const dialogTitle = ref('')
const isEdit = ref(false)
const dialogFormVisible = ref(false)
const rules = reactive({
name: [
{ required: true, message: '请输入部门名称', trigger: 'blur' }
],
})
const elFormRef = ref(null)
const form = ref({
ID: 0,
parentId: '',
name: '',
sort: '',
})
const initForm = () => {
elFormRef.value.resetFields()
form.value = {
ID: 0,
parentId: '',
name: '',
sort: '',
}
}
//
const handleClose = () => {
initForm()
dialogFormVisible.value = false
}
//
const handleSubmit = async () => {
elFormRef.value.validate(async valid => {
if (valid) {
let res
if (isEdit.value) {
res = await updateOrganize(form.value)
} else {
res = await addOrganize(form.value)
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: isEdit.value ? '编辑成功' : '添加成功!'
})
emit('on-save')
}
initForm()
dialogFormVisible.value = false
}
})
}
const openEdit = async (row) => {
console.log(row)
if (row !== null && row.name && row.name !== '') {
isEdit.value = true
dialogTitle.value = '修改-' + row.name
const res = await getOrganizeById({ ID: row.ID })
form.value = res.data.organize
} else {
isEdit.value = false
dialogTitle.value = '新增'
form.value = {
parentId: row.organizeId
}
}
dialogFormVisible.value = true
}
defineExpose({ openEdit })
</script>

View File

@ -0,0 +1,146 @@
<template>
<el-dialog v-model="addUserDialog" title="用户" :show-close="false" :close-on-press-escape="false"
:close-on-click-modal="false">
<el-form ref="userForm" :rules="rules" :model="userInfo" label-width="80px">
<el-form-item label="账号" prop="userName">
<el-input v-model="userInfo.userName" :disabled="dialogFlag !== 'add'" />
</el-form-item>
<el-form-item v-if="dialogFlag === 'add'" label="密码" prop="password">
<el-input v-model="userInfo.password" />
</el-form-item>
<el-form-item label="姓名" prop="nickName">
<el-input v-model="userInfo.nickName" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="userInfo.phone" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="userInfo.email" />
</el-form-item>
<el-form-item label="部门" prop="organizeId">
<el-cascader v-model="userInfo.organizeId" style="width:100%" :options="props.organizeOptions" show-all-levels
:props="{ multiple: false, checkStrictly: true, label: 'name', value: 'ID', disabled: 'disabled', emitPath: false }"
:clearable="false" />
</el-form-item>
<el-form-item label="角色" prop="authorityId">
<el-cascader v-model="userInfo.authorityIds" style="width:100%" :options="props.authOptions"
:show-all-levels="false"
:props="{ multiple: true, checkStrictly: true, label: 'authorityName', value: 'authorityId', disabled: 'disabled', emitPath: false }"
:clearable="false" />
</el-form-item>
<el-form-item label="启用" prop="disabled">
<el-switch v-model="userInfo.enable" inline-prompt :active-value="1" :inactive-value="2" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeAddUserDialog"> </el-button>
<el-button type="primary" @click="enterAddUserDialog"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import {
register,
setUserInfo
} from '@/api/user'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
authOptions: {
type: Object,
},
organizeOptions: {
type: Object
}
})
const emit = defineEmits(['on-save', 'on-close'])
//
const userInfo = ref({
organizeId: 0,
userName: '',
password: '',
nickName: '',
phone: '',
email: '',
authorityId: '',
authorityIds: [],
enable: 1,
})
const rules = ref({
userName: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 5, message: '最低5位字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入用户密码', trigger: 'blur' },
{ min: 6, message: '最低6位字符', trigger: 'blur' }
],
nickName: [
{ required: true, message: '请输入用户昵称', trigger: 'blur' }
],
authorityId: [
{ required: true, message: '请选择用户角色', trigger: 'blur' }
]
})
const userForm = ref(null)
const enterAddUserDialog = async () => {
userInfo.value.authorityId = userInfo.value.authorityIds[0]
userForm.value.validate(async valid => {
if (valid) {
const req = {
...userInfo.value
}
if (dialogFlag.value === 'add') {
const res = await register(req)
if (res.code === 0) {
ElMessage({ type: 'success', message: '创建成功' })
closeAddUserDialog()
emit('on-save')
}
}
if (dialogFlag.value === 'edit') {
const res = await setUserInfo(req)
if (res.code === 0) {
ElMessage({ type: 'success', message: '编辑成功' })
closeAddUserDialog()
emit('on-save')
}
}
}
})
}
const addUserDialog = ref(false)
const closeAddUserDialog = () => {
userForm.value.resetFields()
userInfo.value.headerImg = ''
userInfo.value.authorityIds = []
addUserDialog.value = false
}
const dialogFlag = ref('add')
const openEdit = (row) => {
console.log(row)
if (row !== null && row.userName && row.userName !== '') {
dialogFlag.value = 'edit'
userInfo.value = JSON.parse(JSON.stringify(row))
} else {
dialogFlag.value = 'add'
userInfo.value = {
organizeId: row.organizeId
}
}
addUserDialog.value = true
}
defineExpose({ openEdit })
</script>

View File

@ -0,0 +1,290 @@
<template>
<div v-if="selectedOrganize">
<div class="gva-btn-list" style="margin-bottom: 15px;">
<div class="text-black cursor-pointer text-lg leading-5" @click="totalCollapse">
<div v-if="isCollapse" class="gvaIcon gvaIcon-arrow-double-right" />
<div v-else class="gvaIcon gvaIcon-arrow-double-left" />
</div>
<el-text size="large" tag="b">用户管理当前选中{{ selectedOrganize.name }}{{ total }}</el-text>
</div>
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="addUser">新增用户</el-button>
</div>
<el-table :data="tableData" row-key="ID">
<el-table-column align="left" label="账户" min-width="120" prop="userName" />
<el-table-column align="left" label="姓名" min-width="120" prop="nickName" />
<el-table-column align="left" label="部门" min-width="200">
<template #default="scope">
<el-cascader v-model="scope.row.organizeId" :options="props.organizeOptions" :show-all-levels="false"
:props="{ multiple: false, checkStrictly: true, label: 'name', value: 'ID', disabled: 'disabled', emitPath: false }"
:clearable="false" @visible-change="(flag) => { changeOrganize(flag, scope.row) }" />
</template>
</el-table-column>
<el-table-column align="left" label="角色" min-width="200">
<template #default="scope">
<el-cascader v-model="scope.row.authorityIds" :options="authOptions" :show-all-levels="false" collapse-tags
:props="{ multiple: true, checkStrictly: true, label: 'authorityName', value: 'authorityId', disabled: 'disabled', emitPath: false }"
:clearable="false" @visible-change="(flag) => { changeAuthority(scope.row, flag, 0) }"
@remove-tag="(removeAuth) => { changeAuthority(scope.row, false, removeAuth) }" />
</template>
</el-table-column>
<el-table-column align="left" label="手机号" min-width="120" prop="phone" />
<el-table-column align="left" label="邮箱" min-width="220" prop="email" />
<el-table-column align="left" label="启用" min-width="80">
<template #default="scope">
<el-switch v-model="scope.row.enable" inline-prompt :active-value="1" :inactive-value="2"
@change="() => { switchEnable(scope.row) }" />
</template>
</el-table-column>
<el-table-column label="操作" min-width="120" fixed="right">
<template #default="scope">
<el-button type="primary" link icon="delete" @click="deleteUserFunc(scope.row)" />
<el-button type="primary" link icon="edit" @click="openEdit(scope.row)" />
<el-button type="primary" link icon="Tools" @click="resetPasswordFunc(scope.row)" />
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination :current-page="page" :page-size="pageSize" :page-sizes="[10, 30, 50, 100]" :total="total"
layout="total, sizes, prev, pager, next, jumper" @current-change="handleChangePage"
@size-change="handleChangePageSize" />
</div>
</div>
<div v-else>
<el-text size="large" tag="b">请选择部门</el-text>
</div>
<UserEdit ref="elUserEditRef" :authOptions="authOptions" :organizeOptions="props.organizeOptions"
@on-save="handleSaveUser" />
</template>
<script setup>
import {
getUserList,
setUserAuthorities,
setUserInfo,
deleteUser,
resetPassword
} from '@/api/user'
import { getAuthorityList } from '@/api/authority'
import { nextTick, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { equalArr } from '@/utils/arr'
import UserEdit from '@/view/organize/components/userEdit.vue'
defineOptions({
name: 'User',
})
const props = defineProps({
organizeOptions: {
type: Object,
required: true
}
})
const emit = defineEmits(['collapse'])
const isCollapse = ref(false)
const totalCollapse = () => {
isCollapse.value = !isCollapse.value
emit('collapse', isCollapse.value)
}
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const selectedOrganize = ref(null)
//
const handleChangePageSize = (val) => {
pageSize.value = val
getTableData()
}
const handleChangePage = (val) => {
page.value = val
getTableData()
}
//
const getTableData = async () => {
const table = await getUserList({ page: page.value, pageSize: pageSize.value, organizeId: selectedOrganize.value.ID })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
//
const authOptions = ref([])
const setAuthorityOptions = (AuthorityData, optionsData) => {
AuthorityData &&
AuthorityData.forEach(item => {
if (item.children && item.children.length) {
const option = {
authorityId: item.authorityId,
authorityName: item.authorityName,
children: []
}
setAuthorityOptions(item.children, option.children)
optionsData.push(option)
} else {
const option = {
authorityId: item.authorityId,
authorityName: item.authorityName
}
optionsData.push(option)
}
})
}
const setOptions = (authData) => {
authOptions.value = []
setAuthorityOptions(authData, authOptions.value)
}
const initPage = async () => {
const res = await getAuthorityList({ page: 1, pageSize: 999 })
setOptions(res.data.list)
}
initPage()
watch(() => tableData.value, () => {
setAuthorityIds()
})
const openList = async (row) => {
selectedOrganize.value = row
getTableData()
}
const resetPasswordFunc = (row) => {
ElMessageBox.confirm(
'是否将此用户密码重置为123456?',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const res = await resetPassword({
ID: row.ID,
})
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg,
})
} else {
ElMessage({
type: 'error',
message: res.msg,
})
}
})
}
const setAuthorityIds = () => {
tableData.value && tableData.value.forEach((user) => {
user.authorityIds = user.authorities && user.authorities.map(i => {
return i.authorityId
})
})
}
const deleteUserFunc = async (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await deleteUser({ id: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
await getTableData()
}
})
}
const elUserEditRef = ref(null)
const addUser = () => {
elUserEditRef.value.openEdit({ organizeId: selectedOrganize.value.ID })
}
const openEdit = (user) => {
elUserEditRef.value.openEdit(user)
}
const handleSaveUser = () => {
getTableData()
}
const tempAuth = {}
const changeAuthority = async (row, flag, removeAuth) => {
if (flag) {
if (!removeAuth) {
tempAuth[row.ID] = [...row.authorityIds]
}
return
}
if (equalArr(tempAuth[row.ID], row.authorityIds)) {
return
}
await nextTick()
const res = await setUserAuthorities({
ID: row.ID,
authorityIds: row.authorityIds
})
if (res.code === 0) {
ElMessage({ type: 'success', message: '角色设置成功' })
} else {
if (!removeAuth) {
row.authorityIds = [...tempAuth[row.ID]]
delete tempAuth[row.ID]
} else {
row.authorityIds = [removeAuth, ...row.authorityIds]
}
}
}
let oldOrganizeId = -1
const changeOrganize = async (flag, row) => {
if (flag) {
//
oldOrganizeId = row.organizeId
return
}
//
if (oldOrganizeId === row.organizeId) {
//
return
}
const res = await setUserInfo(row)
if (res.code === 0) {
ElMessage({ type: 'success', message: '部门设置成功' })
getTableData()
} else {
row.organizeId = oldOrganizeId
}
}
const switchEnable = async (row) => {
await nextTick()
const req = {
...row
}
const res = await setUserInfo(req)
if (res.code === 0) {
ElMessage({ type: 'success', message: `${req.enable === 2 ? '禁用' : '启用'}成功` })
await getTableData()
userInfo.value.headerImg = ''
userInfo.value.authorityIds = []
}
}
defineExpose({ openList })
</script>

141
src/view/organize/index.vue Normal file
View File

@ -0,0 +1,141 @@
<template>
<el-container>
<el-aside v-if="!isCollapse">
<div class="gva-table-box">
<div class="gva-btn-list">
<el-text size="large" tag="b">部门管理</el-text>
</div>
<!-- 由于此处分类跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
<el-tree ref="elOrganizeTreeRef" :data="dataSource" node-key="ID" default-expand-all highlight-current
:expand-on-click-node="false" @current-change="handleNodeClick">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ data.name }}</span>
<span>
<el-button v-if="data.ID > 0" type="success" link size="small" @click="handleNodeEdit(data)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
<el-button v-if="data.ID > 0" type="danger" link size="small" @click="handleNodeRemove(data)">
<el-icon>
<Delete />
</el-icon>
</el-button>
<el-button type="primary" link size="small" @click="handleNodeAdd(data)">
<el-icon>
<Plus />
</el-icon>
</el-button>
</span>
</span>
</template>
</el-tree>
<OrganizeEdit ref="elOrganizeEditRef" :organizeOptions="dataSource" @on-save="handleSaveSuccess" />
</div>
</el-aside>
<el-main :style="!isCollapse ? 'padding:0; margin-left: 15px;' : 'padding:0;'">
<div class="gva-table-box">
<UserList ref="elUserListRef" :organizeOptions="dataSource" @collapse="handleCollapse" />
</div>
</el-main>
</el-container>
</template>
<script setup>
import { ref, watch } from 'vue'
import UserList from '@/view/organize/components/userList.vue'
import OrganizeEdit from '@/view/organize/components/organizeEdit.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getOrganizeTree,
getOrganizeById,
addOrganize,
deleteOrganize,
updateOrganize,
} from '@/api/organize'
const props = defineProps({
name: 'Organize',
})
const isCollapse = ref(false)
const handleCollapse = (flag) => {
isCollapse.value = flag
}
const dataSource = ref([
{
ID: 0,
name: "中科智库",
children: []
}
])
const elOrganizeTreeRef = ref(null)
watch(() => dataSource.value, () => {
// elOrganizeTreeRef.setCurrentKey(0, true)
})
const getTreeData = async () => {
const res = await getOrganizeTree()
const root = [{
ID: 0,
name: "中科智库",
children: [],
}]
dataSource.value = root
if (res.code === 0) {
if (res.data.organizeTree) {
dataSource.value[0].children = res.data.organizeTree
}
}
}
getTreeData()
const elOrganizeEditRef = ref(null)
const handleNodeClick = (data) => {
elUserListRef.value.openList(data)
}
const handleNodeAdd = (data) => {
elOrganizeEditRef.value.openEdit({ parentId: data.ID })
}
const handleNodeEdit = async (data) => {
elOrganizeEditRef.value.openEdit(data)
}
const handleNodeRemove = (data) => {
console.log(data)
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await deleteOrganize({ ID: data.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
await getTreeData()
elUserListRef.value.openList(data)
}
})
}
const elUserListRef = ref(null)
const handleSaveSuccess = () => {
getTreeData()
}
</script>
<style>
.el-tree-node__content {
height: 32px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>