Explorar el Código

feat: 角色管理

wanggaokun hace 1 año
padre
commit
f59cf632bf
Se han modificado 48 ficheros con 1292 adiciones y 1077 borrados
  1. 1 1
      .prettierrc.cjs
  2. 1 4
      src/api/index.ts
  3. 1 1
      src/api/modules/system/menu.ts
  4. 81 0
      src/api/modules/system/role.ts
  5. 9 0
      src/api/modules/system/user.ts
  6. 18 17
      src/components/CustomDialog/index.vue
  7. 1 6
      src/components/DictTag/index.vue
  8. 1 6
      src/components/ECharts/config/index.ts
  9. 1 3
      src/components/ECharts/index.vue
  10. 1 3
      src/components/Grid/components/GridItem.vue
  11. 1 3
      src/components/Highlight/index.vue
  12. 1 2
      src/components/HighlightDialog/index.vue
  13. 2 14
      src/components/ImportExcel/index.vue
  14. 3 13
      src/components/ProForm/components/Item.vue
  15. 11 4
      src/components/ProForm/index.vue
  16. 2 2
      src/components/ProFormOld/index.vue
  17. 1 5
      src/components/ProTable/components/TableColumn.vue
  18. 13 37
      src/components/ProTable/index.vue
  19. 1 2
      src/components/ProTable/interface/index.ts
  20. 1 3
      src/components/SelectFilter/index.vue
  21. 122 0
      src/components/TableDialog/index.vue
  22. 373 0
      src/components/Upload/File.vue
  23. 1 4
      src/hooks/useTheme.ts
  24. 3 7
      src/hooks/useTime.ts
  25. 1 7
      src/layouts/LayoutClassic/index.vue
  26. 1 7
      src/layouts/LayoutColumns/index.vue
  27. 1 7
      src/layouts/LayoutVertical/index.vue
  28. 1 6
      src/layouts/components/Header/components/AssemblySize.vue
  29. 1 5
      src/layouts/components/Header/components/Breadcrumb.vue
  30. 1 6
      src/layouts/components/Header/components/Language.vue
  31. 3 27
      src/layouts/components/ThemeDrawer/index.vue
  32. 19 4
      src/routers/modules/routerData.json
  33. 15 0
      src/typings/ProForm.d.ts
  34. 1 3
      src/views/login/components/LoginForm.vue
  35. 1 2
      src/views/monitor/logininfor/index.vue
  36. 1 2
      src/views/monitor/operlog/index.vue
  37. 5 28
      src/views/system/dept/index.vue
  38. 6 30
      src/views/system/dict/data.vue
  39. 3 11
      src/views/system/dict/index.vue
  40. 6 18
      src/views/system/menu/index.vue
  41. 5 21
      src/views/system/post/index.vue
  42. 0 296
      src/views/system/role/authRole.vue
  43. 179 0
      src/views/system/role/authUser.vue
  44. 8 0
      src/views/system/role/index.scss
  45. 364 67
      src/views/system/role/index.vue
  46. 0 338
      src/views/system/user/authUser.vue
  47. 9 21
      src/views/system/user/index.vue
  48. 11 34
      src/views/tool/gen/index.vue

+ 1 - 1
.prettierrc.cjs

@@ -2,7 +2,7 @@
 
 module.exports = {
   // 指定最大换行长度
-  printWidth: 130,
+  printWidth: 150,
   // 缩进制表符宽度 | 空格数
   tabWidth: 2,
   // 使用制表符而不是空格缩进行 (true:制表符,false:空格)

+ 1 - 4
src/api/index.ts

@@ -110,10 +110,7 @@ class RequestHttp {
           // 生成一个 AES 密钥
           const aesKey = generateAesKey()
           config.headers[encryptHeader] = encrypt(encryptBase64(aesKey))
-          config.data =
-            typeof config.data === 'object'
-              ? encryptWithAes(JSON.stringify(config.data), aesKey)
-              : encryptWithAes(config.data, aesKey)
+          config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey)
         }
         return config
       },

+ 1 - 1
src/api/modules/system/menu.ts

