Browse Source

feat: 前端系统管理部分修改
1、部门管理新增分管领导
2、用户管理新增->关联人员,删除->解除关联
3、页面样式优化

qiny 1 year ago
parent
commit
1df139bdd8

+ 2 - 1
client/package.json

@@ -31,6 +31,7 @@
     "@form-create/designer": "^3.1.3",
     "@form-create/element-ui": "^3.1.24",
     "@iconify/iconify": "^3.1.1",
+    "@tanstack/vue-query": "4.32.6",
     "@videojs-player/vue": "^1.0.0",
     "@vue-office/docx": "^1.3.1",
     "@vue-office/excel": "^1.4.7",
@@ -49,9 +50,9 @@
     "dayjs": "^1.11.10",
     "diagram-js": "^12.3.0",
     "echarts": "^5.4.3",
-    "echarts-wordcloud": "^2.1.0",
     "echarts-gl": "^1.1.2",
     "echarts-liquidfill": "^2.0.6",
+    "echarts-wordcloud": "^2.1.0",
     "element-plus": "2.3.14",
     "fast-xml-parser": "^4.3.0",
     "highlight.js": "^11.8.0",

+ 20 - 0
client/src/api/system/dept/index.ts

@@ -41,3 +41,23 @@ export const updateDept = async (params: DeptVO) => {
 export const deleteDept = async (id: number) => {
   return await request.delete({ url: '/system/dept/delete?id=' + id })
 }
+
+// 部门关联人员
+interface IAssociated {
+  id: string
+  minorDeptId: string
+}
+export const associatedUser = async (params: IAssociated) => {
+  return await request.put({
+    url: `/system/user/add-minorDept`,
+    data: params
+  })
+}
+
+// 解除人员与部门关联
+export const disassociateUser = async (params: IAssociated) => {
+  return await request.put({
+    url: `/system/user/remove-minorDept`,
+    data: params
+  })
+}

+ 10 - 0
client/src/utils/tool.ts

@@ -0,0 +1,10 @@
+/**
+ * @description 一些会用到的实用工具
+ */
+
+/**
+ * 计算表格高度 = 页面高度 - 传入高度
+ */
+export const calculateTableHeight = (height) => {
+  return document.body.clientHeight - height
+}

+ 18 - 1
client/src/views/system/dept/DeptForm.vue

@@ -21,11 +21,26 @@
       <el-form-item label="部门名称" prop="name">
         <el-input v-model="formData.name" placeholder="请输入部门名称" />
       </el-form-item>
+      <el-form-item label="分管领导" prop="deptLeaderUserId">
+        <el-select
+          v-model="formData.deptLeaderUserId"
+          filterable
+          clearable
+          placeholder="请选择部门分管领导"
+        >
+          <el-option
+            v-for="item in userList"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
       <el-form-item label="显示排序" prop="sort">
         <el-input-number v-model="formData.sort" :min="0" controls-position="right" />
       </el-form-item>
       <el-form-item label="负责人" prop="leaderUserId">
-        <el-select v-model="formData.leaderUserId" clearable placeholder="请输入负责人">
+        <el-select v-model="formData.leaderUserId" filterable clearable placeholder="请选择负责人">
           <el-option
             v-for="item in userList"
             :key="item.id"
@@ -78,6 +93,7 @@ const formData = ref({
   title: '',
   parentId: undefined,
   name: undefined,
+  deptLeaderUserId: undefined,
   sort: undefined,
   leaderUserId: undefined,
   phone: undefined,
@@ -153,6 +169,7 @@ const resetForm = () => {
     title: '',
     parentId: undefined,
     name: undefined,
+    deptLeaderUserId: undefined,
     sort: undefined,
     leaderUserId: undefined,
     phone: undefined,

+ 19 - 6
client/src/views/system/dept/index.vue

@@ -50,22 +50,28 @@
   </ContentWrap>
 
   <!-- 列表 -->
-  <ContentWrap>
+  <ContentWrap class="dept-table">
     <el-table
       v-loading="loading"
       :data="list"
       row-key="id"
       :default-expand-all="isExpandAll"
       v-if="refreshTable"
+      :height="calculateTableHeight(310)"
     >
-      <el-table-column prop="name" label="部门名称" width="260" />
-      <el-table-column prop="leader" label="负责人" width="120">
+      <el-table-column prop="name" label="部门名称" width="360" />
+      <el-table-column prop="leader" label="负责人" width="100">
         <template #default="scope">
           {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}
         </template>
       </el-table-column>
-      <el-table-column prop="sort" label="排序" width="200" />
-      <el-table-column prop="status" label="状态" width="100">
+      <el-table-column prop="deptLeaderUserId" label="分管领导" width="100">
+        <template #default="scope">
+          {{ userList.find((user) => user.id === scope.row.deptLeaderUserId)?.nickname }}
+        </template>
+      </el-table-column>
+      <el-table-column prop="sort" label="排序" width="80" align="center" />
+      <el-table-column prop="status" label="状态" width="100" align="center">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>
@@ -74,7 +80,7 @@
         label="创建时间"
         align="center"
         prop="createTime"
-        width="180"
+        width="240"
         :formatter="dateFormatter"
       />
       <el-table-column label="操作" align="center" class-name="fixed-width">
@@ -110,6 +116,7 @@ import { handleTree } from '@/utils/tree'
 import * as DeptApi from '@/api/system/dept'
 import DeptForm from './DeptForm.vue'
 import * as UserApi from '@/api/system/user'
+import { calculateTableHeight } from '@/utils/tool'
 
 defineOptions({ name: 'SystemDept' })
 
@@ -188,3 +195,9 @@ onMounted(async () => {
   userList.value = await UserApi.getSimpleUserList()
 })
 </script>
+<style>
+.dept-table {
+  padding-bottom: 0 !important;
+  margin-bottom: 0 !important;
+}
+</style>

+ 68 - 0
client/src/views/system/user/AssociatedUser.vue

@@ -0,0 +1,68 @@
+<script lang="ts" setup>
+/**
+ * @description 关联用户弹窗
+ */
+import { ref } from 'vue'
+import { getSimpleUserList } from '@/api/system/user'
+import { associatedUser } from '@/api/system/dept'
+interface IAssociatedUser {
+  deptId: string // 关联部门id
+  deptName?: string // 名称
+}
+const props = defineProps<IAssociatedUser>()
+const { deptId, deptName = '' } = toRefs(props)
+const message = useMessage() // 消息弹窗
+const userList = ref<any[]>([]) // 用户列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const selectUser = ref(null) // 选中的用户
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submit = async () => {
+  if (!selectUser.value) {
+    message.info('请先选择关联用户!')
+    return
+  }
+  // 提交请求
+  formLoading.value = true
+  try {
+    await associatedUser({ id: selectUser.value, minorDeptId: deptId.value })
+    dialogVisible.value = false
+    message.success('用户关联成功!')
+    // 发送操作成功的事件
+    emit('success')
+  } catch {
+    // message.error('关联失败,请稍后再试!')
+    return
+  } finally {
+    formLoading.value = false
+    // 结束后初始化选择框
+    selectUser.value = null
+  }
+}
+/** 打开弹窗 */
+const open = async () => {
+  // 获得用户列表
+  userList.value = await getSimpleUserList()
+  dialogVisible.value = true
+}
+/** 取消 */
+const cancel = () => {
+  selectUser.value = null
+  dialogVisible.value = false
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>
+<template>
+  <el-dialog v-model="dialogVisible" :title="deptName" width="360">
+    关联人员:
+    <el-select filterable clearable placeholder="请选择" v-model="selectUser">
+      <el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
+    </el-select>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submit">确 定</el-button>
+      <el-button @click="cancel">取 消</el-button>
+    </template>
+  </el-dialog>
+</template>
+<style scoped lang="scss"></style>

+ 2 - 1
client/src/views/system/user/DeptTree.vue

@@ -17,6 +17,7 @@
       highlight-current
       node-key="id"
       @node-click="handleNodeClick"
+      :style="{ height: `${calculateTableHeight(300)}px`, overflowY: 'scroll' }"
     />
   </div>
 </template>
@@ -25,6 +26,7 @@
 import { ElTree } from 'element-plus'
 import * as DeptApi from '@/api/system/dept'
 import { defaultProps, handleTree } from '@/utils/tree'
+import { calculateTableHeight } from '@/utils/tool'
 
 defineOptions({ name: 'SystemUserDeptTree' })
 
@@ -48,7 +50,6 @@ const filterNode = (name: string, data: Tree) => {
 
 /** 处理部门被点击 */
 const handleNodeClick = async (row: { [key: string]: any }) => {
-  console.log(row)
   emits('node-click', row)
 }
 const emits = defineEmits(['node-click'])

+ 14 - 11
client/src/views/system/user/UserForm.vue

@@ -19,8 +19,11 @@
               v-model="formData.deptId"
               :data="deptList"
               :props="defaultProps"
+              :default-expand-all="true"
               check-strictly
               node-key="id"
+              filterable
+              clearable
               placeholder="请选择归属部门"
             />
           </el-form-item>
@@ -33,8 +36,15 @@
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="邮箱" prop="email">
-            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
+          <el-form-item label="用户性别">
+            <el-select v-model="formData.sex" placeholder="请选择">
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
           </el-form-item>
         </el-col>
       </el-row>
@@ -57,15 +67,8 @@
       </el-row>
       <el-row>
         <el-col :span="12">
-          <el-form-item label="用户性别">
-            <el-select v-model="formData.sex" placeholder="请选择">
-              <el-option
-                v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" maxlength="50" placeholder="请输入邮箱" />
           </el-form-item>
         </el-col>
         <el-col :span="12">

+ 94 - 15
client/src/views/system/user/index.vue

@@ -3,14 +3,22 @@
   <doc-alert title="三方登陆" url="https://doc.iocoder.cn/social-user/" />
   <doc-alert title="Excel 导入导出" url="https://doc.iocoder.cn/excel-import-and-export/" />
 
-  <el-row :gutter="20">
+  <el-row :gutter="20" class="user-manage-row">
     <!-- 左侧部门树 -->
-    <el-col :span="4" :xs="24">
-      <ContentWrap class="h-1/1">
+    <el-col :span="6" :xs="24">
+      <ContentWrap
+        class="h-1/1"
+        :style="{
+          height: `${calculateTableHeight(175)}px`,
+          overflowY: 'hidden',
+          paddingBottom: 0,
+          marginBottom: 0
+        }"
+      >
         <DeptTree @node-click="handleDeptNodeClick" />
       </ContentWrap>
     </el-col>
-    <el-col :span="20" :xs="24">
+    <el-col :span="18" :xs="24">
       <!-- 搜索 -->
       <ContentWrap>
         <el-form
@@ -20,10 +28,10 @@
           :inline="true"
           label-width="68px"
         >
-          <el-form-item label="用户名称" prop="username">
+          <el-form-item label="用户昵称" prop="nickname">
             <el-input
-              v-model="queryParams.username"
-              placeholder="请输入用户称"
+              v-model="queryParams.nickname"
+              placeholder="请输入用户称"
               clearable
               @keyup.enter="handleQuery"
               class="!w-240px"
@@ -66,13 +74,22 @@
           <el-form-item>
             <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
             <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
-            <el-button
+            <!-- <el-button
               type="primary"
               plain
               @click="openForm('create')"
               v-hasPermi="['system:user:create']"
             >
               <Icon icon="ep:plus" /> 新增
+            </el-button> -->
+            <el-button
+              type="primary"
+              plain
+              @click="associatedUser"
+              v-hasPermi="['system:user:create']"
+              v-if="deptName !== '浙江万维空间股份有限公司'"
+            >
+              <Icon icon="ep:plus" /> 关联人员
             </el-button>
             <el-button
               type="warning"
@@ -95,17 +112,19 @@
         </el-form>
       </ContentWrap>
       <ContentWrap>
-        <el-table v-loading="loading" :data="list">
+        <el-table v-loading="loading" :data="list" :height="calculateTableHeight(400)">
           <el-table-column
-            label="用户名称"
+            label="工号"
             align="center"
             prop="username"
+            width="100"
             :show-overflow-tooltip="true"
           />
           <el-table-column
             label="用户昵称"
             align="center"
             prop="nickname"
+            width="120"
             :show-overflow-tooltip="true"
           />
           <el-table-column
@@ -115,8 +134,8 @@
             prop="dept.name"
             :show-overflow-tooltip="true"
           />
-          <el-table-column label="手机号码" align="center" prop="mobile" width="120" />
-          <el-table-column label="状态" key="status">
+          <el-table-column label="手机号码" align="center" prop="mobile" width="150" />
+          <el-table-column label="状态" key="status" width="80">
             <template #default="scope">
               <el-switch
                 v-model="scope.row.status"
@@ -131,7 +150,7 @@
             align="center"
             prop="createTime"
             :formatter="dateFormatter"
-            width="180"
+            width="240"
           />
           <el-table-column label="操作" align="center" width="160">
             <template #default="scope">
@@ -155,12 +174,12 @@
                   <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
                   <template #dropdown>
                     <el-dropdown-menu>
-                      <el-dropdown-item
+                      <!-- <el-dropdown-item
                         command="handleDelete"
                         v-if="checkPermi(['system:user:delete'])"
                       >
                         <Icon icon="ep:delete" />删除
-                      </el-dropdown-item>
+                      </el-dropdown-item> -->
                       <el-dropdown-item
                         command="handleResetPwd"
                         v-if="checkPermi(['system:user:update-password'])"
@@ -173,6 +192,12 @@
                       >
                         <Icon icon="ep:circle-check" />分配角色
                       </el-dropdown-item>
+                      <el-dropdown-item
+                        command="disassociate"
+                        v-if="scope?.row?.deptId !== currentDeptId"
+                      >
+                        <Icon icon="ep:promotion" />取消关联
+                      </el-dropdown-item>
                     </el-dropdown-menu>
                   </template>
                 </el-dropdown>
@@ -192,6 +217,13 @@
 
   <!-- 添加或修改用户对话框 -->
   <UserForm ref="formRef" @success="getList" />
+  <!-- 关联人员 -->
+  <AssociatedUser
+    ref="associatedRef"
+    @success="getList"
+    :deptId="queryParams.deptId"
+    :deptName="deptName"
+  />
   <!-- 用户导入对话框 -->
   <UserImportForm ref="importFormRef" @success="getList" />
   <!-- 分配角色 -->
@@ -204,10 +236,14 @@ import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import { CommonStatusEnum } from '@/utils/constants'
 import * as UserApi from '@/api/system/user'
+import { disassociateUser } from '@/api/system/dept'
 import UserForm from './UserForm.vue'
 import UserImportForm from './UserImportForm.vue'
 import UserAssignRoleForm from './UserAssignRoleForm.vue'
 import DeptTree from './DeptTree.vue'
+import { calculateTableHeight } from '@/utils/tool'
+import AssociatedUser from './AssociatedUser.vue'
+import { ElMessageBox } from 'element-plus'
 
 defineOptions({ name: 'SystemUser' })
 
@@ -217,10 +253,13 @@ const { t } = useI18n() // 国际化
 const loading = ref(true) // 列表的加载中
 const total = ref(0) // 列表的总页数
 const list = ref([]) // 列表的数
+const deptName = ref('浙江万维空间股份有限公司') // 当前部门名(默认万维)
+const currentDeptId = ref('4e99393c-c0ea-4146-a7fb-56fb8019c477') // 当前部门id
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   username: undefined,
+  nickname: undefined,
   mobile: undefined,
   status: 0,
   deptId: undefined,
@@ -255,6 +294,8 @@ const resetQuery = () => {
 /** 处理部门被点击 */
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
+  currentDeptId.value = row.id
+  deptName.value = row.name ?? ''
   await getList()
 }
 
@@ -303,6 +344,34 @@ const handleExport = async () => {
   }
 }
 
+/** 关联人员 */
+const associatedRef = ref()
+const associatedUser = () => {
+  associatedRef.value.open()
+}
+/** 取消关联 */
+const disassociate = (row) => {
+  const { deptId, id, nickname } = row
+  // 判断是否是主要部门,主要部门不能取消关联
+  if (deptId === currentDeptId.value) {
+    message.error('主要部门不能取消关联!')
+    return
+  }
+  ElMessageBox.confirm(`确定将 ${nickname} 与 ${deptName.value} 取消关联?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(async () => {
+    try {
+      await disassociateUser({ id, minorDeptId: currentDeptId.value })
+      message.success('取消关联成功!')
+      await getList()
+    } catch {
+      // message.error('取消关联失败,请稍后再试!')
+    }
+  })
+}
+
 /** 操作分发 */
 const handleCommand = (command: string, row: UserApi.UserVO) => {
   switch (command) {
@@ -315,6 +384,10 @@ const handleCommand = (command: string, row: UserApi.UserVO) => {
     case 'handleRole':
       handleRole(row)
       break
+    case 'disassociate':
+      // 取消人员关联
+      disassociate(row)
+      break
     default:
       break
   }
@@ -359,3 +432,9 @@ onMounted(() => {
   getList()
 })
 </script>
+<style>
+.user-manage-row {
+  margin-bottom: -15px;
+  padding-bottom: 0;
+}
+</style>