@@ -78,7 +78,7 @@ export const exportApi = (data: any) => {
  * @name 查询菜单下拉树结构
  * @returns returns
  */
-export const treeselectApi = () => {
+export const treeSelectApi = () => {
   return http.get<any>(`/system/menu/treeselect`)
 }
 

+ 81 - 0
src/api/modules/system/role.ts

@@ -9,6 +9,24 @@ export const listRoleApi = (query: any) => {
   return http.get<any>('/system/role/list', query, { loading: true })
 }
 
+/**
+ * @name 根据角色ID查询部门树结构
+ * @param roleId 角色Id
+ * @returns 返回列表
+ */
+export const deptTreeSelectApi = (roleId: any) => {
+  return http.get<any>(`/system/role/deptTree/${roleId}`)
+}
+
+/**
+ * @name 根据角色ID查询菜单下拉树结构
+ * @param roleId 角色Id
+ * @returns 返回列表
+ */
+export const roleMenuTreeSelectApi = (roleId: any) => {
+  return http.get<any>(`/system/menu/roleMenuTreeselect/${roleId}`)
+}
+
 /**
  * @name 查询角色信息详细
  * @param roleId roleId
@@ -36,6 +54,24 @@ export const updateRoleApi = (data: any) => {
   return http.put<any>('/system/role', data, { loading: false })
 }
 
+/**
+ * @name 角色数据权限
+ * @param data data
+ * @returns returns
+ */
+export const dataScopeApi = (data: any) => {
+  return http.put<any>('/system/role/dataScope', data, { loading: false })
+}
+
+/**
+ * @name 修改角色状态
+ * @param data data
+ * @returns returns
+ */
+export const changeStatusApi = (data: any) => {
+  return http.put<any>('/system/role/changeStatus', data, { loading: false })
+}
+
 /**
  * @name 删除角色信息
  * @param roleId roleId
@@ -68,3 +104,48 @@ export const importDataApi = (data: any) => {
 export const exportApi = (data: any) => {
   return http.downloadPost('/system/role/export', data)
 }
+
+/**
+ * @name 查询角色已授权用户列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const allocatedUserListApi = (query: any) => {
+  return http.get<any>(`/system/role/authUser/allocatedList`, query, { loading: true })
+}
+
+/**
+ * @name 查询角色未授权用户列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const unallocatedUserListApi = (query: any) => {
+  return http.get<any>(`/system/role/authUser/unallocatedList`, query, { loading: true })
+}
+
+/**
+ * @name 取消用户授权角色
+ * @param data data
+ * @returns returns
+ */
+export const authUserCancelApi = (data: any) => {
+  return http.put<any>('/system/role/authUser/cancel', data, { loading: false })
+}
+
+/**
+ * @name 批量取消用户授权角色
+ * @param data data
+ * @returns returns
+ */
+export const authUserCancelAllApi = (data: any) => {
+  return http.put<any>('/system/role/authUser/cancelAll', data, { loading: false })
+}
+
+/**
+ * @name 授权用户选择
+ * @param data data
+ * @returns returns
+ */
+export const authUserSelectAllApi = (data: any) => {
+  return http.put<any>('/system/role/authUser/selectAll', data, { loading: false })
+}

+ 9 - 0
src/api/modules/system/user.ts

@@ -61,6 +61,15 @@ export const changeUserStatus = (data: any) => {
   return http.put<any>('/system/user/changeStatus', data, { loading: false })
 }
 
+/**
+ * @name 用户密码重置
+ * @param data data
+ * @returns returns
+ */
+export const resetUserPwdApi = (data: any) => {
+  return http.put<any>('/system/user/resetPwd', data, { loading: false })
+}
+
 /**
  * @name 下载模板
  * @returns returns

+ 18 - 17
src/components/CustomDialog/index.vue

@@ -8,11 +8,12 @@
     :top="parameter.top"
     draggable
   >
-    <slot name="default" :model="parameter.model"></slot>
+    <slot name="default" :parameter="parameter"></slot>
     <template #footer>
       <span class="dialog-footer">
-        <el-button type="primary" v-if="parameter.isEdit" :loading="butLoading" @click="handleSubmit">确认</el-button>
-        <el-button @click="handleCancel">取消</el-button>
+        <slot name="footer" :parameter="parameter"></slot>
+        <!-- <el-button type="primary" v-if="parameter.isEdit" :loading="butLoading" @click="handleSubmit">确认</el-button>
+        <el-button @click="handleCancel">取消</el-button> -->
       </span>
     </template>
   </el-dialog>
@@ -20,7 +21,6 @@
 
 <script setup lang="ts" name="FormDialog">
 import { ref } from 'vue'
-import { ElMessage } from 'element-plus'
 
 export interface FormParameterProps {
   title: string // 标题
@@ -43,18 +43,18 @@ const parameter = ref<FormParameterProps>({
   api: undefined
 })
 // 提交
-const handleSubmit = () => {
-  butLoading.value = true
-  parameter.value.api!(parameter.value.model).then(res => {
-    if (res.code == 200) {
-      ElMessage.success('操作成功')
-      dialogVisible.value = false
-    } else {
-      console.log('message', res.message)
-    }
-  })
-  butLoading.value = false
-}
+// const handleSubmit = () => {
+//   butLoading.value = true
+//   parameter.value.api!(parameter.value.model).then(res => {
+//     if (res.code == 200) {
+//       ElMessage.success('操作成功')
+//       dialogVisible.value = false
+//     } else {
+//       console.log('message', res.message)
+//     }
+//   })
+//   butLoading.value = false
+// }
 
 // 取消按钮,重置表单,关闭弹框
 const handleCancel = () => {
@@ -70,6 +70,7 @@ const openDialog = (params: FormParameterProps) => {
 }
 
 defineExpose({
-  openDialog
+  openDialog,
+  handleCancel
 })
 </script>

+ 1 - 6
src/components/DictTag/index.vue

@@ -2,12 +2,7 @@
   <div>
     <template v-for="(item, index) in options">
       <template v-if="values.includes(item.value)">
-        <span
-          v-if="item.elTagType == 'default' || item.elTagType == ''"
-          :key="item.value"
-          :index="index"
-          :class="item.elTagClass"
-        >
+        <span v-if="item.elTagType == 'default' || item.elTagType == ''" :key="item.value" :index="index" :class="item.elTagClass">
           {{ item.label }}
         </span>
         <el-tag

+ 1 - 6
src/components/ECharts/config/index.ts

@@ -23,12 +23,7 @@ import type {
   RadarSeriesOption,
   GaugeSeriesOption
 } from 'echarts/charts'
-import type {
-  TitleComponentOption,
-  TooltipComponentOption,
-  GridComponentOption,
-  DatasetComponentOption
-} from 'echarts/components'
+import type { TitleComponentOption, TooltipComponentOption, GridComponentOption, DatasetComponentOption } from 'echarts/components'
 import type { ComposeOption } from 'echarts/core'
 import 'echarts-liquidfill'
 

+ 1 - 3
src/components/ECharts/index.vue

@@ -26,9 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
 })
 
 const echartsStyle = computed(() => {
-  return props.width || props.height
-    ? { height: props.height + 'px', width: props.width + 'px' }
-    : { height: '100%', width: '100%' }
+  return props.width || props.height ? { height: props.height + 'px', width: props.width + 'px' } : { height: '100%', width: '100%' }
 })
 
 const chartRef = ref<HTMLDivElement | HTMLCanvasElement>()

+ 1 - 3
src/components/Grid/components/GridItem.vue

@@ -58,9 +58,7 @@ const style = computed(() => {
     }
   } else {
     return {
-      gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${
-        span + offset > cols.value ? cols.value : span + offset
-      }`,
+      gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${span + offset > cols.value ? cols.value : span + offset}`,
       marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset'
     }
   }

+ 1 - 3
src/components/Highlight/index.vue

@@ -1,7 +1,5 @@
 <template>
-  <pre
-    :class="'hx-scroll ' + lineNumbers"
-  ><code :class="'language-'+ type" v-html="Prism.highlight(code, Prism.languages[type], type)"></code></pre>
+  <pre :class="'hx-scroll ' + lineNumbers"><code :class="'language-'+ type" v-html="Prism.highlight(code, Prism.languages[type], type)"></code></pre>
 </template>
 <script setup lang="ts">
 import { onMounted, computed } from 'vue'

+ 1 - 2
src/components/HighlightDialog/index.vue

@@ -7,8 +7,7 @@
     :width="parameter.width"
     draggable
   >
-    <preview-code v-if="flag" :code="parameter.code" :type="parameter.type" :is-show-line-numbers="parameter.isShowLineNumbers">
-    </preview-code>
+    <preview-code v-if="flag" :code="parameter.code" :type="parameter.type" :is-show-line-numbers="parameter.isShowLineNumbers"> </preview-code>
     <template #footer>
       <span class="dialog-footer">
         <el-button type="primary" v-if="parameter.isEdit" :loading="butLoading">导出</el-button>

+ 2 - 14
src/components/ImportExcel/index.vue

@@ -1,12 +1,5 @@
 <template>
-  <el-dialog
-    v-model="dialogVisible"
-    :width="parameter.width"
-    :top="parameter.top"
-    :title="`${parameter.title}`"
-    :destroy-on-close="true"
-    draggable
-  >
+  <el-dialog v-model="dialogVisible" :width="parameter.width" :top="parameter.top" :title="`${parameter.title}`" :destroy-on-close="true" draggable>
     <el-form class="drawer-multiColumn-form" label-width="100px">
       <el-form-item label="文件上传">
         <el-upload
@@ -38,12 +31,7 @@
               </div>
               <div class="el-upload__tip text-center">
                 请上传 .xls , .xlsx 标准格式文件,文件最大为 {{ parameter.fileSize }}M。
-                <el-link
-                  type="primary"
-                  :underline="false"
-                  style="font-size: 12px; vertical-align: baseline"
-                  @click="downloadTemp"
-                >
+                <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="downloadTemp">
                   模板下载
                 </el-link>
               </div>

+ 3 - 13
src/components/ProForm/components/Item.vue

@@ -19,22 +19,12 @@
       ></component>
     </template>
     <template v-if="item.compOptions.elTagName === 'radio-group'">
-      <component
-        :is="`el-radio`"
-        v-for="(col, index) in itemEnum"
-        :key="index"
-        :label="col[item.compOptions.valueKey || 'value']"
-      >
+      <component :is="`el-radio`" v-for="(col, index) in itemEnum" :key="index" :value="col[item.compOptions.valueKey || 'value']">
         {{ col[item.compOptions.labelKey || 'label'] }}
       </component>
     </template>
     <template v-if="item.compOptions.elTagName === 'radio-button'">
-      <component
-        :is="`el-radio-button`"
-        v-for="(col, index) in itemEnum"
-        :key="index"
-        :label="col[item.compOptions.valueKey || 'value']"
-      >
+      <component :is="`el-radio-button`" v-for="(col, index) in itemEnum" :key="index" :label="col[item.compOptions.valueKey || 'value']">
         {{ col[item.compOptions.labelKey || 'label'] }}
       </component>
     </template>
@@ -43,7 +33,7 @@
         :is="`el-checkbox`"
         v-for="col in itemEnum"
         :key="col[item.compOptions.valueKey || 'value']"
-        :label="col[item.compOptions.valueKey || 'value']"
+        :value="col[item.compOptions.valueKey || 'value']"
       >
         {{ col[item.compOptions.labelKey || 'label'] }}
       </component>

+ 11 - 4
src/components/ProForm/index.vue

@@ -14,14 +14,19 @@
                   <i :class="'iconfont icon-yiwen'"></i>
                 </el-tooltip>
               </el-space>
-              <span>{{ `${_formOptions.labelSuffix}` }}</span>
-            </template>
-            <template v-if="item.compOptions.elTagName === 'slot'">
-              <slot :name="item.prop" :form-model="formModel"></slot>
+              <span v-if="item.hideLabelSuffix">{{ `${item.hideLabelSuffix ? '' : ':'}` }}</span>
+              <span v-else>{{ `${_formOptions.labelSuffix}` }}</span>
             </template>
+            <!-- <template v-if="item.compOptions.elTagName === 'slot'"> -->
+            <slot :name="item.prop" :form-model="formModel"></slot>
+            <slot name="default" :form-model="formModel"></slot>
+            <!-- </template> -->
             <template v-if="item.compOptions.elTagName === 'icon'">
               <SelectIcon v-model:icon-value="formModel[item.prop]" />
             </template>
+            <template v-else-if="item.compOptions.elTagName === 'file-upload'">
+              <FileUpload v-model:model-value="formModel[item.prop]" />
+            </template>
             <Item v-else :item="item" :form-model="formModel" />
           </component>
         </el-col>
@@ -46,6 +51,7 @@ import { ref, computed, ComputedRef, watch, unref, provide } from 'vue'
 import type { FormInstance } from 'element-plus'
 import Item from '@/components/ProForm/components/Item.vue'
 import SelectIcon from '@/components/SelectIcon/index.vue'
+import FileUpload from '@/components/Upload/File.vue'
 // import { handleProp } from '@/utils'
 // 表单整体配置项
 export interface ProFormProps {
@@ -162,6 +168,7 @@ const resetForm = (formEl: FormInstance | undefined) => {
 // 暴露方法给父组件使用
 defineExpose({
   proFormRef,
+  formModel,
   resetForm,
   onSubmit
 })

+ 2 - 2
src/components/ProFormOld/index.vue

@@ -16,7 +16,7 @@
               <!-- 单选框 -->
               <el-radio-group v-if="item.type === 'radio'" v-model="formModel[item.field]" :disabled="item.disabled">
                 <el-radio
-                  :label="val[item.options?.valueKey || 'value']"
+                  :value="val[item.options?.valueKey || 'value']"
                   v-for="val in item.options?.data"
                   :key="val[item.options?.valueKey || 'value']"
                 >
@@ -28,7 +28,7 @@
                 <el-checkbox
                   v-for="val in item.options?.data"
                   :key="val[item.options?.valueKey || 'value']"
-                  :label="val[item.options?.valueKey || 'value']"
+                  :value="val[item.options?.valueKey || 'value']"
                   >{{ val[item.options?.labelKey || 'label'] }}
                 </el-checkbox>
               </el-checkbox-group>

+ 1 - 5
src/components/ProTable/components/TableColumn.vue

@@ -29,11 +29,7 @@ const RenderTableColumn = (item: ColumnProps) => {
   return (
     <>
       {item.isShow && (
-        <el-table-column
-          {...item}
-          align={item.align ?? 'center'}
-          showOverflowTooltip={item.showOverflowTooltip ?? item.prop !== 'operation'}
-        >
+        <el-table-column {...item} align={item.align ?? 'center'} showOverflowTooltip={item.showOverflowTooltip ?? item.prop !== 'operation'}>
           {{
             default: (scope: RenderScope<any>) => {
               if (item._children) return item._children.map(child => RenderTableColumn(child))

+ 13 - 37
src/components/ProTable/index.vue

@@ -1,13 +1,6 @@
 <template>
   <!-- 查询表单 -->
-  <SearchForm
-    v-show="isShowSearch"
-    :search="_search"
-    :reset="_reset"
-    :columns="searchColumns"
-    :search-param="searchParam"
-    :search-col="searchCol"
-  />
+  <SearchForm v-show="isShowSearch" :search="_search" :reset="_reset" :columns="searchColumns" :search-param="searchParam" :search-col="searchCol" />
 
   <!-- 表格主体 -->
   <div class="card table-main">
@@ -20,24 +13,12 @@
         <slot name="toolButton">
           <el-button v-if="showToolButton('refresh')" :icon="Refresh" circle @click="getTableList" />
           <el-button v-if="showToolButton('setting') && columns.length" :icon="Operation" circle @click="openColSetting" />
-          <el-button
-            v-if="showToolButton('search') && searchColumns?.length"
-            :icon="Search"
-            circle
-            @click="isShowSearch = !isShowSearch"
-          />
+          <el-button v-if="showToolButton('search') && searchColumns?.length" :icon="Search" circle @click="isShowSearch = !isShowSearch" />
         </slot>
       </div>
     </div>
     <!-- 表格主体 -->
-    <el-table
-      ref="tableRef"
-      v-bind="$attrs"
-      :data="processTableData"
-      :border="border"
-      :row-key="rowKey"
-      @selection-change="selectionChange"
-    >
+    <el-table ref="tableRef" v-bind="$attrs" :data="processTableData" :border="border" :row-key="rowKey" @selection-change="selectionChange">
       <!-- 默认插槽 -->
       <slot />
       <template v-for="item in tableColumns" :key="item">
@@ -87,12 +68,7 @@
     </el-table>
     <!-- 分页组件 -->
     <slot name="pagination">
-      <Pagination
-        v-if="pagination"
-        :pageable="pageable"
-        :handle-size-change="handleSizeChange"
-        :handle-current-change="handleCurrentChange"
-      />
+      <Pagination v-if="pagination" :pageable="pageable" :handle-size-change="handleSizeChange" :handle-current-change="handleCurrentChange" />
     </slot>
   </div>
   <!-- 列设置 -->
@@ -165,8 +141,13 @@ const radio = ref('')
 const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey)
 
 // 表格操作 Hooks
-const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } =
-  useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError)
+const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(
+  props.requestApi,
+  props.initParam,
+  props.pagination,
+  props.dataCallback,
+  props.requestError
+)
 
 // 清空选中数据列表
 const clearSelection = () => tableRef.value!.clearSelection()
@@ -182,10 +163,7 @@ onMounted(() => {
 const processTableData = computed(() => {
   if (!props.data) return tableData.value
   if (!props.pagination) return props.data
-  return props.data.slice(
-    (pageable.value.pageNum - 1) * pageable.value.pageSize,
-    pageable.value.pageSize * pageable.value.pageNum
-  )
+  return props.data.slice((pageable.value.pageNum - 1) * pageable.value.pageSize, pageable.value.pageSize * pageable.value.pageNum)
 })
 
 // 监听页面 initParam 改化,重新获取表格数据
@@ -237,9 +215,7 @@ const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) =>
 
 // 过滤需要搜索的配置项 && 排序
 const searchColumns = computed(() => {
-  return flatColumns.value
-    ?.filter(item => item.search?.el || item.search?.render)
-    .sort((a, b) => a.search!.order! - b.search!.order!)
+  return flatColumns.value?.filter(item => item.search?.el || item.search?.render).sort((a, b) => a.search!.order! - b.search!.order!)
 })
 
 // 设置 搜索表单默认排序 && 搜索表单项的默认值

+ 1 - 2
src/components/ProTable/interface/index.ts

@@ -68,8 +68,7 @@ export type HeaderRenderScope<T> = {
   [key: string]: any
 }
 
-export interface ColumnProps<T = any>
-  extends Partial<Omit<TableColumnCtx<T>, 'type' | 'children' | 'renderCell' | 'renderHeader'>> {
+export interface ColumnProps<T = any> extends Partial<Omit<TableColumnCtx<T>, 'type' | 'children' | 'renderCell' | 'renderHeader'>> {
   type?: TypeProps // 列类型
   tag?: boolean | Ref<boolean> // 是否是标签展示
   isShow?: boolean | Ref<boolean> // 是否显示在表格当中

+ 1 - 3
src/components/SelectFilter/index.vue

@@ -11,9 +11,7 @@
             v-for="option in item.options"
             :key="option.value"
             :class="{
-              active:
-                option.value === selected[item.key] ||
-                (Array.isArray(selected[item.key]) && selected[item.key].includes(option.value))
+              active: option.value === selected[item.key] || (Array.isArray(selected[item.key]) && selected[item.key].includes(option.value))
             }"
             @click="select(item, option)"
           >

+ 122 - 0
src/components/TableDialog/index.vue

@@ -0,0 +1,122 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    :close-on-click-modal="false"
+    :title="parameter.title"
+    :destroy-on-close="true"
+    :append-to-body="true"
+    :width="parameter.width"
+    :top="parameter.top"
+    draggable
+  >
+    <ProTable
+      ref="proTable"
+      height="50vh"
+      :tool-button="parameter.toolButton"
+      :columns="parameter.columns"
+      :row-key="parameter.rowKey"
+      :request-api="parameter.getTableList"
+      :init-param="parameter.initParam"
+    />
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button type="primary" :loading="butLoading" @click="handleSubmit">确认</el-button>
+        <el-button @click="handleCancel">取消</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts" name="FormDialog">
+import { ref } from 'vue'
+import ProTable from '@/components/ProTable/index.vue'
+import { ElMessage } from 'element-plus'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+
+export interface TableParameterProps {
+  title?: string // 标题
+  width?: number // 弹框宽度
+  top?: string // 离顶部距离
+  toolButton?: boolean
+  initParam?: any // 初始化参数
+  apiParam?: (params?: any) => any
+  rowKey?: string
+  api?: (params: any) => Promise<any> // 表单提交api
+  columns?: ColumnProps[] // 动态表单字段
+  getTableList?: (params: any) => Promise<any> // 获取表格数据的Api
+  backTableList?: (params: any) => Promise<any> // 返回主表表格数据的Api
+}
+// dialog状态
+const dialogVisible = ref(false)
+const butLoading = ref(false)
+// 接受父组件参数,配置默认值
+// const parameter = ref<TableParameterProps>({
+//   title: '',
+//   width: 600,
+//   top: '5vh',
+//   toolButton: false,
+//   columns: [] as ColumnProps[]
+// })
+let parameter = withDefaults(defineProps<TableParameterProps>(), {
+  title: '',
+  width: 600,
+  top: '10vh',
+  rowKey: 'id',
+  toolButton: false,
+  initParam: {},
+  columns: () => []
+})
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 定义 emit 事件
+const emit = defineEmits<{
+  submitForm: []
+}>()
+// 表单提交校验
+const handleSubmit = () => {
+  butLoading.value = true
+  if (!proTable.value?.selectedList.length) {
+    ElMessage.warning('请选择数据')
+    butLoading.value = false
+    return
+  }
+  let paramObj: any = undefined
+  let params: any[] | undefined = []
+  if (parameter.apiParam) {
+    paramObj = parameter.apiParam(proTable.value?.selectedList)
+  } else {
+    params = proTable.value?.selectedList.map(item => item[parameter.rowKey])
+  }
+  parameter.api!(paramObj || params).then(res => {
+    if (res.code == 200) {
+      ElMessage.success('操作成功')
+      dialogVisible.value = false
+      emit('submitForm')
+    } else {
+      console.log('message', res.message)
+    }
+  })
+  butLoading.value = false
+}
+
+// 取消按钮,重置表单,关闭弹框
+const handleCancel = () => {
+  butLoading.value = false
+  dialogVisible.value = false
+}
+
+// 接收父组件参数
+const openDialog = (params: TableParameterProps) => {
+  parameter = { ...parameter, ...params }
+  butLoading.value = false
+  dialogVisible.value = true
+}
+
+defineExpose({
+  openDialog,
+  proTable,
+  handleCancel
+})
+</script>
+<style scoped lang="scss"></style>

+ 373 - 0
src/components/Upload/File.vue

@@ -0,0 +1,373 @@
+<template>
+  <div class="upload-file">
+    <el-upload
+      ref="uploadRef"
+      :action="uploadFileUrl"
+      v-model:file-list="_fileList"
+      class="upload-file-uploader"
+      :show-file-list="false"
+      :multiple="true"
+      :disabled="self_disabled"
+      :limit="limit"
+      :before-upload="beforeUpload"
+      :on-exceed="handleExceed"
+      :on-success="uploadSuccess"
+      :on-error="uploadError"
+      :accept="fileType.join(',')"
+      :headers="headers"
+    >
+      <el-button :icon="icon" type="primary">{{ text }}</el-button>
+    </el-upload>
+    <!-- 上传提示 -->
+    <div class="el-upload__tip" v-if="showTip">
+      请上传
+      <template v-if="fileSize">
+        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
+      </template>
+      <template v-if="fileType">
+        格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
+      </template>
+      的文件
+    </div>
+    <!-- 文件列表 -->
+    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
+      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in _fileList">
+        <el-link :href="`${baseUrl}${file.url}`" :underline="false" target="_blank">
+          <span class="document">
+            {{ getFileName(file.name) }}
+          </span>
+        </el-link>
+        <div class="ele-upload-list__item-content-action">
+          <el-link :underline="false" @click="handleRemove(index)" type="danger">删除</el-link>
+        </div>
+      </li>
+    </transition-group>
+  </div>
+</template>
+
+<script setup lang="ts" name="UploadImgs">
+import { ref, computed, inject, watch } from 'vue'
+import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
+import { ElNotification, ElMessage, formContextKey, formItemContextKey, UploadInstance } from 'element-plus'
+import { getToken } from '@/utils/token'
+interface UploadFileProps {
+  modelValue?: any
+  disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
+  drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
+  fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
+  height?: string // 组件高度 ==> 非必传(默认为 150px)
+  width?: string // 组件宽度 ==> 非必传(默认为 150px)
+  borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  isShowTip?: boolean // 是否显示提示信息 ==> 非必传(默认为 true)
+  text?: string // 按钮文字
+  icon?: string
+  fileList?: UploadUserFile[]
+  fileType?: Array<any>
+}
+// const emit = defineEmits(['update:modelValue'])
+const emit = defineEmits<{
+  'update:modelValue': [value: any]
+}>()
+const baseUrl = import.meta.env.VITE_API_URL
+const uploadFileUrl = ref(import.meta.env.VITE_API_URL + '/common/upload') // 上传文件服务器地址
+const headers = ref({ Authorization: 'Bearer ' + getToken() })
+const uploadRef = ref<UploadInstance>()
+const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
+const props = withDefaults(defineProps<UploadFileProps>(), {
+  fileList: () => [],
+  drag: true,
+  disabled: false,
+  limit: 1,
+  fileSize: 500,
+  fileType: () => ['doc', 'xls', 'xlsx', 'ppt', 'txt', 'pdf'],
+  height: '150px',
+  width: '150px',
+  borderRadius: '8px',
+  text: '文件上传',
+  isShowTip: false
+})
+
+// 获取 el-form 组件上下文
+const formContext = inject(formContextKey, void 0)
+// 获取 el-form-item 组件上下文
+const formItemContext = inject(formItemContextKey, void 0)
+// 判断是否禁用上传和删除
+const self_disabled = computed(() => {
+  return props.disabled || formContext?.disabled
+})
+
+const _fileList = ref<UploadUserFile[]>(props.fileList)
+
+// 获取文件名称
+const getFileName = name => {
+  if (name.lastIndexOf('/') > -1) {
+    return name.slice(name.lastIndexOf('/') + 1)
+  } else {
+    return ''
+  }
+}
+
+// 监听 props.fileList 列表默认值改变
+watch(
+  () => props.fileList,
+  (n: UploadUserFile[]) => {
+    _fileList.value = n
+  }
+)
+
+/**
+ * @description 文件上传之前判断
+ * @param rawFile 选择的文件
+ * */
+const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
+  // 校验文件格式
+  const fileName = rawFile.name.split('.')
+  const fileExt = fileName[fileName.length - 1]
+  const isTypeOk = props.fileType.indexOf(fileExt) >= 0
+  // 校检文件大小
+  const isLt = rawFile.size / 1024 / 1024 < props.fileSize
+
+  if (!isTypeOk)
+    ElNotification({
+      title: '温馨提示',
+      message: '上传文件不符合所需的格式!',
+      type: 'warning'
+    })
+  if (!isLt)
+    setTimeout(() => {
+      ElNotification({
+        title: '温馨提示',
+        message: `上传文件大小不能超过 ${props.fileSize}M!`,
+        type: 'warning'
+      })
+    }, 0)
+  return isTypeOk && isLt
+}
+
+/**
+ * @description 文件上传成功
+ * @param response 上传响应结果
+ * @param uploadFile 上传的文件
+ * */
+const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
+  if (response.code === 200) {
+    uploadFile.url = response.url
+    uploadFile.name = response.fileName
+    emit('update:modelValue', _fileList.value)
+    // 调用 el-form 内部的校验方法(可自动校验)
+    formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
+    ElNotification({
+      title: '温馨提示',
+      message: '文件上传成功!',
+      type: 'success'
+    })
+  } else {
+    ElMessage.error(response.msg)
+    uploadRef.value?.handleRemove(uploadFile)
+  }
+  //   code: 200
+  // fileName: "/profile/upload/2024/04/19/demo_20240419154324A003.txt"
+  // msg: "操作成功"
+  // newFileName: "demo_20240419154324A003.txt"
+  // originalFilename: "demo.txt"
+  // url: "http://localhost:8080/profile/upload/2024/04/19/demo_20240419154324A003.txt"
+  // uploadList.push({ name: res.fileName, url: res.fileName });
+}
+
+/**
+ * @description 删除图片
+ * @param file 删除的文件
+ * */
+const handleRemove = (index: number) => {
+  // _fileList.value = _fileList.value.filter(item => item.url !== file.url || item.name !== file.name)
+  _fileList.value.splice(index, 1)
+  emit('update:modelValue', _fileList.value)
+}
+
+/**
+ * @description 文件上传错误
+ * */
+const uploadError = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: '文件上传失败,请您重新上传!',
+    type: 'error'
+  })
+}
+
+/**
+ * @description 文件数超出
+ * */
+const handleExceed = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: `当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`,
+    type: 'warning'
+  })
+}
+
+/**
+ * @description 图片预览
+ * @param file 预览的文件
+ * */
+// const viewImageUrl = ref('')
+// const imgViewVisible = ref(false)
+// const handlePictureCardPreview: UploadProps['onPreview'] = file => {
+//   viewImageUrl.value = file.url!
+//   imgViewVisible.value = true
+// }
+</script>
+
+<style scoped lang="scss">
+// .upload-file-uploader {
+//   margin-bottom: 5px;
+// }
+// .upload-file-list .el-upload-list__item {
+//   position: relative;
+//   margin-bottom: 10px;
+//   line-height: 2;
+//   border: 1px solid #e4e7ed;
+// }
+// .upload-file-list .ele-upload-list__item-content {
+//   display: flex;
+//   align-items: center;
+//   justify-content: space-between;
+//   color: inherit;
+// }
+// .ele-upload-list__item-content-action .el-link {
+//   margin-right: 10px;
+// }
+// .is-error {
+//   .upload {
+//     :deep(.el-upload--picture-card),
+//     :deep(.el-upload-dragger) {
+//       border: 1px dashed var(--el-color-danger) !important;
+//       &:hover {
+//         border-color: var(--el-color-primary) !important;
+//       }
+//     }
+//   }
+// }
+// :deep(.disabled) {
+//   .el-upload--picture-card,
+//   .el-upload-dragger {
+//     cursor: not-allowed;
+//     background: var(--el-disabled-bg-color) !important;
+//     border: 1px dashed var(--el-border-color-darker);
+//     &:hover {
+//       border-color: var(--el-border-color-darker) !important;
+//     }
+//   }
+// }
+// .upload-box {
+//   .no-border {
+//     :deep(.el-upload--picture-card) {
+//       border: none !important;
+//     }
+//   }
+//   :deep(.upload) {
+//     .el-upload-dragger {
+//       display: flex;
+//       align-items: center;
+//       justify-content: center;
+//       width: 100%;
+//       height: 100%;
+//       padding: 0;
+//       overflow: hidden;
+//       border: 1px dashed var(--el-border-color-darker);
+//       border-radius: v-bind(borderRadius);
+//       &:hover {
+//         border: 1px dashed var(--el-color-primary);
+//       }
+//     }
+//     .el-upload-dragger.is-dragover {
+//       background-color: var(--el-color-primary-light-9);
+//       border: 2px dashed var(--el-color-primary) !important;
+//     }
+//     .el-upload-list__item,
+//     .el-upload--picture-card {
+//       width: v-bind(width);
+//       height: v-bind(height);
+//       background-color: transparent;
+//       border-radius: v-bind(borderRadius);
+//     }
+//     .upload-image {
+//       width: 100%;
+//       height: 100%;
+//       object-fit: contain;
+//     }
+//     .upload-handle {
+//       position: absolute;
+//       top: 0;
+//       right: 0;
+//       box-sizing: border-box;
+//       display: flex;
+//       align-items: center;
+//       justify-content: center;
+//       width: 100%;
+//       height: 100%;
+//       cursor: pointer;
+//       background: rgb(0 0 0 / 60%);
+//       opacity: 0;
+//       transition: var(--el-transition-duration-fast);
+//       .handle-icon {
+//         display: flex;
+//         flex-direction: column;
+//         align-items: center;
+//         justify-content: center;
+//         padding: 0 6%;
+//         color: aliceblue;
+//         .el-icon {
+//           margin-bottom: 15%;
+//           font-size: 140%;
+//         }
+//         span {
+//           font-size: 100%;
+//         }
+//       }
+//     }
+//     .el-upload-list__item {
+//       &:hover {
+//         .upload-handle {
+//           opacity: 1;
+//         }
+//       }
+//     }
+//     .upload-empty {
+//       display: flex;
+//       flex-direction: column;
+//       align-items: center;
+//       font-size: 12px;
+//       line-height: 30px;
+//       color: var(--el-color-info);
+//       .el-icon {
+//         font-size: 28px;
+//         color: var(--el-text-color-secondary);
+//       }
+//     }
+//   }
+//   .el-upload__tip {
+//     line-height: 15px;
+//     text-align: center;
+//   }
+// }
+.upload-file-uploader {
+  margin-bottom: 5px;
+}
+.upload-file-list .el-upload-list__item {
+  position: relative;
+  margin-bottom: 10px;
+  line-height: 2;
+  border: 1px solid #e4e7ed;
+}
+.upload-file-list .ele-upload-list__item-content {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  color: inherit;
+}
+.ele-upload-list__item-content-action .el-link {
+  margin-right: 10px;
+}
+</style>

+ 1 - 4
src/hooks/useTheme.ts

@@ -33,10 +33,7 @@ export const useTheme = () => {
     }
     // 计算主题颜色变化
     document.documentElement.style.setProperty('--el-color-primary', val)
-    document.documentElement.style.setProperty(
-      '--el-color-primary-dark-2',
-      isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`
-    )
+    document.documentElement.style.setProperty('--el-color-primary-dark-2', isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`)
     for (let i = 1; i <= 9; i++) {
       const primaryColor = isDark.value ? `${getDarkColor(val, i / 10)}` : `${getLightColor(val, i / 10)}`
       document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor)

+ 3 - 7
src/hooks/useTime.ts

@@ -20,15 +20,11 @@ export const useTime = () => {
     month.value = date.getMonth() + 1
     week.value = '日一二三四五六'.charAt(date.getDay())
     day.value = date.getDate()
-    hour.value =
-      (date.getHours() + '')?.padStart(2, '0') ||
-      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
+    hour.value = (date.getHours() + '')?.padStart(2, '0') || new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours())
     minute.value =
-      (date.getMinutes() + '')?.padStart(2, '0') ||
-      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
+      (date.getMinutes() + '')?.padStart(2, '0') || new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes())
     second.value =
-      (date.getSeconds() + '')?.padStart(2, '0') ||
-      new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getSeconds())
+      (date.getSeconds() + '')?.padStart(2, '0') || new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getSeconds())
     nowTime.value = `${year.value}年${month.value}月${day.value} ${hour.value}:${minute.value}:${second.value}`
   }
 

+ 1 - 7
src/layouts/LayoutClassic/index.vue

@@ -17,13 +17,7 @@
       <el-aside>
         <div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
           <el-scrollbar>
-            <el-menu
-              :router="false"
-              :default-active="activeMenu"
-              :collapse="isCollapse"
-              :unique-opened="accordion"
-              :collapse-transition="false"
-            >
+            <el-menu :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
               <SubMenu :menu-list="menuList" />
             </el-menu>
           </el-scrollbar>

+ 1 - 7
src/layouts/LayoutColumns/index.vue

@@ -27,13 +27,7 @@
         <span v-show="subMenuList.length" class="logo-text">{{ isCollapse ? 'G' : title }}</span>
       </div>
       <el-scrollbar>
-        <el-menu
-          :router="false"
-          :default-active="activeMenu"
-          :collapse="isCollapse"
-          :unique-opened="accordion"
-          :collapse-transition="false"
-        >
+        <el-menu :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
           <SubMenu :menu-list="subMenuList" />
         </el-menu>
       </el-scrollbar>

+ 1 - 7
src/layouts/LayoutVertical/index.vue

@@ -8,13 +8,7 @@
           <span v-show="!isCollapse" class="logo-text">{{ title }}</span>
         </div>
         <el-scrollbar>
-          <el-menu
-            :router="false"
-            :default-active="activeMenu"
-            :collapse="isCollapse"
-            :unique-opened="accordion"
-            :collapse-transition="false"
-          >
+          <el-menu :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
             <SubMenu :menu-list="menuList" />
           </el-menu>
         </el-scrollbar>

+ 1 - 6
src/layouts/components/Header/components/AssemblySize.vue

@@ -3,12 +3,7 @@
     <i :class="'iconfont icon-contentright'" class="toolBar-icon"></i>
     <template #dropdown>
       <el-dropdown-menu>
-        <el-dropdown-item
-          v-for="item in assemblySizeList"
-          :key="item.value"
-          :command="item.value"
-          :disabled="assemblySize === item.value"
-        >
+        <el-dropdown-item v-for="item in assemblySizeList" :key="item.value" :command="item.value" :disabled="assemblySize === item.value">
           {{ item.label }}
         </el-dropdown-item>
       </el-dropdown-menu>

+ 1 - 5
src/layouts/components/Header/components/Breadcrumb.vue

@@ -3,11 +3,7 @@
     <el-breadcrumb :separator-icon="ArrowRight">
       <transition-group name="breadcrumb">
         <el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.path">
-          <div
-            class="el-breadcrumb__inner is-link"
-            :class="{ 'item-no-icon': !item.meta.icon }"
-            @click="onBreadcrumbClick(item, index)"
-          >
+          <div class="el-breadcrumb__inner is-link" :class="{ 'item-no-icon': !item.meta.icon }" @click="onBreadcrumbClick(item, index)">
             <el-icon v-if="item.meta.icon && globalStore.breadcrumbIcon" class="breadcrumb-icon">
               <component :is="item.meta.icon"></component>
             </el-icon>

+ 1 - 6
src/layouts/components/Header/components/Language.vue

@@ -3,12 +3,7 @@
     <i :class="'iconfont icon-zhongyingwen'" class="toolBar-icon"></i>
     <template #dropdown>
       <el-dropdown-menu>
-        <el-dropdown-item
-          v-for="item in languageList"
-          :key="item.value"
-          :command="item.value"
-          :disabled="language === item.value"
-        >
+        <el-dropdown-item v-for="item in languageList" :key="item.value" :command="item.value" :disabled="language === item.value">
           {{ item.label }}
         </el-dropdown-item>
       </el-dropdown-menu>

+ 3 - 27
src/layouts/components/ThemeDrawer/index.vue

@@ -140,35 +140,11 @@ import SwitchDark from '@/components/SwitchDark/index.vue'
 const { changePrimary, changeGreyOrWeak, setAsideTheme, setHeaderTheme } = useTheme()
 
 const globalStore = useGlobalStore()
-const {
-  layout,
-  primary,
-  isGrey,
-  isWeak,
-  asideInverted,
-  headerInverted,
-  isCollapse,
-  accordion,
-  breadcrumb,
-  breadcrumbIcon,
-  tabs,
-  tabsIcon,
-  footer
-} = storeToRefs(globalStore)
+const { layout, primary, isGrey, isWeak, asideInverted, headerInverted, isCollapse, accordion, breadcrumb, breadcrumbIcon, tabs, tabsIcon, footer } =
+  storeToRefs(globalStore)
 
 // 预定义主题颜色
-const colorList = [
-  DEFAULT_PRIMARY,
-  '#daa96e',
-  '#0c819f',
-  '#409eff',
-  '#27ae60',
-  '#ff5c93',
-  '#e74c3c',
-  '#fd726d',
-  '#f39c12',
-  '#9b59b6'
-]
+const colorList = [DEFAULT_PRIMARY, '#daa96e', '#0c819f', '#409eff', '#27ae60', '#ff5c93', '#e74c3c', '#fd726d', '#f39c12', '#9b59b6']
 
 // 设置布局方式
 const setLayout = (val: LayoutType) => {

+ 19 - 4
src/routers/modules/routerData.json

@@ -3,11 +3,11 @@
   "data": [
     {
       "path": "/index",
-      "name": "home",
+      "name": "HomeIndex",
       "component": "index",
       "hidden": false,
       "meta": {
-        "icon": "HomeFilled",
+        "icon": "",
         "title": "首页",
         "link": "",
         "full": false,
@@ -21,7 +21,7 @@
       "component": "system/dict/data",
       "hidden": true,
       "meta": {
-        "icon": "data",
+        "icon": "",
         "title": "字典数据",
         "activeMenu": "/system/dict",
         "link": "",
@@ -36,7 +36,7 @@
       "component": "tool/gen/editGenTable",
       "hidden": true,
       "meta": {
-        "icon": "data",
+        "icon": "",
         "title": "编辑生成配置",
         "activeMenu": "/tool/gen",
         "link": "",
@@ -44,6 +44,21 @@
         "affix": false,
         "noCache": true
       }
+    },
+    {
+      "path": "/system/role-auth/user/:roleId",
+      "name": "AuthUser",
+      "component": "system/role/authUser",
+      "hidden": true,
+      "meta": {
+        "icon": "",
+        "title": "分配用户",
+        "activeMenu": "/system/role",
+        "link": "",
+        "full": false,
+        "affix": false,
+        "noCache": true
+      }
     }
   ],
   "msg": "成功"

+ 15 - 0
src/typings/ProForm.d.ts

@@ -24,6 +24,7 @@ declare namespace ProForm {
     | 'cascader'
     | 'radio-group'
     | 'radio-button'
+    | 'file-upload'
     | 'slot'
     | 'rate'
     | 'slider'
@@ -55,6 +56,7 @@ declare namespace ProForm {
     value?: any // 默认值
     tooltip?: string // 问号,tooltip提示
     required?: boolean
+    hideLabelSuffix?: boolean // label后缀是否隐藏
     rules?: FormItemRule[]
     span?: number // 表单col宽度
     show?: (params?: any) => Promise<any> | boolean | string // 是否显示 默认显示
@@ -83,6 +85,7 @@ declare namespace ProForm {
     clearable?: boolean // 是否可清空
     showPassword?: boolean // 是否显示切换密码图标
     enum?: EnumProps[] | Ref<EnumProps[]> | ((params?: any) => Promise<any>) // 枚举字典
+    onChange?: (value: any) => void
     enumKey?: string
     labelKey?: string
     valueKey?: string
@@ -90,6 +93,7 @@ declare namespace ProForm {
     placeholder?: string
     data?: Record<string, any>[]
     multiple?: boolean
+    disabled?: boolean
     elTagName?: ElTagName
     type?: string
     rangeSeparator?: string // 时间范围分隔符
@@ -109,5 +113,16 @@ declare namespace ProForm {
     checkStrictly?: boolean // 可选
     renderAfterExpand?: boolean // 可选
     controlsPosition?: 'left' | 'right' // 可选
+    onChange?: (value: any) => void
+    onSelect?: (value: any) => void
+    onRemove?: (value: any) => void
+    onClear?: () => void
+    onFocus?: () => void
+    onBlur?: () => void
+    onInput?: (value: any) => void
+    onSearch?: (value: any) => void
+    onVisibleChange?: (value: any) => void
+    onExpand?: (value: any) => void
+    onCheck?: (value: any) => void
   }
 }

+ 1 - 3
src/views/login/components/LoginForm.vue

@@ -26,9 +26,7 @@
   </el-form>
   <div class="login-btn">
     <el-button :icon="CircleClose" round size="large" @click="resetForm(loginFormRef)"> 重置 </el-button>
-    <el-button :icon="UserFilled" round size="large" type="primary" :loading="loading" @click="handleLogin(loginFormRef)">
-      登录
-    </el-button>
+    <el-button :icon="UserFilled" round size="large" type="primary" :loading="loading" @click="handleLogin(loginFormRef)"> 登录 </el-button>
   </div>
 </template>
 

+ 1 - 2
src/views/monitor/logininfor/index.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" row-key="infoId" :request-api="listLogininforApi" :init-param="initParam">
-    </ProTable>
+    <ProTable ref="proTable" :columns="columns" row-key="infoId" :request-api="listLogininforApi" :init-param="initParam"> </ProTable>
     <FormDialog ref="formDialogRef" />
     <ImportExcel ref="dialogRef" />
   </div>

+ 1 - 2
src/views/monitor/operlog/index.vue

@@ -1,7 +1,6 @@
 <template>
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" row-key="operId" :request-api="listOperlogApi" :init-param="initParam">
-    </ProTable>
+    <ProTable ref="proTable" :columns="columns" row-key="operId" :request-api="listOperlogApi" :init-param="initParam"> </ProTable>
   </div>
 </template>
 

+ 5 - 28
src/views/system/dept/index.vue

@@ -11,38 +11,15 @@
     >
       <!-- 表格 header 按钮 -->
       <template #tableHeader>
-        <el-button type="primary" v-auth="['system:dept:add']" :icon="CirclePlus" @click="openDialog(1, '部门新增')">
-          新增
-        </el-button>
+        <el-button type="primary" v-auth="['system:dept:add']" :icon="CirclePlus" @click="openDialog(1, '部门新增')"> 新增 </el-button>
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system:dept:edit']"
-          @click="openDialog(2, '部门编辑', scope.row)"
-        >
-          编辑
-        </el-button>
-        <el-button
-          type="primary"
-          link
-          :icon="CirclePlus"
-          v-auth="['system:dept:add']"
-          @click="openDialog(4, '部门新增', scope.row)"
-        >
+        <el-button type="primary" link :icon="EditPen" v-auth="['system:dept:edit']" @click="openDialog(2, '部门编辑', scope.row)"> 编辑 </el-button>
+        <el-button type="primary" link :icon="CirclePlus" v-auth="['system:dept:add']" @click="openDialog(4, '部门新增', scope.row)">
           新增
         </el-button>
-        <el-button
-          v-if="scope.row.parentId != 0"
-          type="primary"
-          link
-          :icon="Delete"
-          v-auth="['system:dept:remove']"
-          @click="deleteDept(scope.row)"
-        >
+        <el-button v-if="scope.row.parentId != 0" type="primary" link :icon="Delete" v-auth="['system:dept:remove']" @click="deleteDept(scope.row)">
           删除
         </el-button>
       </template>
@@ -52,7 +29,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="Dept">
+<script setup lang="tsx" name="DeptManage">
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'

+ 6 - 30
src/views/system/dict/data.vue

@@ -4,9 +4,7 @@
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
         <el-button type="primary" v-auth="['system/dict:data:add']" @click="openDialog(1, '字典数据新增')">新增</el-button>
-        <el-button type="primary" v-auth="['system/dict:data:export']" :icon="Download" plain @click="downloadFile">
-          导出
-        </el-button>
+        <el-button type="primary" v-auth="['system/dict:data:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
         <el-button
           type="danger"
           v-auth="['system:user:remove']"
@@ -20,27 +18,13 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="View"
-          v-auth="['system/dict:data:query']"
-          @click="openDialog(3, '字典数据查看', scope.row)"
-        >
+        <el-button type="primary" link :icon="View" v-auth="['system/dict:data:query']" @click="openDialog(3, '字典数据查看', scope.row)">
           查看
         </el-button>
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system/dict:data:edit']"
-          @click="openDialog(2, '字典数据编辑', scope.row)"
-        >
+        <el-button type="primary" link :icon="EditPen" v-auth="['system/dict:data:edit']" @click="openDialog(2, '字典数据编辑', scope.row)">
           编辑
         </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['system/dict:data:remove']" @click="deleteDictData(scope.row)">
-          删除
-        </el-button>
+        <el-button type="primary" link :icon="Delete" v-auth="['system/dict:data:remove']" @click="deleteDictData(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -48,7 +32,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="Data">
+<script setup lang="tsx" name="DictData">
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
@@ -58,15 +42,7 @@ import ImportExcel from '@/components/ImportExcel/index.vue'
 import FormDialog from '@/components/DialogOld/form.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { Delete, EditPen, Download, View } from '@element-plus/icons-vue'
-import {
-  listDataApi,
-  delDataApi,
-  addDataApi,
-  updateDataApi,
-  getDataApi,
-  exportApi,
-  getDictsApi
-} from '@/api/modules/system/dictData'
+import { listDataApi, delDataApi, addDataApi, updateDataApi, getDataApi, exportApi, getDictsApi } from '@/api/modules/system/dictData'
 import { getDictApi } from '@/api/modules/system/dict'
 
 import { useRoute } from 'vue-router'

+ 3 - 11
src/views/system/dict/index.vue

@@ -18,18 +18,10 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system:dict:edit']"
-          @click="openDialog(2, '字典类型编辑', scope.row)"
-        >
+        <el-button type="primary" link :icon="EditPen" v-auth="['system:dict:edit']" @click="openDialog(2, '字典类型编辑', scope.row)">
           编辑
         </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['system:dict:remove']" @click="deleteDict(scope.row)">
-          删除
-        </el-button>
+        <el-button type="primary" link :icon="Delete" v-auth="['system:dict:remove']" @click="deleteDict(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -37,7 +29,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="Dict">
+<script setup lang="tsx" name="DictManage">
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'

+ 6 - 18
src/views/system/menu/index.vue

@@ -15,35 +15,23 @@
         <el-button type="primary" v-auth="['system:menu:add']" @click="openDialog(1, '菜单新增')">新增</el-button>
       </template>
       <!-- 菜单图标 -->
-      <!-- <template #icon="scope">
+      <template #icon="scope">
         <el-icon :size="18">
           <component :is="scope.row.icon"></component>
         </el-icon>
-      </template> -->
+      </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system:menu:edit']"
-          @click="openDialog(2, '菜单编辑', scope.row)"
-        >
-          编辑
-        </el-button>
-        <el-button type="primary" link :icon="CirclePlus" v-auth="['system:menu:add']" @click="openDialog(1, '菜单新增')">
-          新增
-        </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['system:menu:remove']" @click="deleteMenu(scope.row)">
-          删除
-        </el-button>
+        <el-button type="primary" link :icon="EditPen" v-auth="['system:menu:edit']" @click="openDialog(2, '菜单编辑', scope.row)"> 编辑 </el-button>
+        <el-button type="primary" link :icon="CirclePlus" v-auth="['system:menu:add']" @click="openDialog(1, '菜单新增')"> 新增 </el-button>
+        <el-button type="primary" link :icon="Delete" v-auth="['system:menu:remove']" @click="deleteMenu(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
   </div>
 </template>
 
-<script setup lang="tsx" name="Menu">
+<script setup lang="tsx" name="MenuManage">
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'

+ 5 - 21
src/views/system/post/index.vue

@@ -3,9 +3,7 @@
     <ProTable ref="proTable" :columns="columns" row-key="postId" :request-api="listPostApi">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['system:post:add']" :icon="CirclePlus" @click="openDialog(1, '岗位信息新增')">
-          新增
-        </el-button>
+        <el-button type="primary" v-auth="['system:post:add']" :icon="CirclePlus" @click="openDialog(1, '岗位信息新增')"> 新增 </el-button>
         <el-button type="primary" v-auth="['system:post:import']" :icon="Upload" plain @click="batchAdd"> 导入 </el-button>
         <el-button type="primary" v-auth="['system:post:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
         <el-button
@@ -21,27 +19,13 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="View"
-          v-auth="['system:post:query']"
-          @click="openDialog(3, '岗位信息查看', scope.row)"
-        >
+        <el-button type="primary" link :icon="View" v-auth="['system:post:query']" @click="openDialog(3, '岗位信息查看', scope.row)">
           查看
         </el-button>
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system:post:edit']"
-          @click="openDialog(2, '岗位信息编辑', scope.row)"
-        >
+        <el-button type="primary" link :icon="EditPen" v-auth="['system:post:edit']" @click="openDialog(2, '岗位信息编辑', scope.row)">
           编辑
         </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['system:post:remove']" @click="deletePost(scope.row)">
-          删除
-        </el-button>
+        <el-button type="primary" link :icon="Delete" v-auth="['system:post:remove']" @click="deletePost(scope.row)"> 删除 </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef" />
@@ -49,7 +33,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="Post">
+<script setup lang="tsx" name="PostManage">
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'

+ 0 - 296
src/views/system/role/authRole.vue

@@ -1,296 +0,0 @@
-<template>
-  <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi">
-      <!-- 表格 header 按钮 -->
-      <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="openDialog(1, '角色信息新增')">
-          新增
-        </el-button>
-        <el-button type="primary" v-auth="['system:role:import']" :icon="Upload" plain @click="batchAdd"> 导入 </el-button>
-        <el-button type="primary" v-auth="['system:role:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
-        <el-button
-          type="danger"
-          v-auth="['system:user:remove']"
-          :icon="Delete"
-          plain
-          :disabled="!scope.isSelected"
-          @click="batchDelete(scope.selectedListIds)"
-        >
-          批量删除
-        </el-button>
-      </template>
-      <!-- 表格操作 -->
-      <template #operation="scope">
-        <el-button
-          type="primary"
-          link
-          :icon="View"
-          v-auth="['system:role:query']"
-          @click="openDialog(3, '角色信息查看', scope.row)"
-        >
-          查看
-        </el-button>
-        <el-button
-          type="primary"
-          link
-          :icon="EditPen"
-          v-auth="['system:role:edit']"
-          @click="openDialog(2, '角色信息编辑', scope.row)"
-        >
-          编辑
-        </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['system:role:remove']" @click="deleteRole(scope.row)">
-          删除
-        </el-button>
-      </template>
-    </ProTable>
-    <FormDialog ref="formDialogRef" />
-    <ImportExcel ref="dialogRef" />
-  </div>
-</template>
-
-<script setup lang="tsx" name="Role">
-import { ref, reactive } from 'vue'
-import { useHandleData } from '@/hooks/useHandleData'
-import { useDownload } from '@/hooks/useDownload'
-import { ElMessageBox } from 'element-plus'
-import ProTable from '@/components/ProTable/index.vue'
-import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
-import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, Download, Upload, View, CirclePlus } from '@element-plus/icons-vue'
-import {
-  listRoleApi,
-  delRoleApi,
-  addRoleApi,
-  updateRoleApi,
-  importTemplateApi,
-  importDataApi,
-  exportApi,
-  getRoleApi
-} from '@/api/modules/system/role'
-
-// ProTable 实例
-const proTable = ref<ProTableInstance>()
-
-// 删除角色信息信息
-const deleteRole = async (params: any) => {
-  await useHandleData(delRoleApi, params.roleId, `删除【${params.roleId}】角色信息`)
-  proTable.value?.getTableList()
-}
-
-// 批量删除角色信息信息
-const batchDelete = async (ids: string[]) => {
-  await useHandleData(delRoleApi, ids, '删除所选角色信息信息')
-  proTable.value?.clearSelection()
-  proTable.value?.getTableList()
-}
-
-// 导出角色信息列表
-const downloadFile = async () => {
-  ElMessageBox.confirm('确认导出角色信息数据?', '温馨提示', { type: 'warning' }).then(() =>
-    useDownload(exportApi, '角色信息列表', proTable.value?.searchParam)
-  )
-}
-
-// 批量添加角色信息
-const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
-const batchAdd = () => {
-  const params = {
-    title: '角色信息',
-    tempApi: importTemplateApi,
-    importApi: importDataApi,
-    getTableList: proTable.value?.getTableList
-  }
-  dialogRef.value?.acceptParams(params)
-}
-
-const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
-// 打开弹框的功能
-const openDialog = async (type: number, title: string, row?: any) => {
-  let res = { data: {} }
-  if (row?.roleId) {
-    res = await getRoleApi(row?.roleId || null)
-  }
-  // 重置表单
-  setFieldList()
-  const params = {
-    title,
-    width: 580,
-    isEdit: type !== 3,
-    fieldList: fieldList,
-    model: type == 1 ? {} : res.data,
-    api: type == 1 ? addRoleApi : updateRoleApi,
-    getTableList: proTable.value?.getTableList
-  }
-  formDialogRef.value?.openDialog(params)
-}
-
-// 表格配置项
-const columns = reactive<ColumnProps<any>[]>([
-  { type: 'selection', fixed: 'left', width: 70 },
-  { prop: 'roleId', label: '角色ID' },
-  {
-    prop: 'roleName',
-    label: '角色名称',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'roleKey',
-    label: '角色权限字符串',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'roleSort',
-    label: '显示顺序',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'dataScope',
-    label: '数据范围',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'menuCheckStrictly',
-    label: '菜单树选择项是否关联显示',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'deptCheckStrictly',
-    label: '部门树选择项是否关联显示',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'status',
-    label: '角色状态',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'delFlag',
-    label: '删除标志',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'createBy',
-    label: '创建者',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'createTime',
-    label: '创建时间',
-    search: {
-      el: 'date-picker',
-      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
-    },
-    width: 120
-  },
-  {
-    prop: 'updateBy',
-    label: '更新者',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  {
-    prop: 'updateTime',
-    label: '更新时间',
-    search: {
-      el: 'date-picker',
-      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
-    },
-    width: 120
-  },
-  {
-    prop: 'remark',
-    label: '备注',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
-  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
-])
-// 表单配置项
-let fieldList: Form.FieldItem[] = []
-const setFieldList = () => {
-  fieldList = [
-    {
-      label: '角色名称',
-      field: 'roleName',
-      rules: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
-      placeholder: '请输入角色名称'
-    },
-    {
-      label: '角色权限字符串',
-      field: 'roleKey',
-      rules: [{ required: true, message: '角色权限字符串不能为空', trigger: 'blur' }],
-      placeholder: '请输入角色权限字符串'
-    },
-    {
-      label: '显示顺序',
-      field: 'roleSort',
-      rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
-      placeholder: '请输入显示顺序'
-    },
-    {
-      label: '数据范围',
-      field: 'dataScope',
-      placeholder: '请输入数据范围'
-    },
-    {
-      label: '菜单树选择项是否关联显示',
-      field: 'menuCheckStrictly',
-      placeholder: '请输入菜单树选择项是否关联显示'
-    },
-    {
-      label: '部门树选择项是否关联显示',
-      field: 'deptCheckStrictly',
-      placeholder: '请输入部门树选择项是否关联显示'
-    },
-    {
-      label: '角色状态',
-      field: 'status',
-      rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
-      placeholder: '请输入角色状态'
-    },
-    {
-      label: '删除标志',
-      field: 'delFlag',
-      placeholder: '请输入删除标志'
-    },
-    {
-      label: '备注',
-      field: 'remark',
-      placeholder: '请输入备注'
-    }
-  ]
-}
-</script>

+ 179 - 0
src/views/system/role/authUser.vue

@@ -0,0 +1,179 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="userId" :request-api="allocatedUserListApi" :init-param="initParam">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="addUser"> 添加用户 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['system:role:import']"
+          :disabled="!scope.isSelected"
+          :icon="CircleClose"
+          plain
+          @click="cancelAuthUserAll(scope.selectedListIds)"
+        >
+          批量取消授权
+        </el-button>
+        <el-button type="warning" v-auth="['system:role:export']" :icon="Close" plain @click="closeCurrentTab"> 关闭 </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link :icon="CircleClose" v-auth="['system:role:query']" @click="cancelAuthUser(scope.row)"> 取消授权 </el-button>
+      </template>
+    </ProTable>
+    <TableDialog ref="tableDialogRef" @submit-form="submitForm" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="Role">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import ProTable from '@/components/ProTable/index.vue'
+import TableDialog from '@/components/TableDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import { CircleClose, Close, CirclePlus } from '@element-plus/icons-vue'
+import {
+  allocatedUserListApi,
+  unallocatedUserListApi,
+  authUserSelectAllApi,
+  authUserCancelApi,
+  authUserCancelAllApi
+} from '@/api/modules/system/role'
+import { useRoute } from 'vue-router'
+import { useTabsStore } from '@/stores/modules/tabs'
+import { getDictsApi } from '@/api/modules/system/dictData'
+const tabStore = useTabsStore()
+const route = useRoute()
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 如果表格需要初始化请求参数
+const initParam = reactive({ roleId: route.params.roleId })
+
+// 批量取消用户授权
+const cancelAuthUserAll = async (ids: string[]) => {
+  await useHandleData(authUserCancelAllApi, { userIds: ids, roleId: route.params.roleId }, '删除所选角色信息信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+// 取消用户授权
+const cancelAuthUser = async (params: any) => {
+  await useHandleData(authUserCancelApi, { userId: params.userId, roleId: route.params.roleId }, `删除【${params.userName}】角色信息`)
+  proTable.value?.getTableList()
+}
+
+// 批量添加
+const tableDialogRef = ref<InstanceType<typeof TableDialog> | null>(null)
+const addUser = () => {
+  const params = {
+    title: '选择用户',
+    width: 880,
+    toolButton: false,
+    columns: subColumns,
+    initParam: { roleId: route.params.roleId },
+    apiParam: val => {
+      const userIds = val.map((item: any) => item.userId)
+      return {
+        userIds,
+        roleId: route.params.roleId
+      }
+    },
+    rowKey: 'userId',
+    api: authUserSelectAllApi,
+    getTableList: unallocatedUserListApi,
+    backTableList: allocatedUserListApi
+  }
+  tableDialogRef.value?.openDialog(params)
+}
+
+// 表格弹框提交
+const submitForm = () => {
+  proTable.value?.getTableList()
+}
+
+const closeCurrentTab = () => {
+  if (route.meta.affix) return
+  tabStore.removeTabs(route.fullPath)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  {
+    prop: 'userName',
+    label: '用户名称',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'nickName',
+    label: '角色昵称'
+  },
+  {
+    prop: 'email',
+    label: '邮箱'
+  },
+  {
+    prop: 'phonenumber',
+    label: '手机',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'status',
+    label: '状态',
+    tag: true,
+    enum: () => getDictsApi('sys_normal_disable'),
+    fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  },
+  {
+    prop: 'createTime',
+    label: '创建时间',
+    search: {
+      el: 'date-picker',
+      props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+
+// 表格配置项
+const subColumns: ColumnProps[] = [
+  { type: 'selection', fixed: 'left', width: 60 },
+  {
+    label: '用户名称',
+    prop: 'userName',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    label: '用户昵称',
+    prop: 'nickName'
+  },
+  {
+    label: '邮箱',
+    prop: 'email'
+  },
+  {
+    label: '手机',
+    prop: 'phonenumber'
+  },
+  {
+    prop: 'status',
+    label: '状态',
+    tag: true,
+    enum: () => getDictsApi('sys_normal_disable'),
+    fieldNames: { label: 'dictLabel', value: 'dictValue' }
+  },
+  {
+    prop: 'createTime',
+    label: '创建时间'
+  }
+]
+</script>

+ 8 - 0
src/views/system/role/index.scss

@@ -0,0 +1,8 @@
+/* tree border */
+.tree-border {
+  width: 100%;
+  margin-top: 5px;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 4px;
+}

+ 364 - 67
src/views/system/role/index.vue

@@ -1,11 +1,9 @@
 <template>
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi" :init-param="initParam">
+    <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="openDialog(1, '角色信息新增')">
-          新增
-        </el-button>
+        <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="openDialog(1, '角色信息新增')"> 新增 </el-button>
         <el-button type="primary" v-auth="['system:role:import']" :icon="Upload" plain @click="batchAdd"> 导入 </el-button>
         <el-button type="primary" v-auth="['system:role:export']" :icon="Download" plain @click="downloadFile"> 导出 </el-button>
         <el-button
@@ -23,43 +21,98 @@
       <template #operation="scope">
         <el-button
           type="primary"
+          v-if="scope.row.roleId !== 1"
           link
           :icon="EditPen"
           v-auth="['system:role:edit']"
-          @click="openDialog(2, '角色信息编辑', scope.row)"
+          @click="openDialog(2, '角色编辑', scope.row)"
         >
           编辑
         </el-button>
+        <el-button type="primary" v-if="scope.row.roleId !== 1" link :icon="Delete" v-auth="['system:role:remove']" @click="deleteRole(scope.row)">
+          删除
+        </el-button>
         <el-button
           type="primary"
+          v-if="scope.row.roleId !== 1"
           link
-          :icon="View"
-          v-auth="['system:role:query']"
-          @click="openDialog(3, '角色信息查看', scope.row)"
+          :icon="CircleCheck"
+          v-auth="['system:role:edit']"
+          @click="openDataDialog(3, '数据权限', scope.row)"
         >
-          查看
+          数据权限
         </el-button>
-
-        <el-button type="primary" link :icon="Delete" v-auth="['system:role:remove']" @click="deleteRole(scope.row)">
-          删除
+        <el-button type="primary" v-if="scope.row.roleId !== 1" link :icon="User" v-auth="['system:role:edit']" @click="handleAuthUser(scope.row)">
+          分配用户
         </el-button>
       </template>
     </ProTable>
-    <FormDialog ref="formDialogRef" />
+    <FormDialog ref="formDialogRef">
+      <template #default="{ parameter }">
+        <ProFrom ref="proFormRef" :items-options="itemsOptions" :disabled="!parameter.isEdit" :form-options="_options" :model="parameter.model">
+          <template #menuCheckStrictly="{ formModel }">
+            <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
+            <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
+            <el-checkbox v-model="formModel.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')"> 父子联动 </el-checkbox>
+            <el-tree
+              class="tree-border"
+              :data="menuOptions"
+              show-checkbox
+              ref="menuRef"
+              node-key="id"
+              :check-strictly="!formModel.menuCheckStrictly"
+              empty-text="加载中,请稍后"
+              :props="{ label: 'label', children: 'children' }"
+            />
+          </template>
+        </ProFrom>
+      </template>
+      <template #footer="{ parameter }">
+        <el-button type="primary" :loading="butLoading" @click="handleSubmit(proFormRef, parameter, formDialogRef, '1')">确认</el-button>
+        <el-button @click="handleCancel(formDialogRef)">取消</el-button>
+      </template>
+    </FormDialog>
+    <FormDialog ref="formDataRef">
+      <template #default="{ parameter }">
+        <ProFrom ref="proFormDateRef" :items-options="itemsOptions" :form-options="_options" :model="parameter.model">
+          <template #deptCheckStrictly="{ formModel }">
+            <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
+            <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
+            <el-checkbox v-model="formModel.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')"> 父子联动 </el-checkbox>
+            <el-tree
+              class="tree-border"
+              :data="deptOptions"
+              show-checkbox
+              ref="deptRef"
+              default-expand-all
+              node-key="id"
+              :check-strictly="!formModel.deptCheckStrictly"
+              empty-text="加载中,请稍后"
+              :props="{ label: 'label', children: 'children' }"
+            />
+          </template>
+        </ProFrom>
+      </template>
+      <template #footer="{ parameter }">
+        <el-button type="primary" :loading="butLoading" @click="handleSubmit(proFormDateRef, parameter, formDataRef)">确认</el-button>
+        <el-button @click="handleCancel(formDataRef)">取消</el-button>
+      </template>
+    </FormDialog>
     <ImportExcel ref="dialogRef" />
   </div>
 </template>
 
-<script setup lang="tsx" name="Role">
-import { ref, reactive } from 'vue'
+<script setup lang="tsx" name="RoleManage">
+import { ref, reactive, nextTick } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
-import { ElMessageBox } from 'element-plus'
+import { ElMessageBox, ElMessage } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/FormDialog/index.vue'
+import FormDialog from '@/components/CustomDialog/index.vue'
+import ProFrom from '@/components/ProForm/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, Download, Upload, View, CirclePlus } from '@element-plus/icons-vue'
+import { Delete, EditPen, Download, Upload, CircleCheck, User, CirclePlus } from '@element-plus/icons-vue'
 import {
   listRoleApi,
   delRoleApi,
@@ -68,18 +121,71 @@ import {
   importTemplateApi,
   importDataApi,
   exportApi,
-  getRoleApi
+  changeStatusApi,
+  deptTreeSelectApi,
+  dataScopeApi,
+  roleMenuTreeSelectApi
 } from '@/api/modules/system/role'
+import { treeSelectApi } from '@/api/modules/system/menu'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+// 菜单列表
+const menuOptions = ref<any>()
+const menuExpand = ref(false)
+const menuNodeAll = ref(false)
+const deptExpand = ref(true)
+const deptNodeAll = ref(false)
+const menuRef = ref<any>(null)
+const proFormRef = ref<any>(null)
+const proFormDateRef = ref<any>(null)
+const deptOptions = ref<any[]>([])
+const deptRef = ref<any>(null)
+const butLoading = ref(false)
+// 数据范围选项
+const dataScopeOptions = ref([
+  { value: '1', label: '全部数据权限' },
+  { value: '2', label: '自定数据权限' },
+  { value: '3', label: '本部门数据权限' },
+  { value: '4', label: '本部门及以下数据权限' },
+  { value: '5', label: '仅本人数据权限' }
+])
+
+// 获取菜单
+const getMenuTreeSelect = () => {
+  treeSelectApi().then((response: any) => {
+    if (response.code === 200) {
+      menuOptions.value = response.data
+    }
+  })
+}
+getMenuTreeSelect()
+
+// 根据角色ID查询菜单树结构
+const getRoleMenuTreeSelect = (roleId: any) => {
+  return roleMenuTreeSelectApi(roleId).then((res: any) => {
+    menuOptions.value = res.data.menus
+    return res
+  })
+}
+
+// 根据角色ID查询部门树结构
+const getDeptTree = (roleId: string) => {
+  return deptTreeSelectApi(roleId).then((res: any) => {
+    if (res.code === 200) {
+      deptOptions.value = res.data.depts
+      return res
+    }
+  })
+}
 
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 
-// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({ type: 1 })
-
 // 删除角色信息信息
 const deleteRole = async (params: any) => {
-  await useHandleData(delRoleApi, params.roleId, `删除编号为【${params.roleId}】角色信息`)
+  await useHandleData(delRoleApi, params.roleId, `删除【${params.roleName}】角色信息`)
   proTable.value?.getTableList()
 }
 
@@ -112,22 +218,168 @@ const batchAdd = () => {
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
-  let res = { data: {} }
+  reset()
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 720,
+    isEdit: true,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : row,
+    api: type == 1 ? addRoleApi : updateRoleApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
   if (row?.roleId) {
-    res = await getRoleApi(row?.roleId || null)
+    getRoleMenuTreeSelect(row?.roleId).then(res => {
+      let checkedKeys = res.data.checkedKeys
+      checkedKeys.forEach((v: any) => {
+        nextTick(() => {
+          menuRef.value.setChecked(v, true, false)
+        })
+      })
+    })
   }
+}
+const formDataRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDataDialog = async (type: number, title: string, row?: any) => {
+  reset()
   // 重置表单
-  setFormItems()
+  setPerDataItemsOptions()
   const params = {
     title,
-    width: 580,
-    isEdit: type !== 3, // 是否编辑
-    itemsOptions: formItems,
-    model: type == 1 ? {} : res.data, // 表单数据
-    api: type == 1 ? addRoleApi : updateRoleApi, // 提交api
-    getTableList: proTable.value?.getTableList // 刷新表格
+    width: 720,
+    isEdit: true,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : row,
+    api: dataScopeApi,
+    getTableList: proTable.value?.getTableList
   }
-  formDialogRef.value?.openDialog(params)
+
+  formDataRef.value?.openDialog(params)
+  if (row?.roleId) {
+    nextTick(() => {
+      getDeptTree(row?.roleId).then(res => {
+        nextTick(() => {
+          if (deptRef.value) {
+            deptRef.value.setCheckedKeys(res.data.checkedKeys)
+          }
+        })
+      })
+    })
+  }
+}
+
+// 树权限(展开/折叠
+const handleCheckedTreeExpand = (value: any, type: string) => {
+  if (type === 'menu') {
+    const nodes = menuRef.value.store._getAllNodes()
+    nodes.forEach(item => {
+      item.expanded = value
+    })
+  } else if (type === 'dept') {
+    const nodes = deptRef.value.store._getAllNodes()
+    nodes.forEach(item => {
+      item.expanded = value
+    })
+  }
+}
+
+// 树权限(全选/全不选)
+const handleCheckedTreeNodeAll = (value: any, type: string) => {
+  if (type === 'menu') {
+    menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
+  } else if (type === 'dept') {
+    deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
+  }
+}
+
+// 树权限(父子联动)
+const handleCheckedTreeConnect = (value: any, type: string) => {
+  if (type === 'menu') {
+    proFormRef.value.formModel.menuCheckStrictly = value ? true : false
+  } else if (type === 'dept') {
+    proFormDateRef.value.formModel.deptCheckStrictly = value ? true : false
+  }
+}
+
+// 分配用户
+function handleAuthUser(row: any) {
+  router.push('/system/role-auth/user/' + row.roleId)
+}
+
+// 切换绝俗状态
+const changeStatus = async (row: any) => {
+  await useHandleData(
+    changeStatusApi,
+    { roleId: row.roleId, version: row.version, status: row.status == '1' ? 0 : 1 },
+    `切换【${row.roleName}】角色状态`
+  )
+  proTable.value?.getTableList()
+}
+
+// 所有部门节点数据
+const getDeptAllCheckedKeys = () => {
+  // 目前被选中的部门节点
+  let checkedKeys = deptRef.value.getCheckedKeys()
+  // 半选中的部门节点
+  let halfCheckedKeys = deptRef.value.getHalfCheckedKeys()
+  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
+  return checkedKeys
+}
+
+// 所有菜单节点数据
+const getMenuAllCheckedKeys = () => {
+  // 目前被选中的菜单节点
+  let checkedKeys = menuRef.value.getCheckedKeys()
+  // 半选中的菜单节点
+  let halfCheckedKeys = menuRef.value.getHalfCheckedKeys()
+  checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
+  return checkedKeys
+}
+// 提交
+const handleSubmit = (proFormRef: any, parameter: any, closeRefVal: any, type?: string) => {
+  const formEl = proFormRef.proFormRef
+  butLoading.value = true
+  if (!formEl) return
+  formEl.validate(valid => {
+    if (valid) {
+      if (type === '1') {
+        parameter.model.menuIds = getMenuAllCheckedKeys()
+      } else {
+        parameter.model.deptIds = getDeptAllCheckedKeys()
+      }
+      parameter.api!(parameter.model).then(res => {
+        if (res.code == 200) {
+          proFormRef?.resetForm(formEl)
+          ElMessage.success('操作成功')
+          handleCancel(closeRefVal)
+          proTable.value?.getTableList()
+        } else {
+          console.log('message', res.message)
+        }
+      })
+      butLoading.value = false
+    }
+    butLoading.value = false
+  })
+}
+
+// 取消按钮,重置表单,关闭弹框
+const handleCancel = (closeRefVal: any) => {
+  closeRefVal.handleCancel()
+}
+
+const reset = () => {
+  if (menuRef.value) {
+    menuRef.value.setCheckedKeys([])
+  }
+  menuExpand.value = false
+  menuNodeAll.value = false
+  deptExpand.value = true
+  deptNodeAll.value = false
 }
 
 // 表格配置项
@@ -136,30 +388,32 @@ const columns = reactive<ColumnProps<any>[]>([
   { prop: 'roleId', label: '角色编号' },
   {
     prop: 'roleName',
-    label: '角色名称',
-    search: {
-      el: 'input'
-    }
+    label: '角色名称'
   },
   {
     prop: 'roleKey',
-    label: '角色权限字符串',
-    search: {
-      el: 'input'
-    }
+    label: '角色权限字符串'
   },
   {
     prop: 'roleSort',
-    label: '显示顺序',
-    search: {
-      el: 'input'
-    }
+    label: '显示顺序'
   },
   {
     prop: 'status',
     label: '角色状态',
-    search: {
-      el: 'input'
+    enum: () => getDictsApi('sys_normal_disable'),
+    search: { el: 'tree-select' },
+    fieldNames: { label: 'dictLabel', value: 'dictValue' },
+    render: scope => {
+      return (
+        <el-switch
+          model-value={scope.row.status}
+          active-text={scope.row.status === '1' ? '禁用' : '启用'}
+          active-value={'0'}
+          inactive-value={'1'}
+          onClick={() => changeStatus(scope.row)}
+        />
+      )
     }
   },
   {
@@ -170,12 +424,18 @@ const columns = reactive<ColumnProps<any>[]>([
       props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
     }
   },
-  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+  { prop: 'operation', label: '操作', width: 350, fixed: 'right' }
 ])
+let _options = ref<ProForm.FormOptions>({
+  labelWidth: 120,
+  hasFooter: false,
+  disabled: false
+})
 
-let formItems: ProForm.ItemsOptions[] = []
-const setFormItems = () => {
-  formItems = [
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
     {
       label: '角色名称',
       prop: 'roleName',
@@ -185,9 +445,10 @@ const setFormItems = () => {
       }
     },
     {
-      label: '角色权限字符串',
+      label: '角色权限',
       prop: 'roleKey',
       rules: [{ required: true, message: '角色权限字符串不能为空', trigger: 'blur' }],
+      tooltip: '@PreAuthorize("@ss.hasPermi(`admin`)")',
       compOptions: {
         placeholder: '请输入角色权限字符串'
       }
@@ -195,47 +456,83 @@ const setFormItems = () => {
     {
       label: '显示顺序',
       prop: 'roleSort',
+      span: 12,
       rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入显示顺序'
+        elTagName: 'input-number'
       }
     },
     {
-      label: '数据范围',
-      prop: 'dataScope',
+      label: '角色状态',
+      prop: 'status',
+      span: 12,
+      rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
       compOptions: {
-        placeholder: '请输入数据范围'
+        elTagName: 'radio-group',
+        enum: () => getDictsApi('sys_normal_disable'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue'
       }
     },
     {
-      label: '菜单树选择项是否关联显示',
+      label: '菜单权限',
       prop: 'menuCheckStrictly',
+      hideLabelSuffix: true,
       compOptions: {
+        elTagName: 'slot',
         placeholder: '请输入菜单树选择项是否关联显示'
       }
     },
     {
-      label: '部门树选择项是否关联显示',
-      prop: 'deptCheckStrictly',
+      label: '备注',
+      prop: 'remark',
+      compOptions: {
+        type: 'textarea',
+        placeholder: '请输入内容'
+      }
+    }
+  ]
+}
+const setPerDataItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '角色名称',
+      prop: 'roleName',
       compOptions: {
-        placeholder: '请输入部门树选择项是否关联显示'
+        disabled: true
       }
     },
     {
-      label: '角色状态',
-      prop: 'status',
-      rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
+      label: '权限字符',
+      prop: 'roleKey',
       compOptions: {
-        placeholder: '请输入角色状态'
+        disabled: true
       }
     },
     {
-      label: '备注',
-      prop: 'remark',
+      label: '权限范围',
+      prop: 'dataScope',
+      compOptions: {
+        elTagName: 'select',
+        enum: dataScopeOptions.value,
+        onChange: (val: string) => {
+          if (val !== '2') {
+            deptRef.value.setCheckedKeys([])
+          }
+        }
+      }
+    },
+    {
+      label: '数据权限',
+      prop: 'deptCheckStrictly',
+      hideLabelSuffix: true,
       compOptions: {
-        placeholder: '请输入备注'
+        elTagName: 'slot'
       }
     }
   ]
 }
 </script>
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 0 - 338
src/views/system/user/authUser.vue

@@ -1,338 +0,0 @@
-<template>
-  <div class="main-box">
-    <TreeFilter
-      title="部门列表"
-      :is-all="false"
-      :data="treeFilterData"
-      :default-value="initParam.deptId"
-      @change="changeTreeFilter"
-    />
-    <div class="table-box">
-      <ProTable
-        ref="proTable"
-        row-key="userId"
-        :indent="20"
-        :columns="columns"
-        :data-callback="dataCallback"
-        :request-api="listUserApi"
-        :request-auto="false"
-        :init-param="initParam"
-        :search-col="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3 }"
-      >
-        <!-- 表格 header 按钮 -->
-        <template #tableHeader="scope">
-          <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="openDialog(1, '用户新增')">
-            新增
-          </el-button>
-          <el-button type="primary" v-auth="['system:user:import']" :icon="Upload" plain @click="batchAdd">导入</el-button>
-          <el-button type="primary" v-auth="['system:user:export']" :icon="Download" plain @click="downloadFile">导出</el-button>
-          <el-button
-            type="danger"
-            v-auth="['system:user:remove']"
-            :icon="Delete"
-            plain
-            :disabled="!scope.isSelected"
-            @click="batchDelete(scope.selectedListIds)"
-          >
-            批量删除
-          </el-button>
-        </template>
-        <!-- 表格操作 -->
-        <template #operation="scope">
-          <el-button type="primary" link :icon="View" @click="openDialog(3, '用户查看', scope.row)">查看</el-button>
-          <el-button type="primary" link :icon="EditPen" @click="openDialog(2, '用户编辑', scope.row)">编辑</el-button>
-          <el-button type="primary" link :icon="Delete" @click="deleteAccount(scope.row)">删除</el-button>
-        </template>
-      </ProTable>
-      <FormDialog ref="formDialogRef" />
-      <ImportExcel ref="dialogRef" />
-    </div>
-  </div>
-</template>
-
-<script setup lang="tsx" name="treeProTable">
-import { onMounted, reactive, ref } from 'vue'
-import { User } from '@/api/interface'
-import { useDownload } from '@/hooks/useDownload'
-import { useHandleData } from '@/hooks/useHandleData'
-import { ElMessageBox } from 'element-plus'
-import ProTable from '@/components/ProTable/index.vue'
-import TreeFilter from '@/components/TreeFilter/index.vue'
-import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
-import { CirclePlus, Download, Upload, View, Delete, EditPen } from '@element-plus/icons-vue'
-import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
-import {
-  listUserApi,
-  delUserApi,
-  addUserApi,
-  updateUserApi,
-  changeUserStatus,
-  importTemplateApi,
-  importDataApi,
-  exportApi,
-  getUserApi,
-  deptTreeSelectApi
-} from '@/api/modules/system/user'
-import { getDictsApi } from '@/api/modules/system/dictData'
-onMounted(() => {
-  getTreeFilter()
-})
-
-// ProTable 实例
-const proTable = ref<ProTableInstance>()
-// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
-const initParam = reactive({ deptId: '' })
-
-// 获取 treeFilter 数据
-// 当 proTable 的 requestAuto 属性为 false,不会自动请求表格数据,等待 treeFilter 数据回来之后,更改 initParam.departmentId 的值,才会触发请求 proTable 数据
-const treeFilterData = ref<any>([])
-const getTreeFilter = async () => {
-  const { data } = await deptTreeSelectApi()
-  treeFilterData.value = data
-  initParam.deptId = treeFilterData.value[0].id
-}
-
-// 树形筛选切换
-const changeTreeFilter = (val: string) => {
-  // ElMessage.success('请注意查看请求参数变化 🤔')
-  proTable.value!.pageable.pageNum = 1
-  initParam.deptId = val
-}
-
-const dataCallback = (data: any) => {
-  const page = proTable.value!.pageable
-  return {
-    list: data.data,
-    total: data.total,
-    pageNum: page.pageNum,
-    pageSize: page.pageSize
-  }
-}
-
-// 切换用户状态
-const changeStatus = async (row: User.ResUserList) => {
-  await useHandleData(
-    changeUserStatus,
-    { userId: row.userId, status: row.status == '1' ? 0 : 1 },
-    `切换【${row.userName}】用户状态`
-  )
-  proTable.value?.getTableList()
-}
-
-// 删除用户信息
-const deleteAccount = async (params: User.ResUserList) => {
-  await useHandleData(delUserApi, { id: [params.id] }, `删除【${params.userName}】用户`)
-  proTable.value?.getTableList()
-}
-
-// 批量删除用户信息
-const batchDelete = async (id: string[]) => {
-  await useHandleData(delUserApi, { id }, '删除所选用户信息')
-  proTable.value?.clearSelection()
-  proTable.value?.getTableList()
-}
-
-// 导出用户列表
-const downloadFile = async () => {
-  ElMessageBox.confirm('确认导出用户数据?', '温馨提示', { type: 'warning' }).then(() =>
-    useDownload(exportApi, '用户列表', proTable.value?.searchParam)
-  )
-}
-
-// 批量添加用户
-const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
-const batchAdd = () => {
-  const params = {
-    title: '用户',
-    tempApi: importTemplateApi,
-    importApi: importDataApi,
-    getTableList: proTable.value?.getTableList
-  }
-  dialogRef.value?.acceptParams(params)
-}
-const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
-const postOptions = ref<any[]>([])
-const roleOptions = ref<any[]>([])
-// 打开弹框的功能
-const openDialog = async (type: number, title: string, row?: any) => {
-  let res = await getUserApi(row?.userId || null)
-
-  postOptions.value = res.data.posts
-  roleOptions.value = res.data.roles
-  // 表单项配置
-  const fieldList: Form.FieldItem[] = [
-    {
-      label: '用户昵称',
-      placeholder: '请输入用户昵称',
-      span: 12,
-      field: 'nickName',
-      rules: [{ required: true, message: '用户昵称不能为空' }]
-    },
-    {
-      label: '归属部门',
-      span: 12,
-      field: 'deptId',
-      placeholder: '请选择归属部门',
-      enum: () => deptTreeSelectApi(),
-      type: 'select-tree',
-      options: {
-        valueKey: 'id',
-        labelKey: 'label',
-        children: 'children'
-      }
-    },
-    {
-      label: '手机号码',
-      placeholder: '请输入手机号码',
-      span: 12,
-      field: 'phonenumber',
-      rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
-    },
-    {
-      label: '邮箱',
-      placeholder: '请输入邮箱',
-      span: 12,
-      field: 'email',
-      rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }]
-    },
-    {
-      label: '性别',
-      span: 12,
-      field: 'sex',
-      enum: () => getDictsApi('sys_user_gender'),
-      type: 'select',
-      options: {
-        labelKey: 'dictLabel',
-        valueKey: 'dictValue'
-      }
-    },
-    {
-      label: '状态',
-      span: 12,
-      field: 'status',
-      type: 'radio',
-      enum: () => getDictsApi('sys_normal_disable'),
-      options: {
-        labelKey: 'dictLabel',
-        valueKey: 'dictValue'
-      }
-    },
-    {
-      label: '岗位',
-      span: 12,
-      field: 'postIds',
-      enum: postOptions.value,
-      type: 'select',
-      options: {
-        valueKey: 'postId',
-        labelKey: 'postName',
-        multiple: true
-      }
-    },
-    {
-      label: '角色',
-      span: 12,
-      field: 'roleIds',
-      enum: roleOptions.value,
-      type: 'select',
-      options: {
-        valueKey: 'roleId',
-        labelKey: 'roleName',
-        multiple: true
-      }
-    },
-    {
-      label: '备注',
-      placeholder: '请输入内容',
-      field: 'remark',
-      type: 'textarea'
-    }
-  ]
-  if (type == 1) {
-    fieldList.splice(
-      4,
-      0,
-      {
-        label: '用户名称',
-        placeholder: '请输入用户名称',
-        field: 'userName',
-        span: 12,
-        rules: [
-          { required: true, message: '用户名称不能为空' },
-          { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
-        ]
-      },
-      {
-        label: '用户密码',
-        placeholder: '请输入密码',
-        field: 'password',
-        type: 'password',
-        showPassword: true,
-        span: 12,
-        rules: [
-          { required: true, message: '密码不能为空' },
-          { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
-        ]
-      }
-    )
-  }
-  if (row?.userId) {
-    res.data.data.postIds = res.data.postIds
-    res.data.data.roleIds = res.data.roleIds
-  }
-
-  const params = {
-    title,
-    width: 680,
-    isEdit: type !== 3,
-    fieldList: fieldList,
-    model: type == 1 ? {} : res.data.data,
-    api: type == 1 ? addUserApi : updateUserApi,
-    getTableList: proTable.value?.getTableList
-  }
-  console.log('model', params.model)
-
-  formDialogRef.value?.openDialog(params)
-}
-// 表格配置项
-const columns = reactive<ColumnProps<User.ResUserList>[]>([
-  { type: 'selection', fixed: 'left', width: 70 },
-  { prop: 'userId', label: '用户编号' },
-  { prop: 'userName', label: '用户名称', search: { el: 'input' }, width: 120 },
-  { prop: 'nickName', label: '用户昵称', search: { el: 'input' }, width: 120 },
-  { prop: 'dept.deptName', label: '部门', width: 120 },
-  { prop: 'phonenumber', label: '手机号码', width: 120 },
-  {
-    prop: 'status',
-    label: '用户状态',
-    width: 120,
-    enum: () => getDictsApi('sys_normal_disable'),
-    search: { el: 'tree-select' },
-    fieldNames: { label: 'dictLabel', value: 'dictValue' },
-    render: scope => {
-      return (
-        <el-switch
-          model-value={scope.row.status}
-          active-text={scope.row.status === '1' ? '禁用' : '启用'}
-          active-value={'0'}
-          inactive-value={'1'}
-          onClick={() => changeStatus(scope.row)}
-        />
-      )
-    }
-  },
-  {
-    prop: 'sex',
-    label: '性别',
-    width: 120,
-    enum: () => getDictsApi('sys_user_gender'),
-    fieldNames: {
-      label: 'dictLabel',
-      value: 'dictValue'
-    }
-  },
-  { prop: 'createTime', label: '创建时间', width: 180 },
-  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
-])
-</script>

+ 9 - 21
src/views/system/user/index.vue

@@ -1,12 +1,6 @@
 <template>
   <div class="main-box">
-    <TreeFilter
-      title="部门列表"
-      :is-all="false"
-      :data="treeFilterData"
-      :default-value="initParam.deptId"
-      @change="changeTreeFilter"
-    />
+    <TreeFilter title="部门列表" :is-all="false" :data="treeFilterData" :default-value="initParam.deptId" @change="changeTreeFilter" />
     <div class="table-box">
       <ProTable
         ref="proTable"
@@ -20,9 +14,7 @@
       >
         <!-- 表格 header 按钮 -->
         <template #tableHeader="scope">
-          <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="openDialog(1, '用户新增')">
-            新增
-          </el-button>
+          <el-button type="primary" v-auth="['system:user:add']" :icon="CirclePlus" @click="openDialog(1, '用户新增')"> 新增 </el-button>
           <el-button type="primary" v-auth="['system:user:import']" :icon="Upload" plain @click="batchAdd">导入</el-button>
           <el-button type="primary" v-auth="['system:user:export']" :icon="Download" plain @click="downloadFile">导出</el-button>
           <el-button
@@ -38,9 +30,9 @@
         </template>
         <!-- 表格操作 -->
         <template #operation="scope">
-          <el-button type="primary" link :icon="View" @click="openDialog(3, '用户查看', scope.row)">查看</el-button>
-          <el-button type="primary" link :icon="EditPen" @click="openDialog(2, '用户编辑', scope.row)">编辑</el-button>
-          <el-button type="primary" link :icon="Delete" @click="deleteAccount(scope.row)">删除</el-button>
+          <el-button type="primary" link v-if="scope.row.userId !== 1" :icon="View" @click="openDialog(3, '用户查看', scope.row)">查看</el-button>
+          <el-button type="primary" link v-if="scope.row.userId !== 1" :icon="EditPen" @click="openDialog(2, '用户编辑', scope.row)">编辑</el-button>
+          <el-button type="primary" link v-if="scope.row.userId !== 1" :icon="Delete" @click="deleteAccount(scope.row)">删除</el-button>
         </template>
       </ProTable>
       <FormDialog ref="formDialogRef" />
@@ -49,7 +41,7 @@
   </div>
 </template>
 
-<script setup lang="tsx" name="treeProTable">
+<script setup lang="tsx" name="UserManage">
 import { onMounted, reactive, ref } from 'vue'
 import { User } from '@/api/interface'
 import { useDownload } from '@/hooks/useDownload'
@@ -101,11 +93,7 @@ const changeTreeFilter = (val: string) => {
 
 // 切换用户状态
 const changeStatus = async (row: User.ResUserList) => {
-  await useHandleData(
-    changeUserStatus,
-    { userId: row.userId, status: row.status == '1' ? 0 : 1 },
-    `切换【${row.userName}】用户状态`
-  )
+  await useHandleData(changeUserStatus, { userId: row.userId, status: row.status == '1' ? 0 : 1 }, `切换【${row.userName}】用户状态`)
   proTable.value?.getTableList()
 }
 
@@ -267,8 +255,8 @@ const openDialog = async (type: number, title: string, row?: any) => {
     )
   }
   if (row?.userId) {
-    res.data.postIds = res.data.postIds
-    res.data.roleIds = res.data.roleIds
+    res.data.user.postIds = res.data.postIds
+    res.data.user.roleIds = res.data.roleIds
   }
 
   const params = {

+ 11 - 34
src/views/tool/gen/index.vue

@@ -3,13 +3,7 @@
     <ProTable ref="proTable" :columns="columns" row-key="tableId" :request-api="listTableApi">
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
-        <el-button
-          type="primary"
-          v-auth="['tool:gen:code']"
-          :disabled="!scope.isSelected"
-          :icon="CirclePlus"
-          @click="handleGenTable()"
-        >
+        <el-button type="primary" v-auth="['tool:gen:code']" :disabled="!scope.isSelected" :icon="CirclePlus" @click="handleGenTable()">
           生成
         </el-button>
         <el-button type="primary" v-auth="['tool:gen:import']" :icon="Upload" plain @click="batchAdd"> 导入表 </el-button>
@@ -26,27 +20,19 @@
       </template>
       <!-- 表格操作 -->
       <template #operation="scope">
-        <el-button type="primary" link :icon="View" v-auth="['tool:gen:query']" @click="openDialog(1, '代码预览', scope.row)">
-          预览
-        </el-button>
+        <el-button type="primary" link :icon="View" v-auth="['tool:gen:query']" @click="openDialog(1, '代码预览', scope.row)"> 预览 </el-button>
         <el-button type="primary" link :icon="EditPen" v-auth="['tool:gen:edit']" @click="toDetail(scope.row)"> 编辑 </el-button>
-        <el-button type="primary" link :icon="Delete" v-auth="['tool:gen:remove']" @click="deleteRole(scope.row)">
-          删除
-        </el-button>
-        <el-button type="primary" link :icon="Refresh" v-auth="['tool:gen:edit']" @click="handleSyncDb(scope.row)">
-          同步
-        </el-button>
-        <el-button type="primary" link :icon="Download" v-auth="['tool:gen:code']" @click="handleGenTable(scope.row)">
-          生成代码
-        </el-button>
+        <el-button type="primary" link :icon="Delete" v-auth="['tool:gen:remove']" @click="deleteRole(scope.row)"> 删除 </el-button>
+        <el-button type="primary" link :icon="Refresh" v-auth="['tool:gen:edit']" @click="handleSyncDb(scope.row)"> 同步 </el-button>
+        <el-button type="primary" link :icon="Download" v-auth="['tool:gen:code']" @click="handleGenTable(scope.row)"> 生成代码 </el-button>
       </template>
     </ProTable>
     <FormDialog ref="formDialogRef">
-      <template #default="{ model }">
+      <template #default="{ parameter }">
         <div class="dialog-slot-c">
-          <el-tabs v-model="model.activeName" type="border-card">
+          <el-tabs v-model="parameter.model.activeName" type="border-card">
             <el-tab-pane
-              v-for="(value, key) in model.data"
+              v-for="(value, key) in parameter.model.data"
               :label="key.toString().substring(key.toString().lastIndexOf('/') + 1, key.toString().indexOf('.vm'))"
               :name="key.toString().substring(key.toString().lastIndexOf('/') + 1, key.toString().indexOf('.vm'))"
               :key="value"
@@ -62,8 +48,7 @@
                     : key
                         .toString()
                         .substring(key.toString().lastIndexOf('/') + 1, key.toString().indexOf('.vm'))
-                        .split('.')[1] ||
-                      key.toString().substring(key.toString().lastIndexOf('/') + 1, key.toString().indexOf('.vm'))
+                        .split('.')[1] || key.toString().substring(key.toString().lastIndexOf('/') + 1, key.toString().indexOf('.vm'))
                 "
               >
               </preview-code>
@@ -83,19 +68,11 @@ import { useDownload } from '@/hooks/useDownload'
 import { useRouter } from 'vue-router'
 import ProTable from '@/components/ProTable/index.vue'
 import FormDialog from '@/components/CustomDialog/index.vue'
-import TableDialog from '@/components/DialogOld/table.vue'
+import TableDialog from '@/components/TableDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { Delete, EditPen, Download, Upload, Refresh, View, CirclePlus } from '@element-plus/icons-vue'
 import PreviewCode from '@/components/Highlight/index.vue'
-import {
-  listTableApi,
-  batchGenCodeApi,
-  listDbTableApi,
-  importTableApi,
-  previewTableApi,
-  delTableApi,
-  synchDbApi
-} from '@/api/modules/tool/gen'
+import { listTableApi, batchGenCodeApi, listDbTableApi, importTableApi, previewTableApi, delTableApi, synchDbApi } from '@/api/modules/tool/gen'
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 const router = useRouter()