فهرست منبع

feat: 升级系统基础功能

wanggaokun 1 سال پیش
والد
کامیت
56309449c4
79فایلهای تغییر یافته به همراه3803 افزوده شده و 957 حذف شده
  1. 9 2
      README.md
  2. 5 10
      package.json
  3. 7 6
      src/api/index.ts
  4. 13 2
      src/api/interface/index.ts
  5. 59 0
      src/api/interface/login.ts
  6. 2 2
      src/api/interface/menu.ts
  7. 25 0
      src/api/interface/system/config.ts
  8. 28 0
      src/api/interface/system/data.ts
  9. 47 0
      src/api/interface/system/dept.ts
  10. 71 0
      src/api/interface/system/menu.ts
  11. 24 0
      src/api/interface/system/oss.ts
  12. 41 0
      src/api/interface/system/ossConfig.ts
  13. 25 0
      src/api/interface/system/post.ts
  14. 54 0
      src/api/interface/system/role.ts
  15. 20 0
      src/api/interface/system/type.ts
  16. 87 0
      src/api/interface/system/user.ts
  17. 175 0
      src/api/interface/tool/gen.ts
  18. 11 9
      src/api/modules/login.ts
  19. 86 0
      src/api/modules/system/config.ts
  20. 7 31
      src/api/modules/system/dept.ts
  21. 13 22
      src/api/modules/system/dict.ts
  22. 9 9
      src/api/modules/system/dictData.ts
  23. 13 36
      src/api/modules/system/menu.ts
  24. 28 0
      src/api/modules/system/oss.ts
  25. 62 0
      src/api/modules/system/ossConfig.ts
  26. 7 6
      src/api/modules/system/post.ts
  27. 16 14
      src/api/modules/system/role.ts
  28. 45 10
      src/api/modules/system/user.ts
  29. 19 19
      src/api/modules/tool/gen.ts
  30. 0 15
      src/components/CustomDialog/index.vue
  31. 5 1
      src/components/FormDialog/index.vue
  32. 73 0
      src/components/ImagePreview/index.vue
  33. 4 4
      src/components/Loading/fullScreen.ts
  34. 1 1
      src/components/ProForm/components/Item.vue
  35. 29 9
      src/components/ProForm/index.vue
  36. 3 4
      src/components/ProTable/index.vue
  37. 3 3
      src/components/SearchForm/index.vue
  38. 1 1
      src/components/SelectIcon/index.vue
  39. 0 1
      src/components/TableDialog/index.vue
  40. 96 234
      src/components/Upload/File.vue
  41. 235 0
      src/components/Upload/FileS3.vue
  42. 122 77
      src/components/Upload/Imgs.vue
  43. 361 0
      src/components/Upload/ImgsS3.vue
  44. 98 0
      src/components/UploadDialog/index.vue
  45. 30 0
      src/components/iFrame/index.vue
  46. 1 1
      src/directives/modules/role.ts
  47. 15 0
      src/enums/MenuTypeEnum.ts
  48. 99 2
      src/hooks/useDownload.ts
  49. 1 1
      src/languages/modules/en.ts
  50. 1 1
      src/languages/modules/zh.ts
  51. 1 1
      src/layouts/components/Footer/index.vue
  52. 21 18
      src/layouts/components/Header/components/Avatar.vue
  53. 51 6
      src/routers/modules/routerData.json
  54. 25 0
      src/styles/common.scss
  55. 18 0
      src/styles/var.scss
  56. 15 7
      src/typings/ProForm.d.ts
  57. 35 0
      src/typings/element.d.ts
  58. 16 0
      src/typings/global.d.ts
  59. 27 1
      src/utils/common.ts
  60. 2 3
      src/views/login/components/LoginForm.vue
  61. 0 4
      src/views/monitor/logininfor/index.vue
  62. 64 60
      src/views/system/dept/index.vue
  63. 77 83
      src/views/system/dict/data.vue
  64. 39 51
      src/views/system/dict/index.vue
  65. 11 11
      src/views/system/menu/index.vue
  66. 312 0
      src/views/system/oss/config.vue
  67. 183 0
      src/views/system/oss/index.vue
  68. 47 38
      src/views/system/post/index.vue
  69. 4 5
      src/views/system/role/authUser.vue
  70. 10 10
      src/views/system/role/index.vue
  71. 127 108
      src/views/system/user/index.vue
  72. 9 0
      src/views/system/user/profile/index.scss
  73. 222 0
      src/views/system/user/profile/index.vue
  74. 190 0
      src/views/system/user/profile/userAvatar.vue
  75. 1 1
      src/views/tool/gen/components/genInfoForm.vue
  76. 9 10
      src/views/tool/gen/index.vue
  77. 5 1
      tsconfig.json
  78. 74 1
      vite.config.ts
  79. 22 5
      yarn.lock

+ 9 - 2
README.md

@@ -1,3 +1,10 @@
-# km-web
+# taais-web
 
 
-前端脚手架
+```
+yarn install
+
+## 启动
+yarn serve
+```
+
+前端脚手架

+ 5 - 10
package.json

@@ -1,22 +1,14 @@
 {
 {
-  "name": "km-admin",
+  "name": "taais-admin",
   "private": true,
   "private": true,
   "version": "1.2.0",
   "version": "1.2.0",
   "type": "module",
   "type": "module",
-  "description": "km-admin open source management system",
+  "description": "taais-admin open source management system",
   "author": {
   "author": {
     "name": "gaokunw",
     "name": "gaokunw",
     "email": "wanggaokun@wo.cn"
     "email": "wanggaokun@wo.cn"
   },
   },
   "license": "MIT",
   "license": "MIT",
-  "homepage": "https://gitee.com/gaokunw/kimi-web",
-  "repository": {
-    "type": "git",
-    "url": "git@gitee.com:gaokunw/kimi-web.git"
-  },
-  "bugs": {
-    "url": "https://gitee.com/gaokunw/kimi-web/issues"
-  },
   "scripts": {
   "scripts": {
     "dev": "vite",
     "dev": "vite",
     "serve": "vite",
     "serve": "vite",
@@ -49,6 +41,7 @@
     "echarts-liquidfill": "^3.1.0",
     "echarts-liquidfill": "^3.1.0",
     "element-plus": "^2.4.3",
     "element-plus": "^2.4.3",
     "file-saver": "^2.0.5",
     "file-saver": "^2.0.5",
+    "image-conversion": "^2.1.1",
     "js-cookie": "^3.0.5",
     "js-cookie": "^3.0.5",
     "jsencrypt": "^3.3.2",
     "jsencrypt": "^3.3.2",
     "md5": "^2.3.0",
     "md5": "^2.3.0",
@@ -62,8 +55,10 @@
     "screenfull": "^6.0.2",
     "screenfull": "^6.0.2",
     "sortablejs": "^1.15.1",
     "sortablejs": "^1.15.1",
     "vue": "^3.3.11",
     "vue": "^3.3.11",
+    "vue-cropper": "^1.1.2",
     "vue-i18n": "^9.6.4",
     "vue-i18n": "^9.6.4",
     "vue-router": "^4.2.5",
     "vue-router": "^4.2.5",
+    "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0"
     "vuedraggable": "^4.1.0"
   },
   },
   "devDependencies": {
   "devDependencies": {

+ 7 - 6
src/api/index.ts

@@ -31,12 +31,13 @@ const config = {
 const encryptHeader = 'encrypt-key'
 const encryptHeader = 'encrypt-key'
 // 是否显示重新登录
 // 是否显示重新登录
 export let isReLogin = { show: false }
 export let isReLogin = { show: false }
-// export const globalHeaders = () => {
-//   return {
-//     Authorization: 'Bearer ' + getToken(),
-//     clientid: import.meta.env.VITE_APP_CLIENT_ID
-//   }
-// }
+
+export const globalHeaders = () => {
+  return {
+    Authorization: 'Bearer ' + getToken(),
+    clientid: import.meta.env.VITE_APP_CLIENT_ID
+  }
+}
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
 axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID
 axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID
 const axiosCanceler = new AxiosCanceler()
 const axiosCanceler = new AxiosCanceler()

+ 13 - 2
src/api/interface/index.ts

@@ -18,11 +18,22 @@ export interface ResPage<T> {
 }
 }
 
 
 // 分页请求参数
 // 分页请求参数
-export interface ReqPage {
+export interface PageQuery {
   pageNum: number
   pageNum: number
   pageSize: number
   pageSize: number
 }
 }
 
 
+// 分页请求参数
+export interface BaseEntity {
+  /** 乐观锁 */
+  version?: number
+  createBy?: any
+  createDept?: any
+  createTime?: string
+  updateBy?: any
+  updateTime?: any
+}
+
 // 文件上传模块
 // 文件上传模块
 export namespace Upload {
 export namespace Upload {
   export interface ResFileUrl {
   export interface ResFileUrl {
@@ -52,7 +63,7 @@ export namespace Login {
 
 
 // 用户管理模块
 // 用户管理模块
 export namespace User {
 export namespace User {
-  export interface ReqUserParams extends ReqPage {
+  export interface ReqUserParams extends PageQuery {
     username: string
     username: string
     gender: number
     gender: number
     idCard: string
     idCard: string

+ 59 - 0
src/api/interface/login.ts

@@ -0,0 +1,59 @@
+/**
+ * 注册
+ */
+export type RegisterForm = {
+  tenantId: number
+  username: string
+  password: string
+  confirmPassword?: string
+  code?: string
+  uuid?: string
+  userType?: string
+}
+
+/**
+ * 登录请求
+ */
+export interface LoginData {
+  tenantId?: number
+  username?: string
+  password?: string
+  rememberMe?: boolean
+  socialCode?: string
+  socialState?: string
+  source?: string
+  code?: string
+  uuid?: string
+  clientId: string
+  grantType: string
+}
+
+/**
+ * 登录响应
+ */
+export interface LoginResult {
+  access_token: string
+}
+
+/**
+ * 验证码返回
+ */
+export interface VerifyCodeResult {
+  captchaEnabled: boolean
+  uuid?: string
+  img?: string
+}
+
+/**
+ * 租户
+ */
+export interface TenantVO {
+  companyName: string
+  domain: any
+  tenantId: string
+}
+
+export interface TenantInfo {
+  tenantEnabled: boolean
+  voList: TenantVO[]
+}

+ 2 - 2
src/api/interface/menu.ts

@@ -1,7 +1,7 @@
-import { ReqPage } from '@/api/interface/index'
+import { PageQuery } from '@/api/interface/index'
 // 用户管理模块
 // 用户管理模块
 export namespace Menu {
 export namespace Menu {
-  export interface ReqMenuParams extends ReqPage {
+  export interface ReqMenuParams extends PageQuery {
     username: string
     username: string
     gender: number
     gender: number
     idCard: string
     idCard: string

+ 25 - 0
src/api/interface/system/config.ts

@@ -0,0 +1,25 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface ConfigVO extends BaseEntity {
+  configId: number | string
+  configName: string
+  configKey: string
+  configValue: string
+  configType: string
+  remark: string
+}
+
+export interface ConfigForm {
+  configId: number | string | undefined
+  configName: string
+  configKey: string
+  configValue: string
+  configType: string
+  version: number
+  remark: string
+}
+
+export interface ConfigQuery extends PageQuery {
+  configName: string
+  configKey: string
+  configType: string
+}

+ 28 - 0
src/api/interface/system/data.ts

@@ -0,0 +1,28 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface DictDataQuery extends PageQuery {
+  dictName: string
+  dictType: string
+  dictLabel: string
+}
+
+export interface DictDataVO extends BaseEntity {
+  dictCode: string
+  dictLabel: string
+  dictValue: string
+  cssClass: string
+  listClass: ElTagType
+  dictSort: number
+  remark: string
+}
+
+export interface DictDataForm {
+  dictType?: string
+  dictCode: string | undefined
+  dictLabel: string
+  dictValue: string
+  cssClass: string
+  listClass: ElTagType
+  dictSort: number
+  version: number
+  remark: string
+}

+ 47 - 0
src/api/interface/system/dept.ts

@@ -0,0 +1,47 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+/**
+ * 部门查询参数
+ */
+export interface DeptQuery extends PageQuery {
+  deptName?: string
+  status?: number
+}
+
+/**
+ * 部门类型
+ */
+export interface DeptVO extends BaseEntity {
+  id: number | string
+  parentName: string
+  parentId: number | string
+  children: DeptVO[]
+  deptId: number | string
+  deptName: string
+  orderNum: number
+  leader: string
+  phone: string
+  email: string
+  status: string
+  delFlag: string
+  ancestors: string
+  menuId: string | number
+}
+
+/**
+ * 部门表单类型
+ */
+export interface DeptForm {
+  parentName?: string
+  parentId?: number | string
+  children?: DeptForm[]
+  deptId?: number | string
+  deptName?: string
+  orderNum?: number
+  leader?: string
+  phone?: string
+  email?: string
+  status?: string
+  version?: number
+  delFlag?: string
+  ancestors?: string
+}

+ 71 - 0
src/api/interface/system/menu.ts

@@ -0,0 +1,71 @@
+import { MenuTypeEnum } from '@/enums/MenuTypeEnum'
+import { BaseEntity } from '@/api/interface/index'
+
+/**
+ * 菜单树形结构类型
+ */
+export interface MenuTreeOption {
+  id: string | number
+  label: string
+  parentId: string | number
+  weight: number
+  children?: MenuTreeOption[]
+}
+
+export interface RoleMenuTree {
+  menus: MenuTreeOption[]
+  checkedKeys: string[]
+}
+
+/**
+ * 菜单查询参数类型
+ */
+export interface MenuQuery {
+  keywords?: string
+  menuName?: string
+  status?: string
+}
+
+/**
+ * 菜单视图对象类型
+ */
+export interface MenuVO extends BaseEntity {
+  parentName: string
+  parentId: string | number
+  children: MenuVO[]
+  menuId: string | number
+  menuName: string
+  orderNum: number
+  path: string
+  component: string
+  queryParam: string
+  isFrame: string
+  isCache: string
+  menuType: MenuTypeEnum
+  visible: string
+  status: string
+  icon: string
+  remark: string
+}
+
+export interface MenuForm {
+  parentName?: string
+  parentId?: string | number
+  children?: MenuForm[]
+  menuId?: string | number
+  menuName: string
+  orderNum: number
+  path: string
+  component?: string
+  queryParam?: string
+  isFrame?: string
+  isCache?: string
+  menuType?: MenuTypeEnum
+  visible?: string
+  status?: string
+  icon?: string
+  remark?: string
+  query?: string
+  perms?: string
+  version?: number
+}

+ 24 - 0
src/api/interface/system/oss.ts

@@ -0,0 +1,24 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+
+export interface OssVO extends BaseEntity {
+  ossId: string | number
+  fileName: string
+  originalName: string
+  fileSuffix: string
+  url: string
+  createByName: string
+  service: string
+}
+
+export interface OssQuery extends PageQuery {
+  fileName: string
+  originalName: string
+  fileSuffix: string
+  createTime: string
+  service: string
+  orderByColumn: string
+  isAsc: string
+}
+export interface OssForm {
+  file: undefined | string
+}

+ 41 - 0
src/api/interface/system/ossConfig.ts

@@ -0,0 +1,41 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+
+export interface OssConfigVO extends BaseEntity {
+  ossConfigId: number | string
+  configKey: string
+  accessKey: string
+  secretKey: string
+  bucketName: string
+  prefix: string
+  endpoint: string
+  domain: string
+  isHttps: string
+  region: string
+  status: string
+  ext1: string
+  remark: string
+  accessPolicy: string
+}
+
+export interface OssConfigQuery extends PageQuery {
+  configKey: string
+  bucketName: string
+  status: string
+}
+
+export interface OssConfigForm {
+  ossConfigId: string | number | undefined
+  configKey: string
+  accessKey: string
+  secretKey: string
+  bucketName: string
+  prefix: string
+  endpoint: string
+  domain: string
+  isHttps: string
+  accessPolicy: string
+  region: string
+  status: string
+  version: number
+  remark: string
+}

+ 25 - 0
src/api/interface/system/post.ts

@@ -0,0 +1,25 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface PostVO extends BaseEntity {
+  postId: number | string
+  postCode: string
+  postName: string
+  postSort: number
+  status: string
+  remark: string
+}
+
+export interface PostForm {
+  postId: number | string | undefined
+  postCode: string
+  postName: string
+  postSort: number
+  status: string
+  version: number
+  remark: string
+}
+
+export interface PostQuery extends PageQuery {
+  postCode: string
+  postName: string
+  status: string
+}

+ 54 - 0
src/api/interface/system/role.ts

@@ -0,0 +1,54 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+/**
+ * 菜单树形结构类型
+ */
+export interface DeptTreeOption {
+  id: string
+  label: string
+  parentId: string
+  weight: number
+  children?: DeptTreeOption[]
+}
+
+export interface RoleDeptTree {
+  checkedKeys: string[]
+  depts: DeptTreeOption[]
+}
+
+export interface RoleVO extends BaseEntity {
+  roleId: string | number
+  roleName: string
+  roleKey: string
+  roleSort: number
+  dataScope: string
+  menuCheckStrictly: boolean
+  deptCheckStrictly: boolean
+  status: string
+  delFlag: string
+  remark?: any
+  flag: boolean
+  menuIds?: Array<string | number>
+  deptIds?: Array<string | number>
+  admin: boolean
+}
+
+export interface RoleQuery extends PageQuery {
+  roleName: string
+  roleKey: string
+  status: string
+}
+
+export interface RoleForm {
+  roleName: string
+  roleKey: string
+  roleSort: number
+  status: string
+  menuCheckStrictly: boolean
+  deptCheckStrictly: boolean
+  remark: string
+  dataScope?: string
+  roleId: string | undefined
+  menuIds: Array<string | number>
+  deptIds: Array<string | number>
+  version?: number
+}

+ 20 - 0
src/api/interface/system/type.ts

@@ -0,0 +1,20 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface DictTypeVO extends BaseEntity {
+  dictId: number | string
+  dictName: string
+  dictType: string
+  remark: string
+}
+
+export interface DictTypeForm {
+  dictId: number | string | undefined
+  dictName: string
+  dictType: string
+  version: number
+  remark: string
+}
+
+export interface DictTypeQuery extends PageQuery {
+  dictName: string
+  dictType: string
+}

+ 87 - 0
src/api/interface/system/user.ts

@@ -0,0 +1,87 @@
+import { RoleVO } from '@/api/interface/system/role'
+import { PostVO } from '@/api/interface/system/post'
+import { DeptVO } from '@/api/interface/system/dept'
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+/**
+ * 用户信息
+ */
+export interface UserInfo {
+  user: UserVO
+  roles: string[]
+  permissions: string[]
+}
+
+/**
+ * 用户查询对象类型
+ */
+export interface UserQuery extends PageQuery {
+  userName?: string
+  phonenumber?: string
+  status?: string
+  deptId?: string | number
+  roleId?: string | number
+}
+
+/**
+ * 用户返回对象
+ */
+export interface UserVO extends BaseEntity {
+  userId: string | number
+  deptId: number
+  userName: string
+  nickName: string
+  userType: string
+  email: string
+  phonenumber: string
+  dept: DeptVO
+  gender: string
+  avatar: string
+  url: string
+  status: string
+  delFlag: string
+  loginIp: string
+  loginDate: string
+  remark: string
+  deptName: string
+  roles: RoleVO[]
+  roleIds: any
+  postIds: any
+  roleId: any
+  admin: boolean
+}
+
+/**
+ * 用户表单类型
+ */
+export interface UserForm {
+  id?: string
+  userId?: string
+  deptId?: number
+  userName: string
+  nickName?: string
+  password: string
+  phonenumber?: string
+  email?: string
+  gender?: string
+  status: string
+  remark?: string
+  postIds: string[]
+  roleIds: string[]
+  version?: number
+}
+
+export interface UserInfoVO {
+  user: UserVO
+  roles: RoleVO[]
+  roleIds: string[]
+  posts: PostVO[]
+  postIds: string[]
+  roleGroup: string
+  postGroup: string
+}
+
+export interface ResetPwdForm {
+  oldPassword: string
+  newPassword: string
+  confirmPassword: string
+}

+ 175 - 0
src/api/interface/tool/gen.ts

@@ -0,0 +1,175 @@
+import { PageQuery, BaseEntity } from '@/api/interface/index'
+export interface TableVO extends BaseEntity {
+  tableId: string | number
+  tableName: string
+  tableComment: string
+  subTableName?: any
+  subTableFkName?: any
+  className: string
+  tplCategory: string
+  packageName: string
+  moduleName: string
+  businessName: string
+  functionName: string
+  functionAuthor: string
+  genType: string
+  genPath: string
+  pkColumn?: any
+  columns?: any
+  options?: any
+  remark?: any
+  treeCode?: any
+  treeParentCode?: any
+  treeName?: any
+  menuIds?: any
+  parentMenuId?: any
+  parentMenuName?: any
+  tree: boolean
+  crud: boolean
+  editColumns: number
+}
+
+export interface TableQuery extends PageQuery {
+  tableName: string
+  tableComment: string
+}
+
+export interface DbColumnVO extends BaseEntity {
+  columnId?: any
+  tableId?: any
+  columnName?: any
+  columnComment?: any
+  columnType?: any
+  javaType?: any
+  javaField?: any
+  isPk?: any
+  isIncrement?: any
+  isRequired?: any
+  isInsert?: any
+  isEdit?: any
+  isList?: any
+  isQuery?: any
+  queryType?: any
+  htmlType?: any
+  dictType?: any
+  sort?: any
+  increment: boolean
+  capJavaField?: any
+  usableColumn: boolean
+  superColumn: boolean
+  list: boolean
+  pk: boolean
+  insert: boolean
+  edit: boolean
+  query: boolean
+  required: boolean
+}
+
+export interface DbTableVO {
+  tableId?: any
+  tableName: string
+  tableComment: string
+  subTableName?: any
+  subTableFkName?: any
+  className?: any
+  tplCategory?: any
+  packageName?: any
+  moduleName?: any
+  businessName?: any
+  functionName?: any
+  functionAuthor?: any
+  genType?: any
+  genPath?: any
+  pkColumn?: any
+  columns: DbColumnVO[]
+  options?: any
+  remark?: any
+  treeCode?: any
+  treeParentCode?: any
+  treeName?: any
+  menuIds?: any
+  parentMenuId?: any
+  parentMenuName?: any
+  tree: boolean
+  crud: boolean
+  version?: number
+}
+
+export interface DbTableQuery extends PageQuery {
+  tableName: string
+  tableComment: string
+}
+
+export interface GenTableVO {
+  info: DbTableVO
+  rows: DbColumnVO[]
+  tables: DbTableVO[]
+}
+
+export interface DbColumnForm extends BaseEntity {
+  columnId: string
+  tableId: string
+  columnName: string
+  columnComment: string
+  columnType: string
+  javaType: string
+  javaField: string
+  isPk: string
+  isIncrement: string
+  isRequired: string
+  isInsert?: any
+  isEdit: string
+  isList: string
+  isQuery?: any
+  queryType: string
+  htmlType: string
+  dictType: string
+  sort: number
+  increment: boolean
+  capJavaField: string
+  usableColumn: boolean
+  superColumn: boolean
+  list: boolean
+  pk: boolean
+  insert: boolean
+  edit: boolean
+  query: boolean
+  required: boolean
+}
+
+export interface DbParamForm {
+  treeCode?: any
+  treeName?: any
+  treeParentCode?: any
+  parentMenuId: string
+}
+
+export interface DbTableForm extends BaseEntity {
+  tableId: string | string
+  tableName: string
+  tableComment: string
+  subTableName?: any
+  subTableFkName?: any
+  className: string
+  tplCategory: string
+  packageName: string
+  moduleName: string
+  businessName: string
+  functionName: string
+  functionAuthor: string
+  genType: string
+  genPath: string
+  pkColumn?: any
+  columns: DbColumnForm[]
+  options: string
+  remark?: any
+  treeCode?: any
+  treeParentCode?: any
+  treeName?: any
+  menuIds?: any
+  parentMenuId: string
+  parentMenuName?: any
+  tree: boolean
+  crud: boolean
+  params: DbParamForm
+}

+ 11 - 9
src/api/modules/login.ts

@@ -1,12 +1,14 @@
-import { Login } from '@/api/interface/index'
+import { UserInfo } from '@/api/interface/system/user'
+import { LoginData, LoginResult, VerifyCodeResult } from '@/api/interface/login'
 import http from '@/api'
 import http from '@/api'
 
 
 /**
 /**
- * @name 登录模块
+ *
+ * 用户登录
+ * @param data {LoginData}
  */
  */
-// 用户登录
-export const loginApi = (params: Login.ReqLoginForm) => {
-  return http.post<Login.ResLogin>('/auth/login', params, { loading: true, isEncrypt: true }) // 正常 post json 请求  ==>  application/json
+export const loginApi = (data: LoginData) => {
+  return http.post<LoginResult>('/auth/login', data, { loading: true, isEncrypt: true }) // 正常 post json 请求  ==>  application/json
 }
 }
 
 
 // 用户退出登录
 // 用户退出登录
@@ -14,11 +16,11 @@ export const logoutApi = () => {
   return http.post('/auth/logout', {}, { loading: false })
   return http.post('/auth/logout', {}, { loading: false })
 }
 }
 
 
-// 用户退出登录
+// 获取用户详细信息
 export const getInfoApi = () => {
 export const getInfoApi = () => {
-  return http.get('/system/user/getInfo', {}, { loading: false })
+  return http.get<UserInfo>('/system/user/getInfo', {}, { loading: false })
 }
 }
-// 用户退出登录
+// 获取验证码
 export const getCodeImg = () => {
 export const getCodeImg = () => {
-  return http.get('/captchaImage', {}, { loading: false })
+  return http.get<VerifyCodeResult>('/captchaImage', {}, { loading: false })
 }
 }

+ 86 - 0
src/api/modules/system/config.ts

@@ -0,0 +1,86 @@
+import { ConfigVO } from '@/api/interface/system/config'
+import http from '@/api'
+/**
+ * @name 根据参数键名查询参数值
+ * @param ossConfigId ossConfigId
+ * @returns returns
+ */
+export const getConfigKeyApi = (configKey: string) => {
+  return http.get<ConfigVO>(`/system/config/configKey/${configKey}`)
+}
+/**
+ * @name 查询参数配置列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listConfigApi = (query: any) => {
+  return http.get<any>('/system/config/list', query, { loading: true })
+}
+
+/**
+ * @name 查询参数配置详细
+ * @param configId configId
+ * @returns returns
+ */
+export const getConfigApi = (configId: any) => {
+  return http.get<any>(`/system/config/${configId}`)
+}
+
+/**
+ * @name 新增参数配置
+ * @param data data
+ * @returns returns
+ */
+export const addConfigApi = (data: any) => {
+  return http.post<any>('/system/config', data, { loading: false })
+}
+
+/**
+ * @name 修改参数配置
+ * @param data data
+ * @returns returns
+ */
+export const updateConfigApi = (data: any) => {
+  return http.put<any>('/system/config', data, { loading: false })
+}
+/**
+ * @name 修改参数配置
+ * @param data data
+ * @returns returns
+ */
+export const updateConfigByKeyApi = (data: any) => {
+  return http.put<any>('/system/config/updateByKey', data, { loading: false })
+}
+
+/**
+ * @name 删除参数配置
+ * @param configId configId
+ * @returns returns
+ */
+export const delConfigApi = (configId: any) => {
+  return http.delete<any>(`/system/config/${configId}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/system/config/importTemplate', {})
+}
+
+/**
+ * @name 导入数据
+ * @returns returns
+ */
+export const importConfigDataApi = (data: any) => {
+  return http.post('/system/config/importData', data)
+}
+
+/**
+ * @name 导出数据
+ * @returns returns
+ */
+export const exportConfigApi = (data: any) => {
+  return http.downloadPost('/system/config/export', data)
+}

+ 7 - 31
src/api/modules/system/dept.ts

@@ -1,11 +1,11 @@
 import http from '@/api'
 import http from '@/api'
-
+import { DeptForm, DeptQuery, DeptVO } from '@/api/interface/system/dept'
 /**
 /**
  * @name 查询部门列表
  * @name 查询部门列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listDeptApi = (query: any) => {
+export const listDeptApi = (query: DeptQuery) => {
   return http.get<any>('/system/dept/list', query, { loading: true })
   return http.get<any>('/system/dept/list', query, { loading: true })
 }
 }
 
 
@@ -14,8 +14,8 @@ export const listDeptApi = (query: any) => {
  * @param deptId deptId
  * @param deptId deptId
  * @returns returns
  * @returns returns
  */
  */
-export const getDeptApi = (deptId: any) => {
-  return http.get<any>(`/system/dept/${deptId}`)
+export const getDeptApi = (deptId: string | number) => {
+  return http.get<DeptVO>(`/system/dept/${deptId}`)
 }
 }
 
 
 /**
 /**
@@ -23,7 +23,7 @@ export const getDeptApi = (deptId: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const addDeptApi = (data: any) => {
+export const addDeptApi = (data: DeptForm) => {
   return http.post<any>('/system/dept', data, { loading: false })
   return http.post<any>('/system/dept', data, { loading: false })
 }
 }
 
 
@@ -32,7 +32,7 @@ export const addDeptApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updateDeptApi = (data: any) => {
+export const updateDeptApi = (data: DeptForm) => {
   return http.put<any>('/system/dept', data, { loading: false })
   return http.put<any>('/system/dept', data, { loading: false })
 }
 }
 
 
@@ -41,30 +41,6 @@ export const updateDeptApi = (data: any) => {
  * @param deptId deptId
  * @param deptId deptId
  * @returns returns
  * @returns returns
  */
  */
-export const delDeptApi = (deptId: any) => {
+export const delDeptApi = (deptId: number | string) => {
   return http.delete<any>(`/system/dept/${deptId}`)
   return http.delete<any>(`/system/dept/${deptId}`)
 }
 }
-
-/**
- * @name 下载模板
- * @returns returns
- */
-export const importTemplateApi = () => {
-  return http.downloadPost('/system/dept/importTemplate', {})
-}
-
-/**
- * @name 导入数据
- * @returns returns
- */
-export const importDataApi = (data: any) => {
-  return http.post('/system/dept/importData', data)
-}
-
-/**
- * @name 导出数据
- * @returns returns
- */
-export const exportApi = (data: any) => {
-  return http.downloadPost('/system/dept/export', data)
-}

+ 13 - 22
src/api/modules/system/dict.ts

@@ -1,12 +1,12 @@
 import http from '@/api'
 import http from '@/api'
-
+import { DictTypeForm, DictTypeVO, DictTypeQuery } from '@/api/interface/system/type'
 /**
 /**
  * @name 查询字典类型列表
  * @name 查询字典类型列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listDictApi = (query: any) => {
-  return http.get<any>('/system/dict/type/list', query, { loading: false })
+export const listDictApi = (query: DictTypeQuery) => {
+  return http.get<DictTypeVO[]>('/system/dict/type/list', query, { loading: false })
 }
 }
 
 
 /**
 /**
@@ -14,7 +14,7 @@ export const listDictApi = (query: any) => {
  * @param dictId dictId
  * @param dictId dictId
  * @returns returns
  * @returns returns
  */
  */
-export const getDictApi = (dictId: any) => {
+export const getDictApi = (dictId: string | number) => {
   return http.get<any>(`/system/dict/type/${dictId}`)
   return http.get<any>(`/system/dict/type/${dictId}`)
 }
 }
 
 
@@ -23,7 +23,7 @@ export const getDictApi = (dictId: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const addDictApi = (data: any) => {
+export const addDictApi = (data: DictTypeForm) => {
   return http.post<any>('/system/dict/type', data, { loading: false })
   return http.post<any>('/system/dict/type', data, { loading: false })
 }
 }
 
 
@@ -32,7 +32,7 @@ export const addDictApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updateDictApi = (data: any) => {
+export const updateDictApi = (data: DictTypeForm) => {
   return http.put<any>('/system/dict/type', data, { loading: false })
   return http.put<any>('/system/dict/type', data, { loading: false })
 }
 }
 
 
@@ -41,32 +41,23 @@ export const updateDictApi = (data: any) => {
  * @param dictId dictId
  * @param dictId dictId
  * @returns returns
  * @returns returns
  */
  */
-export const delDictApi = (id: any) => {
+export const delDictApi = (id: string | number | Array<string | number>) => {
   return http.delete<any>(`/system/dict/type/${id}`)
   return http.delete<any>(`/system/dict/type/${id}`)
 }
 }
 
 
 /**
 /**
- * @name 下载模板
- * @returns returns
- */
-export const importTemplateApi = () => {
-  return http.downloadPost('/system/dict/type/importTemplate', {})
-}
-
-/**
- * @name 导入数据
+ * @name 导出数据
  * @returns returns
  * @returns returns
  */
  */
-export const importDataApi = (data: any) => {
-  return http.post('/system/dict/type/importData', data)
+export const exportApi = (data: any) => {
+  return http.downloadPost('/system/dict/type/export', data)
 }
 }
-
 /**
 /**
- * @name 导出数据
+ * @name 刷新字典缓存
  * @returns returns
  * @returns returns
  */
  */
-export const exportApi = (data: any) => {
-  return http.downloadPost('/system/dict/type/export', data)
+export const refreshCacheApi = () => {
+  return http.delete<any>(`system/dict/type/refreshCache`)
 }
 }
 
 
 /**
 /**

+ 9 - 9
src/api/modules/system/dictData.ts

@@ -1,12 +1,12 @@
 import http from '@/api'
 import http from '@/api'
-
+import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/interface/system/data'
 /**
 /**
  * @name 查询字典数据列表
  * @name 查询字典数据列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listDataApi = (query: any) => {
-  return http.get<any>('/system/dict/data/list', query, { loading: true })
+export const listDataApi = (query: DictDataQuery) => {
+  return http.get<DictDataVO[]>('/system/dict/data/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -14,8 +14,8 @@ export const listDataApi = (query: any) => {
  * @param dictCode dictCode
  * @param dictCode dictCode
  * @returns returns
  * @returns returns
  */
  */
-export const getDataApi = (dictCode: any) => {
-  return http.get<any>(`/system/dict/data/${dictCode}`)
+export const getDataApi = (dictCode: string | number) => {
+  return http.get<DictDataVO>(`/system/dict/data/${dictCode}`)
 }
 }
 
 
 /**
 /**
@@ -24,7 +24,7 @@ export const getDataApi = (dictCode: any) => {
  * @returns returns
  * @returns returns
  */
  */
 export const getDictsApi = (dictType: string) => {
 export const getDictsApi = (dictType: string) => {
-  return http.get<any>(`/system/dict/data/type/${dictType}`)
+  return http.get<DictDataVO[]>(`/system/dict/data/type/${dictType}`)
 }
 }
 
 
 /**
 /**
@@ -32,7 +32,7 @@ export const getDictsApi = (dictType: string) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const addDataApi = (data: any) => {
+export const addDataApi = (data: DictDataForm) => {
   return http.post<any>('/system/dict/data', data, { loading: false })
   return http.post<any>('/system/dict/data', data, { loading: false })
 }
 }
 
 
@@ -41,7 +41,7 @@ export const addDataApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updateDataApi = (data: any) => {
+export const updateDataApi = (data: DictDataForm) => {
   return http.put<any>('/system/dict/data', data, { loading: false })
   return http.put<any>('/system/dict/data', data, { loading: false })
 }
 }
 
 
@@ -50,7 +50,7 @@ export const updateDataApi = (data: any) => {
  * @param dictCode dictCode
  * @param dictCode dictCode
  * @returns returns
  * @returns returns
  */
  */
-export const delDataApi = (id: any) => {
+export const delDataApi = (id: string | number | Array<string | number>) => {
   return http.delete<any>(`/system/dict/data/${id}`)
   return http.delete<any>(`/system/dict/data/${id}`)
 }
 }
 
 

+ 13 - 36
src/api/modules/system/menu.ts

@@ -1,8 +1,9 @@
 import http from '@/api'
 import http from '@/api'
-
+import { MenuQuery, MenuVO, MenuForm, MenuTreeOption, RoleMenuTree } from '@/api/interface/system/menu'
+import { RouteRecordRaw } from 'vue-router'
 // 获取路由
 // 获取路由
 export const getRoutersApi = () => {
 export const getRoutersApi = () => {
-  return http.get<Menu.MenuOptions[]>('/system/menu/getRouters', {}, { loading: false })
+  return http.get<RouteRecordRaw[]>('/system/menu/getRouters', {}, { loading: false })
 }
 }
 
 
 /**
 /**
@@ -10,8 +11,8 @@ export const getRoutersApi = () => {
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listMenuApi = (query?: any) => {
-  return http.get<any>('/system/menu/list', query, { loading: true })
+export const listMenuApi = (query?: MenuQuery) => {
+  return http.get<MenuVO[]>('/system/menu/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -19,8 +20,8 @@ export const listMenuApi = (query?: any) => {
  * @param menuId menuId
  * @param menuId menuId
  * @returns returns
  * @returns returns
  */
  */
-export const getMenuApi = (menuId: any) => {
-  return http.get<any>(`/system/menu/${menuId}`)
+export const getMenuApi = (menuId: string | number) => {
+  return http.get<MenuVO>(`/system/menu/${menuId}`)
 }
 }
 
 
 /**
 /**
@@ -28,7 +29,7 @@ export const getMenuApi = (menuId: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const addMenuApi = (data: any) => {
+export const addMenuApi = (data: MenuForm) => {
   return http.post<any>('/system/menu', data, { loading: false })
   return http.post<any>('/system/menu', data, { loading: false })
 }
 }
 
 
@@ -37,7 +38,7 @@ export const addMenuApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updateMenuApi = (data: any) => {
+export const updateMenuApi = (data: MenuForm) => {
   return http.put<any>('/system/menu', data, { loading: false })
   return http.put<any>('/system/menu', data, { loading: false })
 }
 }
 
 
@@ -46,40 +47,16 @@ export const updateMenuApi = (data: any) => {
  * @param menuId menuId
  * @param menuId menuId
  * @returns returns
  * @returns returns
  */
  */
-export const delMenuApi = (menuId: any) => {
+export const delMenuApi = (menuId: string | number) => {
   return http.delete<any>(`/system/menu/${menuId}`)
   return http.delete<any>(`/system/menu/${menuId}`)
 }
 }
 
 
-/**
- * @name 下载模板
- * @returns returns
- */
-export const importTemplateApi = () => {
-  return http.downloadPost('/system/menu/importTemplate', {})
-}
-
-/**
- * @name 导入数据
- * @returns returns
- */
-export const importDataApi = (data: any) => {
-  return http.post('/system/menu/importData', data)
-}
-
-/**
- * @name 导出数据
- * @returns returns
- */
-export const exportApi = (data: any) => {
-  return http.downloadPost('/system/menu/export', data)
-}
-
 /**
 /**
  * @name 查询菜单下拉树结构
  * @name 查询菜单下拉树结构
  * @returns returns
  * @returns returns
  */
  */
 export const treeSelectApi = () => {
 export const treeSelectApi = () => {
-  return http.get<any>(`/system/menu/treeselect`)
+  return http.get<MenuTreeOption[]>(`/system/menu/treeselect`)
 }
 }
 
 
 /**
 /**
@@ -87,6 +64,6 @@ export const treeSelectApi = () => {
  * @param roleId roleId
  * @param roleId roleId
  * @returns returns
  * @returns returns
  */
  */
-export const roleMenuTreeselectApi = (roleId: any) => {
-  return http.get<any>(`/system/menu/roleMenuTreeselect/${roleId}`)
+export const roleMenuTreeselectApi = (roleId: string | number) => {
+  return http.get<RoleMenuTree>(`/system/menu/roleMenuTreeselect/${roleId}`)
 }
 }

+ 28 - 0
src/api/modules/system/oss.ts

@@ -0,0 +1,28 @@
+import http from '@/api'
+import { OssQuery, OssVO } from '@/api/interface/system/oss'
+/**
+ * @name 查询OSS对象存储列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listOssApi = (query: OssQuery) => {
+  return http.get<OssVO[]>('/resource/oss/list', query, { loading: true })
+}
+
+/**
+ * @name 查询OSS对象基于id串
+ * @param ossId ossId
+ * @returns returns
+ */
+export const getListByIdsApi = (ossIds: string | number) => {
+  return http.get<OssVO[]>(`/resource/oss/listByIds/${ossIds}`)
+}
+
+/**
+ * @name 删除OSS对象存储
+ * @param ossId ossId
+ * @returns returns
+ */
+export const delOssApi = (ossId: string | number | Array<string | number>) => {
+  return http.delete<any>(`/resource/oss/${ossId}`)
+}

+ 62 - 0
src/api/modules/system/ossConfig.ts

@@ -0,0 +1,62 @@
+import http from '@/api'
+import { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/interface/system/ossConfig'
+/**
+ * @name 查询对象存储配置列表
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const listOssConfigApi = (query: OssConfigQuery) => {
+  return http.get<OssConfigVO[]>('/resource/oss/config/list', query, { loading: true })
+}
+
+/**
+ * @name 查询对象存储配置详细
+ * @param ossConfigId ossConfigId
+ * @returns returns
+ */
+export const getOssConfigApi = (ossConfigId: string | number) => {
+  return http.get<OssConfigVO>(`/resource/oss/config/${ossConfigId}`)
+}
+
+/**
+ * @name 新增对象存储配置
+ * @param data data
+ * @returns returns
+ */
+export const addOssConfigApi = (data: OssConfigForm) => {
+  return http.post<any>('/resource/oss/config', data, { loading: false })
+}
+
+/**
+ * @name 修改对象存储配置
+ * @param data data
+ * @returns returns
+ */
+export const updateOssConfigApi = (data: OssConfigForm) => {
+  return http.put<any>('/resource/oss/config', data, { loading: false })
+}
+/**
+ * @name 对象存储状态修改
+ * @param data data
+ * @returns returns
+ */
+export const changeOssConfigStatusApi = (data: { ossConfigId: string | number; version: number; status: string; configKey: string }) => {
+  return http.put<any>('/resource/oss/config/changeStatus', data, { loading: false })
+}
+
+/**
+ * @name 删除对象存储配置
+ * @param ossConfigId ossConfigId
+ * @returns returns
+ */
+export const delOssConfigApi = (ossConfigId: string | number | Array<string | number>) => {
+  return http.delete<any>(`/resource/oss/config/${ossConfigId}`)
+}
+
+/**
+ * @name 下载模板
+ * @returns returns
+ */
+export const importTemplateApi = () => {
+  return http.downloadPost('/resource/oss/config/importTemplate', {})
+}

+ 7 - 6
src/api/modules/system/post.ts

@@ -1,12 +1,13 @@
 import http from '@/api'
 import http from '@/api'
+import { PostForm, PostQuery, PostVO } from '@/api/interface/system/post'
 
 
 /**
 /**
  * @name 查询岗位信息列表
  * @name 查询岗位信息列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listPostApi = (query: any) => {
-  return http.get<any>('/system/post/list', query, { loading: true })
+export const listPostApi = (query: PostQuery) => {
+  return http.get<PostVO[]>('/system/post/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -14,7 +15,7 @@ export const listPostApi = (query: any) => {
  * @param postId postId
  * @param postId postId
  * @returns returns
  * @returns returns
  */
  */
-export const getPostApi = (postId: any) => {
+export const getPostApi = (postId: string | number) => {
   return http.get<any>(`/system/post/${postId}`)
   return http.get<any>(`/system/post/${postId}`)
 }
 }
 
 
@@ -23,7 +24,7 @@ export const getPostApi = (postId: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const addPostApi = (data: any) => {
+export const addPostApi = (data: PostForm) => {
   return http.post<any>('/system/post', data, { loading: false })
   return http.post<any>('/system/post', data, { loading: false })
 }
 }
 
 
@@ -32,7 +33,7 @@ export const addPostApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updatePostApi = (data: any) => {
+export const updatePostApi = (data: PostForm) => {
   return http.put<any>('/system/post', data, { loading: false })
   return http.put<any>('/system/post', data, { loading: false })
 }
 }
 
 
@@ -41,7 +42,7 @@ export const updatePostApi = (data: any) => {
  * @param postId postId
  * @param postId postId
  * @returns returns
  * @returns returns
  */
  */
-export const delPostApi = (postId: any) => {
+export const delPostApi = (postId: string | number | (string | number)[]) => {
   return http.delete<any>(`/system/post/${postId}`)
   return http.delete<any>(`/system/post/${postId}`)
 }
 }
 
 

+ 16 - 14
src/api/modules/system/role.ts

@@ -1,12 +1,14 @@
 import http from '@/api'
 import http from '@/api'
-
+import { RoleQuery, RoleVO, RoleDeptTree } from '@/api/interface/system/role'
+import { UserVO, UserQuery } from '@/api/interface/system/user'
+import { RoleMenuTree } from '@/api/interface/system/menu'
 /**
 /**
  * @name 查询角色信息列表
  * @name 查询角色信息列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listRoleApi = (query: any) => {
-  return http.get<any>('/system/role/list', query, { loading: true })
+export const listRoleApi = (query: RoleQuery) => {
+  return http.get<RoleVO[]>('/system/role/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -14,8 +16,8 @@ export const listRoleApi = (query: any) => {
  * @param roleId 角色Id
  * @param roleId 角色Id
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const deptTreeSelectApi = (roleId: any) => {
-  return http.get<any>(`/system/role/deptTree/${roleId}`)
+export const deptTreeSelectApi = (roleId: string | number) => {
+  return http.get<RoleDeptTree>(`/system/role/deptTree/${roleId}`)
 }
 }
 
 
 /**
 /**
@@ -24,7 +26,7 @@ export const deptTreeSelectApi = (roleId: any) => {
  * @returns 返回列表
  * @returns 返回列表
  */
  */
 export const roleMenuTreeSelectApi = (roleId: any) => {
 export const roleMenuTreeSelectApi = (roleId: any) => {
-  return http.get<any>(`/system/menu/roleMenuTreeselect/${roleId}`)
+  return http.get<RoleMenuTree>(`/system/menu/roleMenuTreeselect/${roleId}`)
 }
 }
 
 
 /**
 /**
@@ -32,8 +34,8 @@ export const roleMenuTreeSelectApi = (roleId: any) => {
  * @param roleId roleId
  * @param roleId roleId
  * @returns returns
  * @returns returns
  */
  */
-export const getRoleApi = (roleId: any) => {
-  return http.get<any>(`/system/role/${roleId}`)
+export const getRoleApi = (roleId: string | number) => {
+  return http.get<RoleVO>(`/system/role/${roleId}`)
 }
 }
 
 
 /**
 /**
@@ -68,7 +70,7 @@ export const dataScopeApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const changeStatusApi = (data: any) => {
+export const changeStatusApi = (data: { roleId: string | number; version: number; status: string }) => {
   return http.put<any>('/system/role/changeStatus', data, { loading: false })
   return http.put<any>('/system/role/changeStatus', data, { loading: false })
 }
 }
 
 
@@ -77,7 +79,7 @@ export const changeStatusApi = (data: any) => {
  * @param roleId roleId
  * @param roleId roleId
  * @returns returns
  * @returns returns
  */
  */
-export const delRoleApi = (roleId: any) => {
+export const delRoleApi = (roleId: Array<string | number> | string | number) => {
   return http.delete<any>(`/system/role/${roleId}`)
   return http.delete<any>(`/system/role/${roleId}`)
 }
 }
 
 
@@ -110,8 +112,8 @@ export const exportApi = (data: any) => {
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const allocatedUserListApi = (query: any) => {
-  return http.get<any>(`/system/role/authUser/allocatedList`, query, { loading: true })
+export const allocatedUserListApi = (query: UserQuery) => {
+  return http.get<UserVO[]>(`/system/role/authUser/allocatedList`, query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -119,8 +121,8 @@ export const allocatedUserListApi = (query: any) => {
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const unallocatedUserListApi = (query: any) => {
-  return http.get<any>(`/system/role/authUser/unallocatedList`, query, { loading: true })
+export const unallocatedUserListApi = (query: UserQuery) => {
+  return http.get<UserVO[]>(`/system/role/authUser/unallocatedList`, query, { loading: true })
 }
 }
 
 
 /**
 /**

+ 45 - 10
src/api/modules/system/user.ts

@@ -1,13 +1,14 @@
 import http from '@/api'
 import http from '@/api'
 import { parseStrEmpty } from '@/utils/common'
 import { parseStrEmpty } from '@/utils/common'
-
+import { UserForm, UserQuery, UserVO, UserInfoVO } from '@/api/interface/system/user'
+import { DeptVO } from '@/api/interface/system/dept'
 /**
 /**
  * @name 查询用户列表
  * @name 查询用户列表
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listUserApi = (query: any) => {
-  return http.get<any>('/system/user/list', query, { loading: true })
+export const listUserApi = (query: UserQuery) => {
+  return http.get<UserVO[]>('/system/user/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -15,8 +16,8 @@ export const listUserApi = (query: any) => {
  * @param userId userId
  * @param userId userId
  * @returns returns
  * @returns returns
  */
  */
-export const getUserApi = (userId?: string) => {
-  return http.get<any>(`/system/user/` + parseStrEmpty(userId))
+export const getUserApi = (userId?: string | number) => {
+  return http.get<UserInfoVO>(`/system/user/` + parseStrEmpty(userId))
 }
 }
 
 
 /**
 /**
@@ -24,14 +25,14 @@ export const getUserApi = (userId?: string) => {
  * @returns returns
  * @returns returns
  */
  */
 export const deptTreeSelectApi = () => {
 export const deptTreeSelectApi = () => {
-  return http.get<any>(`/system/user/deptTree`)
+  return http.get<DeptVO[]>(`/system/user/deptTree`)
 }
 }
 
 
 /**
 /**
  * @name 新增用户
  * @name 新增用户
  * @returns returns
  * @returns returns
  */
  */
-export const addUserApi = (data: any) => {
+export const addUserApi = (data: UserForm) => {
   return http.post<any>('/system/user', data, { loading: false })
   return http.post<any>('/system/user', data, { loading: false })
 }
 }
 
 
@@ -39,7 +40,7 @@ export const addUserApi = (data: any) => {
  * @name 修改用户
  * @name 修改用户
  * @returns returns
  * @returns returns
  */
  */
-export const updateUserApi = (data: any) => {
+export const updateUserApi = (data: UserForm) => {
   return http.put<any>('/system/user', data, { loading: false })
   return http.put<any>('/system/user', data, { loading: false })
 }
 }
 
 
@@ -47,7 +48,7 @@ export const updateUserApi = (data: any) => {
  * @name 删除用户
  * @name 删除用户
  * @returns returns
  * @returns returns
  */
  */
-export const delUserApi = (userId: any) => {
+export const delUserApi = (userId: Array<string | number> | string | number) => {
   return http.delete<any>(`/system/user/${userId}`)
   return http.delete<any>(`/system/user/${userId}`)
 }
 }
 
 
@@ -57,7 +58,7 @@ export const delUserApi = (userId: any) => {
  * @param status status
  * @param status status
  * @returns returns
  * @returns returns
  */
  */
-export const changeUserStatus = (data: any) => {
+export const changeUserStatus = (data: { userId: number | string; version: number; status: string }) => {
   return http.put<any>('/system/user/changeStatus', data, { loading: false })
   return http.put<any>('/system/user/changeStatus', data, { loading: false })
 }
 }
 
 
@@ -93,3 +94,37 @@ export const importDataApi = (params: FormData) => {
 export const exportApi = (data: any) => {
 export const exportApi = (data: any) => {
   return http.downloadPost('/system/user/export', data)
   return http.downloadPost('/system/user/export', data)
 }
 }
+
+/**
+ * @name 用户头像上传
+ * @param params params
+ * @returns returns
+ */
+export const uploadAvatarApi = (params: FormData) => {
+  return http.post<any>('/system/user/profile/avatar', params)
+}
+
+/**
+ * @name 查询用户个人信息
+ * @returns returns
+ */
+export const getUserProfileApi = () => {
+  return http.get<UserInfoVO>(`/system/user/profile`)
+}
+
+/**
+ * @name 修改用户个人信息
+ * @param data data
+ * @returns returns
+ */
+export const updateUserProfileApi = (data: UserForm) => {
+  return http.put<any>('/system/user/profile', data, { loading: false })
+}
+/**
+ * @name 修改用户密码
+ * @param data data
+ * @returns returns
+ */
+export const updateUserPwdApi = (data: { oldPassword: string; newPassword: string }) => {
+  return http.put<any>('system/user/profile/updatePwd', data, { loading: false })
+}

+ 19 - 19
src/api/modules/tool/gen.ts

@@ -1,12 +1,12 @@
 import http from '@/api'
 import http from '@/api'
-
+import { DbTableQuery, DbTableVO, TableQuery, TableVO, GenTableVO, DbTableForm } from '@/api/interface/tool/gen'
 /**
 /**
  * @name 查询生成表数据
  * @name 查询生成表数据
- * @param query 参数
+ * @param query {TableQuery} 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listTableApi = (query: any) => {
-  return http.get<any>('/tool/gen/list', query, { loading: true })
+export const listTableApi = (query: TableQuery) => {
+  return http.get<TableVO[]>('/tool/gen/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -14,8 +14,8 @@ export const listTableApi = (query: any) => {
  * @param query 参数
  * @param query 参数
  * @returns 返回列表
  * @returns 返回列表
  */
  */
-export const listDbTableApi = (query: any) => {
-  return http.get<any>('/tool/gen/db/list', query, { loading: true })
+export const listDbTableApi = (query: DbTableQuery) => {
+  return http.get<DbTableVO[]>('/tool/gen/db/list', query, { loading: true })
 }
 }
 
 
 /**
 /**
@@ -23,8 +23,8 @@ export const listDbTableApi = (query: any) => {
  * @param tableId tableId
  * @param tableId tableId
  * @returns returns
  * @returns returns
  */
  */
-export const getTableApi = (tableId: any) => {
-  return http.get<any>(`/tool/gen/${tableId}`)
+export const getTableApi = (tableId: string | number) => {
+  return http.get<GenTableVO>(`/tool/gen/${tableId}`)
 }
 }
 
 
 /**
 /**
@@ -32,8 +32,8 @@ export const getTableApi = (tableId: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const updateGenTableApi = (data: any) => {
-  return http.put<any>('/tool/gen', data, { loading: false })
+export const updateGenTableApi = (data: DbTableForm) => {
+  return http.put<GenTableVO>('/tool/gen', data, { loading: false })
 }
 }
 
 
 /**
 /**
@@ -41,8 +41,8 @@ export const updateGenTableApi = (data: any) => {
  * @param data data
  * @param data data
  * @returns returns
  * @returns returns
  */
  */
-export const importTableApi = (data: any) => {
-  return http.post<any>('/tool/gen/importTable', data, { loading: false })
+export const importTableApi = (data: { tables: string }) => {
+  return http.post<GenTableVO>('/tool/gen/importTable', data, { loading: false })
 }
 }
 
 
 /**
 /**
@@ -50,7 +50,7 @@ export const importTableApi = (data: any) => {
  * @param tableId tableId
  * @param tableId tableId
  * @returns returns
  * @returns returns
  */
  */
-export const previewTableApi = (tableId: any) => {
+export const previewTableApi = (tableId: string | number) => {
   return http.get<any>(`/tool/gen/preview/${tableId}`)
   return http.get<any>(`/tool/gen/preview/${tableId}`)
 }
 }
 
 
@@ -59,7 +59,7 @@ export const previewTableApi = (tableId: any) => {
  * @param tableId tableId
  * @param tableId tableId
  * @returns returns
  * @returns returns
  */
  */
-export const delTableApi = (tableId: any) => {
+export const delTableApi = (tableId: string | number | Array<string | number>) => {
   return http.delete<any>(`/tool/gen/${tableId}`)
   return http.delete<any>(`/tool/gen/${tableId}`)
 }
 }
 
 
@@ -68,17 +68,17 @@ export const delTableApi = (tableId: any) => {
  * @param tableId tableId
  * @param tableId tableId
  * @returns returns
  * @returns returns
  */
  */
-export const genCodeApi = (tableId: any) => {
+export const genCodeApi = (tableId: string | number) => {
   return http.get<any>(`/tool/gen/genCode/${tableId}`)
   return http.get<any>(`/tool/gen/genCode/${tableId}`)
 }
 }
 
 
 /**
 /**
  * @name 同步数据库
  * @name 同步数据库
- * @param tableName tableName
+ * @param tableId tableName
  * @returns returns
  * @returns returns
  */
  */
-export const synchDbApi = (tableName: any) => {
-  return http.get<any>(`/tool/gen/syncDb/${tableName}`)
+export const synchDbApi = (tableId: string | number) => {
+  return http.get<any>(`/tool/gen/syncDb/${tableId}`)
 }
 }
 
 
 /**
 /**
@@ -86,6 +86,6 @@ export const synchDbApi = (tableName: any) => {
  * @param tableId tableId
  * @param tableId tableId
  * @returns returns
  * @returns returns
  */
  */
-export const batchGenCodeApi = (tableId: any) => {
+export const batchGenCodeApi = (tableId: string | number) => {
   return http.downloadGet(`/tool/gen/batchGenCode?tables=${tableId}`)
   return http.downloadGet(`/tool/gen/batchGenCode?tables=${tableId}`)
 }
 }

+ 0 - 15
src/components/CustomDialog/index.vue

@@ -12,8 +12,6 @@
     <template #footer>
     <template #footer>
       <span class="dialog-footer">
       <span class="dialog-footer">
         <slot name="footer" :parameter="parameter"></slot>
         <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>
       </span>
     </template>
     </template>
   </el-dialog>
   </el-dialog>
@@ -42,19 +40,6 @@ const parameter = ref<FormParameterProps>({
   model: {},
   model: {},
   api: undefined
   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 handleCancel = () => {
 const handleCancel = () => {

+ 5 - 1
src/components/FormDialog/index.vue

@@ -5,6 +5,7 @@
     :title="parameter.title"
     :title="parameter.title"
     :destroy-on-close="true"
     :destroy-on-close="true"
     :width="parameter.width"
     :width="parameter.width"
+    :top="parameter.top"
     draggable
     draggable
   >
   >
     <ProFrom ref="proFormRef" :items-options="parameter.itemsOptions" :form-options="_options" :model="parameter.model" />
     <ProFrom ref="proFormRef" :items-options="parameter.itemsOptions" :form-options="_options" :model="parameter.model" />
@@ -28,6 +29,7 @@ export interface FormParameterProps {
   labelWidth?: number // label宽度
   labelWidth?: number // label宽度
   api?: (params: any) => Promise<any> // 表单提交api
   api?: (params: any) => Promise<any> // 表单提交api
   isEdit?: boolean // 是否编辑
   isEdit?: boolean // 是否编辑
+  top?: string // 离顶部距离
   formOptions?: ProForm.FormOptions // 表单配置
   formOptions?: ProForm.FormOptions // 表单配置
   itemsOptions: ProForm.ItemsOptions[] // 动态表单字段配置
   itemsOptions: ProForm.ItemsOptions[] // 动态表单字段配置
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']> // 表单数据对象
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']> // 表单数据对象
@@ -40,6 +42,7 @@ const butLoading = ref(false)
 const parameter = ref<FormParameterProps>({
 const parameter = ref<FormParameterProps>({
   title: '',
   title: '',
   width: 500,
   width: 500,
+  top: '10vh',
   itemsOptions: [],
   itemsOptions: [],
   formOptions: {},
   formOptions: {},
   isEdit: true
   isEdit: true
@@ -56,11 +59,12 @@ const proFormRef = ref<InstanceType<typeof ProFrom> | null>(null)
 // 表单提交校验
 // 表单提交校验
 const handleSubmit = () => {
 const handleSubmit = () => {
   const formEl = proFormRef.value?.proFormRef
   const formEl = proFormRef.value?.proFormRef
+  const formModel = proFormRef.value?.formModel
   butLoading.value = true
   butLoading.value = true
   if (!formEl) return
   if (!formEl) return
   formEl.validate(valid => {
   formEl.validate(valid => {
     if (valid) {
     if (valid) {
-      parameter.value.api!(parameter.value.model).then(res => {
+      parameter.value.api!({ ...formModel, ...parameter.value.model }).then(res => {
         if (res.code == 200) {
         if (res.code == 200) {
           proFormRef.value?.resetForm(formEl)
           proFormRef.value?.resetForm(formEl)
           ElMessage.success('操作成功')
           ElMessage.success('操作成功')

+ 73 - 0
src/components/ImagePreview/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
+    <template #error>
+      <div class="image-slot">
+        <el-icon><picture-filled /></el-icon>
+      </div>
+    </template>
+  </el-image>
+</template>
+
+<script setup lang="ts" name="ImgPreview">
+import { computed } from 'vue'
+
+interface ImgPreviewProps {
+  src: string
+  height?: string | number // 组件高度 ==> 非必传(默认为 150px)
+  width?: string | number // 组件宽度 ==> 非必传(默认为 150px)
+}
+
+const props = withDefaults(defineProps<ImgPreviewProps>(), {
+  src: '',
+  height: '150px',
+  width: '150px'
+})
+
+const realSrc = computed(() => {
+  if (!props.src) {
+    return
+  }
+  let real_src = props.src.split(',')[0]
+  return real_src
+})
+
+const realSrcList = computed(() => {
+  if (!props.src) {
+    return []
+  }
+  let real_src_list = props.src.split(',')
+  let srcList: string[] = []
+  real_src_list.forEach((item: string) => {
+    return srcList.push(item)
+  })
+  return srcList
+})
+
+const realWidth = computed(() => (typeof props.width == 'string' ? props.width : `${props.width}px`))
+
+const realHeight = computed(() => (typeof props.height == 'string' ? props.height : `${props.height}px`))
+</script>
+
+<style lang="scss" scoped>
+.el-image {
+  background-color: #ebeef5;
+  border-radius: 5px;
+  box-shadow: 0 0 5px 1px #cccccc;
+  :deep(.el-image__inner) {
+    cursor: pointer;
+    transition: all 0.3s;
+    &:hover {
+      transform: scale(1.2);
+    }
+  }
+  :deep(.image-slot) {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    font-size: 30px;
+    color: #909399;
+  }
+}
+</style>

+ 4 - 4
src/components/Loading/fullScreen.ts

@@ -6,11 +6,11 @@ let loadingInstance: ReturnType<typeof ElLoading.service>
 /**
 /**
  * @description 开启 Loading
  * @description 开启 Loading
  * */
  * */
-const startLoading = () => {
+const startLoading = (content: string) => {
   loadingInstance = ElLoading.service({
   loadingInstance = ElLoading.service({
     fullscreen: true,
     fullscreen: true,
     lock: true,
     lock: true,
-    text: 'Loading',
+    text: content,
     background: 'rgba(0, 0, 0, 0.7)'
     background: 'rgba(0, 0, 0, 0.7)'
   })
   })
 }
 }
@@ -26,9 +26,9 @@ const endLoading = () => {
  * @description 显示全屏加载
  * @description 显示全屏加载
  * */
  * */
 let needLoadingRequestCount = 0
 let needLoadingRequestCount = 0
-export const showFullScreenLoading = () => {
+export const showFullScreenLoading = (content: string = 'Loading') => {
   if (needLoadingRequestCount === 0) {
   if (needLoadingRequestCount === 0) {
-    startLoading()
+    startLoading(content)
   }
   }
   needLoadingRequestCount++
   needLoadingRequestCount++
 }
 }

+ 1 - 1
src/components/ProForm/components/Item.vue

@@ -3,7 +3,7 @@
     :is="elTagNameValue"
     :is="elTagNameValue"
     v-bind="item.compOptions"
     v-bind="item.compOptions"
     v-model.trim="_formModel[handleProp(item.prop)]"
     v-model.trim="_formModel[handleProp(item.prop)]"
-    :data="['tree-select', 'tree'].includes(item.compOptions.elTagName!) ? itemEnum : []"
+    :data="['tree-select'].includes(item.compOptions.elTagName!) ? itemEnum : []"
     :options="['cascader', 'select-v2'].includes(item.compOptions.elTagName!) ? itemEnum : []"
     :options="['cascader', 'select-v2'].includes(item.compOptions.elTagName!) ? itemEnum : []"
   >
   >
     <template v-if="item.compOptions.elTagName === 'cascader'" #default="{ data }">
     <template v-if="item.compOptions.elTagName === 'cascader'" #default="{ data }">

+ 29 - 9
src/components/ProForm/index.vue

@@ -27,14 +27,23 @@
             <template v-else-if="item.compOptions.elTagName === 'file-upload'">
             <template v-else-if="item.compOptions.elTagName === 'file-upload'">
               <FileUpload v-model:model-value="formModel[item.prop]" />
               <FileUpload v-model:model-value="formModel[item.prop]" />
             </template>
             </template>
+            <template v-else-if="item.compOptions.elTagName === 'img-upload'">
+              <Imgs v-model="formModel[item.prop]" v-bind="$attrs" />
+            </template>
+            <template v-else-if="item.compOptions.elTagName === 'file-upload-s3'">
+              <FileUploadS3 v-model:model-value="formModel[item.prop]" />
+            </template>
+            <template v-else-if="item.compOptions.elTagName === 'img-upload-s3'">
+              <ImgsS3 v-model="formModel[item.prop]" v-bind="$attrs" />
+            </template>
             <Item v-else :item="item" :form-model="formModel" />
             <Item v-else :item="item" :form-model="formModel" />
           </component>
           </component>
         </el-col>
         </el-col>
       </template>
       </template>
     </el-row>
     </el-row>
     <el-form-item v-if="_formOptions.hasFooter">
     <el-form-item v-if="_formOptions.hasFooter">
-      <slot name="operation" :form-model="formModel" :model="formModel" :pro-form-ref="proFormRef">
-        <el-button type="primary" v-if="_formOptions.showSubmitButton" @click="onSubmit(proFormRef)">{{ _formOptions.submitButtonText }}</el-button>
+      <slot name="operation" :model="formModel" :pro-form-ref="proFormRef">
+        <el-button type="primary" @click="onSubmit(proFormRef)">{{ _formOptions.submitButtonText }}</el-button>
         <el-button v-if="_formOptions.showResetButton" type="info" @click="resetForm(proFormRef)">
         <el-button v-if="_formOptions.showResetButton" type="info" @click="resetForm(proFormRef)">
           {{ _formOptions.resetButtonText }}
           {{ _formOptions.resetButtonText }}
         </el-button>
         </el-button>
@@ -48,16 +57,20 @@
 
 
 <script setup lang="ts" name="ProForm">
 <script setup lang="ts" name="ProForm">
 import { ref, computed, ComputedRef, watch, unref, provide } from 'vue'
 import { ref, computed, ComputedRef, watch, unref, provide } from 'vue'
-import type { FormInstance } from 'element-plus'
+import { ElMessage, type FormInstance } from 'element-plus'
 import Item from '@/components/ProForm/components/Item.vue'
 import Item from '@/components/ProForm/components/Item.vue'
 import SelectIcon from '@/components/SelectIcon/index.vue'
 import SelectIcon from '@/components/SelectIcon/index.vue'
 import FileUpload from '@/components/Upload/File.vue'
 import FileUpload from '@/components/Upload/File.vue'
+import Imgs from '@/components/Upload/Imgs.vue'
+import FileUploadS3 from '@/components/Upload/FileS3.vue'
+import ImgsS3 from '@/components/Upload/ImgsS3.vue'
 // import { handleProp } from '@/utils'
 // import { handleProp } from '@/utils'
 // 表单整体配置项
 // 表单整体配置项
 export interface ProFormProps {
 export interface ProFormProps {
   formOptions?: ProForm.FormOptions
   formOptions?: ProForm.FormOptions
   itemsOptions: ProForm.ItemsOptions[]
   itemsOptions: ProForm.ItemsOptions[]
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']>
   model?: Record<ProForm.FormItem['prop'], ProForm.FormItem['value']>
+  api?: (params: any) => Promise<any> // 表单提交api
 }
 }
 
 
 // 表单的数据
 // 表单的数据
@@ -86,8 +99,7 @@ const _formOptions: ComputedRef<ProForm.FormOptions> = computed(() => {
     labelWidth: 120,
     labelWidth: 120,
     disabled: false,
     disabled: false,
     hasFooter: true,
     hasFooter: true,
-    labelSuffix: ': ',
-    showSubmitButton: true,
+    labelSuffix: ':',
     submitButtonText: '提交',
     submitButtonText: '提交',
     resetButtonText: '重置',
     resetButtonText: '重置',
     cancelButtonText: '取消'
     cancelButtonText: '取消'
@@ -135,21 +147,29 @@ watch(
   () => {
   () => {
     props.itemsOptions.map((item: ProForm.ItemsOptions) => {
     props.itemsOptions.map((item: ProForm.ItemsOptions) => {
       // 如果类型为checkbox,默认值需要设置一个空数组
       // 如果类型为checkbox,默认值需要设置一个空数组
-      const value = ['checkbox', 'transfer'].includes(item.compOptions.elTagName!) ? [] : ''
-      props.model ? (formModel.value = props.model) : (formModel.value[item.prop] = item.value || value)
+      let value = ['checkbox', 'transfer'].includes(item.compOptions.elTagName!) ? [] : props.model[item.prop]
+      props.model[item.prop] ? (formModel.value = props.model) : (formModel.value[item.prop] = item.compOptions.value || value)
     })
     })
   },
   },
   { immediate: true }
   { immediate: true }
 )
 )
-
 // 提交按钮
 // 提交按钮
 const onSubmit = (formEl: FormInstance | undefined) => {
 const onSubmit = (formEl: FormInstance | undefined) => {
   console.log('表单提交数据', formModel.value)
   console.log('表单提交数据', formModel.value)
   if (!formEl) return
   if (!formEl) return
   formEl.validate(valid => {
   formEl.validate(valid => {
     if (valid) {
     if (valid) {
-      emits('submit', formModel.value)
+      if (props.api) emits('submit', formModel.value)
+      props.api!({ ...formModel }).then(res => {
+        if (res.code == 200) {
+          resetForm(formEl)
+          ElMessage.success('操作成功')
+        } else {
+          console.log('message', res.message)
+        }
+      })
     } else {
     } else {
+      console.log('校验失败')
     }
     }
   })
   })
 }
 }

+ 3 - 4
src/components/ProTable/index.vue

@@ -11,9 +11,9 @@
       </div>
       </div>
       <div v-if="toolButton" class="header-button-ri">
       <div v-if="toolButton" class="header-button-ri">
         <slot name="toolButton">
         <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('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" />
         </slot>
         </slot>
       </div>
       </div>
     </div>
     </div>
@@ -82,7 +82,6 @@ import { useTable } from '@/hooks/useTable'
 import { useSelection } from '@/hooks/useSelection'
 import { useSelection } from '@/hooks/useSelection'
 import { BreakPoint } from '@/components/Grid/interface'
 import { BreakPoint } from '@/components/Grid/interface'
 import { ColumnProps, TypeProps } from '@/components/ProTable/interface'
 import { ColumnProps, TypeProps } from '@/components/ProTable/interface'
-import { Refresh, Operation, Search } from '@element-plus/icons-vue'
 import { handleProp } from '@/utils'
 import { handleProp } from '@/utils'
 import SearchForm from '@/components/SearchForm/index.vue'
 import SearchForm from '@/components/SearchForm/index.vue'
 import Pagination from './components/Pagination.vue'
 import Pagination from './components/Pagination.vue'

+ 3 - 3
src/components/SearchForm/index.vue

@@ -18,8 +18,8 @@
         </GridItem>
         </GridItem>
         <GridItem suffix>
         <GridItem suffix>
           <div class="operation">
           <div class="operation">
-            <el-button type="primary" :icon="Search" @click="search"> 搜索 </el-button>
-            <el-button :icon="Delete" @click="reset"> 重置 </el-button>
+            <el-button type="primary" icon="Search" @click="search"> 搜索 </el-button>
+            <el-button icon="Delete" @click="reset"> 重置 </el-button>
             <el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
             <el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
               {{ collapsed ? '展开' : '合并' }}
               {{ collapsed ? '展开' : '合并' }}
               <el-icon class="el-icon--right">
               <el-icon class="el-icon--right">
@@ -36,7 +36,7 @@
 import { computed, ref } from 'vue'
 import { computed, ref } from 'vue'
 import { ColumnProps } from '@/components/ProTable/interface'
 import { ColumnProps } from '@/components/ProTable/interface'
 import { BreakPoint } from '@/components/Grid/interface'
 import { BreakPoint } from '@/components/Grid/interface'
-import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
+import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
 import SearchFormItem from './components/SearchFormItem.vue'
 import SearchFormItem from './components/SearchFormItem.vue'
 import Grid from '@/components/Grid/index.vue'
 import Grid from '@/components/Grid/index.vue'
 import GridItem from '@/components/Grid/components/GridItem.vue'
 import GridItem from '@/components/Grid/components/GridItem.vue'

+ 1 - 1
src/components/SelectIcon/index.vue

@@ -10,7 +10,7 @@
       @click="openDialog"
       @click="openDialog"
     >
     >
       <template #append>
       <template #append>
-        <el-button :icon="customIcons[iconValue]" />
+        <el-button icon="customIcons[iconValue]" />
       </template>
       </template>
     </el-input>
     </el-input>
     <el-dialog v-model="dialogVisible" :title="placeholder" top="10%" width="40%">
     <el-dialog v-model="dialogVisible" :title="placeholder" top="10%" width="40%">

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

@@ -68,7 +68,6 @@ let parameter = withDefaults(defineProps<TableParameterProps>(), {
 })
 })
 // ProTable 实例
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 const proTable = ref<ProTableInstance>()
-
 // 定义 emit 事件
 // 定义 emit 事件
 const emit = defineEmits<{
 const emit = defineEmits<{
   submitForm: []
   submitForm: []

+ 96 - 234
src/components/Upload/File.vue

@@ -3,7 +3,7 @@
     <el-upload
     <el-upload
       ref="uploadRef"
       ref="uploadRef"
       :action="uploadFileUrl"
       :action="uploadFileUrl"
-      v-model:file-list="_fileList"
+      :file-list="_fileList"
       class="upload-file-uploader"
       class="upload-file-uploader"
       :show-file-list="false"
       :show-file-list="false"
       :multiple="true"
       :multiple="true"
@@ -16,7 +16,7 @@
       :accept="fileType.join(',')"
       :accept="fileType.join(',')"
       :headers="headers"
       :headers="headers"
     >
     >
-      <el-button :icon="icon" type="primary">{{ text }}</el-button>
+      <el-button icon="icon" type="primary">{{ text }}</el-button>
     </el-upload>
     </el-upload>
     <!-- 上传提示 -->
     <!-- 上传提示 -->
     <div class="el-upload__tip" v-if="showTip">
     <div class="el-upload__tip" v-if="showTip">
@@ -32,9 +32,9 @@
     <!-- 文件列表 -->
     <!-- 文件列表 -->
     <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
     <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">
       <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">
+        <el-link :href="`${file.url}`" :underline="false" target="_blank">
           <span class="document">
           <span class="document">
-            {{ getFileName(file.name) }}
+            {{ file.name }}
           </span>
           </span>
         </el-link>
         </el-link>
         <div class="ele-upload-list__item-content-action">
         <div class="ele-upload-list__item-content-action">
@@ -47,47 +47,47 @@
 
 
 <script setup lang="ts" name="UploadImgs">
 <script setup lang="ts" name="UploadImgs">
 import { ref, computed, inject, watch } from 'vue'
 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'
+import type { UploadProps, UploadFile } from 'element-plus'
+import { ElMessage, formContextKey, formItemContextKey, UploadInstance } from 'element-plus'
+import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
+import { globalHeaders } from '@/api'
+import { OssVO } from '@/api/interface/system/oss'
+import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
+import { listToString } from '@/utils/common'
 interface UploadFileProps {
 interface UploadFileProps {
-  modelValue?: any
+  modelValue?: string | number
   disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
   disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
   drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
   drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
   limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
   limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
   fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
   fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
-  height?: string // 组件高度 ==> 非必传(默认为 150px)
-  width?: string // 组件宽度 ==> 非必传(默认为 150px)
-  borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
   isShowTip?: boolean // 是否显示提示信息 ==> 非必传(默认为 true)
   isShowTip?: boolean // 是否显示提示信息 ==> 非必传(默认为 true)
   text?: string // 按钮文字
   text?: string // 按钮文字
   icon?: string
   icon?: string
-  fileList?: UploadUserFile[]
-  fileType?: Array<any>
+  fileType?: Array<string>
 }
 }
-// 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>(), {
 const props = withDefaults(defineProps<UploadFileProps>(), {
-  fileList: () => [],
+  modelValue: () => '',
   drag: true,
   drag: true,
   disabled: false,
   disabled: false,
   limit: 1,
   limit: 1,
-  fileSize: 500,
-  fileType: () => ['doc', 'xls', 'xlsx', 'ppt', 'txt', 'pdf'],
-  height: '150px',
-  width: '150px',
-  borderRadius: '8px',
+  fileSize: 5,
+  fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'txt', 'pdf'],
   text: '文件上传',
   text: '文件上传',
-  isShowTip: false
+  isShowTip: true
 })
 })
 
 
+const baseUrl = import.meta.env.VITE_API_URL
+const uploadFileUrl = ref(baseUrl + '/common/upload') // 上传文件服务器地址
+const headers = ref(globalHeaders())
+const uploadRef = ref<UploadInstance>()
+const number = ref(0)
+const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
+const uploadList = ref<any[]>([])
+const emit = defineEmits<{
+  'update:modelValue': [value: any]
+}>()
+
 // 获取 el-form 组件上下文
 // 获取 el-form 组件上下文
 const formContext = inject(formContextKey, void 0)
 const formContext = inject(formContextKey, void 0)
 // 获取 el-form-item 组件上下文
 // 获取 el-form-item 组件上下文
@@ -97,25 +97,41 @@ const self_disabled = computed(() => {
   return props.disabled || formContext?.disabled
   return props.disabled || formContext?.disabled
 })
 })
 
 
-const _fileList = ref<UploadUserFile[]>(props.fileList)
+const _fileList = ref<any[]>([])
 
 
-// 获取文件名称
-const getFileName = name => {
-  if (name.lastIndexOf('/') > -1) {
-    return name.slice(name.lastIndexOf('/') + 1)
-  } else {
-    return ''
-  }
-}
-
-// 监听 props.fileList 列表默认值改变
+// 监听 props.modelValue 列表默认值改变
 watch(
 watch(
-  () => props.fileList,
-  (n: UploadUserFile[]) => {
-    _fileList.value = n
-  }
+  () => props.modelValue,
+  async (val: string | number) => {
+    if (val) {
+      let temp = 1
+      // 首先将值转为数组
+      let list: any[] = []
+      if (Array.isArray(val)) {
+        list = val as OssVO[]
+      } else {
+        const res = await getListByIdsApi(val)
+        list = res.data.map(oss => {
+          return {
+            name: oss.originalName,
+            url: oss.url,
+            ossId: oss.ossId
+          }
+        })
+      }
+      // 然后将数组转为对象数组
+      _fileList.value = list.map(item => {
+        item = { name: item.name, url: item.url, ossId: item.ossId }
+        item.uid = item.uid || new Date().getTime() + temp++
+        return item
+      })
+    } else {
+      _fileList.value = []
+      return []
+    }
+  },
+  { deep: true, immediate: true }
 )
 )
-
 /**
 /**
  * @description 文件上传之前判断
  * @description 文件上传之前判断
  * @param rawFile 选择的文件
  * @param rawFile 选择的文件
@@ -127,21 +143,16 @@ const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
   const isTypeOk = props.fileType.indexOf(fileExt) >= 0
   const isTypeOk = props.fileType.indexOf(fileExt) >= 0
   // 校检文件大小
   // 校检文件大小
   const isLt = rawFile.size / 1024 / 1024 < props.fileSize
   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)
+  if (!isTypeOk) {
+    ElMessage.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`)
+    return false
+  }
+  if (!isLt) {
+    ElMessage.error(`文件大小不能超过 ${props.fileSize}M!`)
+    return false
+  }
+  number.value++
+  showFullScreenLoading('正在上传文件,请稍候...')
   return isTypeOk && isLt
   return isTypeOk && isLt
 }
 }
 
 
@@ -151,28 +162,29 @@ const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
  * @param uploadFile 上传的文件
  * @param uploadFile 上传的文件
  * */
  * */
 const uploadSuccess = (response: any | undefined, uploadFile: 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 {
+  if (response.code !== 200) {
+    number.value--
     ElMessage.error(response.msg)
     ElMessage.error(response.msg)
     uploadRef.value?.handleRemove(uploadFile)
     uploadRef.value?.handleRemove(uploadFile)
+    uploadedSuccessfully()
+    return
+  }
+  uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+  uploadedSuccessfully()
+}
+
+// 上传结束处理
+const uploadedSuccessfully = () => {
+  debugger
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
+    uploadList.value = []
+    number.value = 0
+    emit('update:modelValue', listToString(_fileList.value))
+    tryHideFullScreenLoading()
   }
   }
-  //   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 });
+  // 监听表单验证
+  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
 }
 }
 
 
 /**
 /**
@@ -180,178 +192,28 @@ const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
  * @param file 删除的文件
  * @param file 删除的文件
  * */
  * */
 const handleRemove = (index: number) => {
 const handleRemove = (index: number) => {
-  // _fileList.value = _fileList.value.filter(item => item.url !== file.url || item.name !== file.name)
+  let ossId = _fileList.value[index].ossId
+  delOssApi(ossId)
   _fileList.value.splice(index, 1)
   _fileList.value.splice(index, 1)
-  emit('update:modelValue', _fileList.value)
+  emit('update:modelValue', listToString(_fileList.value))
 }
 }
 
 
 /**
 /**
  * @description 文件上传错误
  * @description 文件上传错误
  * */
  * */
 const uploadError = () => {
 const uploadError = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: '文件上传失败,请您重新上传!',
-    type: 'error'
-  })
+  ElMessage.error('文件上传失败,请您重新上传!')
 }
 }
 
 
 /**
 /**
  * @description 文件数超出
  * @description 文件数超出
  * */
  * */
 const handleExceed = () => {
 const handleExceed = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: `当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`,
-    type: 'warning'
-  })
+  ElMessage.warning(`当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`)
 }
 }
-
-/**
- * @description 图片预览
- * @param file 预览的文件
- * */
-// const viewImageUrl = ref('')
-// const imgViewVisible = ref(false)
-// const handlePictureCardPreview: UploadProps['onPreview'] = file => {
-//   viewImageUrl.value = file.url!
-//   imgViewVisible.value = true
-// }
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <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 {
 .upload-file-uploader {
   margin-bottom: 5px;
   margin-bottom: 5px;
 }
 }

+ 235 - 0
src/components/Upload/FileS3.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="upload-file">
+    <el-upload
+      ref="uploadRef"
+      :action="uploadFileUrl"
+      :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="`${file.url}`" :underline="false" target="_blank">
+          <span class="document">
+            {{ 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 } from 'element-plus'
+import { ElMessage, formContextKey, formItemContextKey, UploadInstance } from 'element-plus'
+import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
+import { globalHeaders } from '@/api'
+import { OssVO } from '@/api/interface/system/oss'
+import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
+import { listToString } from '@/utils/common'
+interface UploadFileProps {
+  modelValue?: string | number
+  disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
+  drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
+  fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
+  isShowTip?: boolean // 是否显示提示信息 ==> 非必传(默认为 true)
+  text?: string // 按钮文字
+  icon?: string
+  fileType?: Array<string>
+}
+// 默认值
+const props = withDefaults(defineProps<UploadFileProps>(), {
+  modelValue: () => '',
+  drag: true,
+  disabled: false,
+  limit: 1,
+  fileSize: 5,
+  fileType: () => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'txt', 'pdf'],
+  text: '文件上传',
+  isShowTip: true
+})
+
+const baseUrl = import.meta.env.VITE_API_URL
+const uploadFileUrl = ref(baseUrl + '/resource/oss/upload') // 上传文件服务器地址
+const headers = ref(globalHeaders())
+const uploadRef = ref<UploadInstance>()
+const number = ref(0)
+const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
+const uploadList = ref<any[]>([])
+const emit = defineEmits<{
+  'update:modelValue': [value: any]
+}>()
+
+// 获取 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<any[]>([])
+
+// 监听 props.modelValue 列表默认值改变
+watch(
+  () => props.modelValue,
+  async (val: string | number) => {
+    if (val) {
+      let temp = 1
+      // 首先将值转为数组
+      let list: any[] = []
+      if (Array.isArray(val)) {
+        list = val as OssVO[]
+      } else {
+        const res = await getListByIdsApi(val)
+        list = res.data.map(oss => {
+          return {
+            name: oss.originalName,
+            url: oss.url,
+            ossId: oss.ossId
+          }
+        })
+      }
+      // 然后将数组转为对象数组
+      _fileList.value = list.map(item => {
+        item = { name: item.name, url: item.url, ossId: item.ossId }
+        item.uid = item.uid || new Date().getTime() + temp++
+        return item
+      })
+    } else {
+      _fileList.value = []
+      return []
+    }
+  },
+  { deep: true, immediate: true }
+)
+/**
+ * @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) {
+    ElMessage.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`)
+    return false
+  }
+  if (!isLt) {
+    ElMessage.error(`文件大小不能超过 ${props.fileSize}M!`)
+    return false
+  }
+  number.value++
+  showFullScreenLoading('正在上传文件,请稍候...')
+  return isTypeOk && isLt
+}
+
+/**
+ * @description 文件上传成功
+ * @param response 上传响应结果
+ * @param uploadFile 上传的文件
+ * */
+const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
+  if (response.code !== 200) {
+    number.value--
+    ElMessage.error(response.msg)
+    uploadRef.value?.handleRemove(uploadFile)
+    uploadedSuccessfully()
+    return
+  }
+  uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+  uploadedSuccessfully()
+}
+
+// 上传结束处理
+const uploadedSuccessfully = () => {
+  debugger
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
+    uploadList.value = []
+    number.value = 0
+    emit('update:modelValue', listToString(_fileList.value))
+    tryHideFullScreenLoading()
+  }
+  // 监听表单验证
+  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
+}
+
+/**
+ * @description 删除图片
+ * @param file 删除的文件
+ * */
+const handleRemove = (index: number) => {
+  let ossId = _fileList.value[index].ossId
+  delOssApi(ossId)
+  _fileList.value.splice(index, 1)
+  emit('update:modelValue', listToString(_fileList.value))
+}
+
+/**
+ * @description 文件上传错误
+ * */
+const uploadError = () => {
+  ElMessage.error('文件上传失败,请您重新上传!')
+}
+
+/**
+ * @description 文件数超出
+ * */
+const handleExceed = () => {
+  ElMessage.warning(`当前最多只能上传 ${props.limit} 个文件 ,请移除后上传!`)
+}
+</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;
+}
+</style>

+ 122 - 77
src/components/Upload/Imgs.vue

@@ -1,18 +1,18 @@
 <template>
 <template>
   <div class="upload-box">
   <div class="upload-box">
     <el-upload
     <el-upload
-      v-model:file-list="_fileList"
-      action="#"
+      :file-list="_fileList"
+      :action="uploadImgUrl"
       list-type="picture-card"
       list-type="picture-card"
-      :class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '']"
-      :multiple="true"
-      :disabled="self_disabled"
-      :limit="limit"
-      :http-request="handleHttpUpload"
       :before-upload="beforeUpload"
       :before-upload="beforeUpload"
-      :on-exceed="handleExceed"
       :on-success="uploadSuccess"
       :on-success="uploadSuccess"
+      :on-exceed="handleExceed"
       :on-error="uploadError"
       :on-error="uploadError"
+      :class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '', { hide: _fileList.length >= limit }]"
+      :multiple="true"
+      :headers="headers"
+      :disabled="self_disabled"
+      :limit="limit"
       :drag="drag"
       :drag="drag"
       :accept="fileType.join(',')"
       :accept="fileType.join(',')"
     >
     >
@@ -36,6 +36,16 @@
         </div>
         </div>
       </template>
       </template>
     </el-upload>
     </el-upload>
+    <div v-if="showTip" class="el-upload__tip">
+      请上传
+      <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>
     <div class="el-upload__tip">
     <div class="el-upload__tip">
       <slot name="tip"></slot>
       <slot name="tip"></slot>
     </div>
     </div>
@@ -46,13 +56,17 @@
 <script setup lang="ts" name="UploadImgs">
 <script setup lang="ts" name="UploadImgs">
 import { ref, computed, inject, watch } from 'vue'
 import { ref, computed, inject, watch } from 'vue'
 import { Plus } from '@element-plus/icons-vue'
 import { Plus } from '@element-plus/icons-vue'
-import { uploadImg } from '@/api/modules/upload'
-import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from 'element-plus'
-import { ElNotification, formContextKey, formItemContextKey } from 'element-plus'
-
+import type { UploadProps, UploadFile } from 'element-plus'
+import { ElMessage, formContextKey, formItemContextKey } from 'element-plus'
+import { listToString } from '@/utils/common'
+import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
+import { compressAccurately } from 'image-conversion'
+import { OssVO } from '@/api/interface/system/oss'
+import { globalHeaders } from '@/api'
+import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
+import { ResultData } from '@/api/interface'
 interface UploadFileProps {
 interface UploadFileProps {
-  fileList: UploadUserFile[]
-  api?: (params: any) => Promise<any> // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传
+  modelValue: string | number
   drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
   drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
   disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
   disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
   limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
   limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
@@ -60,21 +74,30 @@ interface UploadFileProps {
   fileType?: File.ImageMimeType[] // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
   fileType?: File.ImageMimeType[] // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
   height?: string // 组件高度 ==> 非必传(默认为 150px)
   height?: string // 组件高度 ==> 非必传(默认为 150px)
   width?: string // 组件宽度 ==> 非必传(默认为 150px)
   width?: string // 组件宽度 ==> 非必传(默认为 150px)
+  isShowTip?: boolean
   borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
   borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  compressSupport?: boolean // 是否支持图片压缩 ==> 非必传(默认为 false)
+  compressTargetSize?: number // 图片压缩目标大小 ==> 非必传(默认为 300kb)
 }
 }
 
 
 const props = withDefaults(defineProps<UploadFileProps>(), {
 const props = withDefaults(defineProps<UploadFileProps>(), {
-  fileList: () => [],
+  modelValue: () => '',
   drag: true,
   drag: true,
   disabled: false,
   disabled: false,
   limit: 5,
   limit: 5,
   fileSize: 5,
   fileSize: 5,
+  isShowTip: true,
+  compressSupport: false,
+  compressTargetSize: 300,
   fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
   fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
   height: '150px',
   height: '150px',
   width: '150px',
   width: '150px',
   borderRadius: '8px'
   borderRadius: '8px'
 })
 })
 
 
+const baseUrl = import.meta.env.VITE_API_URL
+const uploadImgUrl = ref(baseUrl + '/common/upload') // 上传的图片服务器地址
+const headers = ref(globalHeaders())
 // 获取 el-form 组件上下文
 // 获取 el-form 组件上下文
 const formContext = inject(formContextKey, void 0)
 const formContext = inject(formContextKey, void 0)
 // 获取 el-form-item 组件上下文
 // 获取 el-form-item 组件上下文
@@ -83,15 +106,42 @@ const formItemContext = inject(formItemContextKey, void 0)
 const self_disabled = computed(() => {
 const self_disabled = computed(() => {
   return props.disabled || formContext?.disabled
   return props.disabled || formContext?.disabled
 })
 })
-
-const _fileList = ref<UploadUserFile[]>(props.fileList)
-
-// 监听 props.fileList 列表默认值改变
+const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
+const _fileList = ref<any[]>([])
+const uploadList = ref<any[]>([])
+const number = ref(0)
+const imageUploadRef = ref<ElUploadInstance>()
+// 监听 props.modelValue 列表默认值改变
 watch(
 watch(
-  () => props.fileList,
-  (n: UploadUserFile[]) => {
-    _fileList.value = n
-  }
+  () => props.modelValue,
+  async (val: string | number) => {
+    if (val) {
+      // 首先将值转为数组
+      let list: OssVO[] = []
+      if (Array.isArray(val)) {
+        list = val as OssVO[]
+      } else {
+        const res = await getListByIdsApi(val)
+        list = res.data
+      }
+      // 然后将数组转为对象数组
+      _fileList.value = list.map(item => {
+        // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
+        let itemData
+        if (typeof item === 'string') {
+          itemData = { name: item, url: item }
+        } else {
+          // 此处name使用ossId 防止删除出现重名
+          itemData = { name: item.ossId, url: item.url, ossId: item.ossId }
+        }
+        return itemData
+      })
+    } else {
+      _fileList.value = []
+      return []
+    }
+  },
+  { deep: true, immediate: true }
 )
 )
 
 
 /**
 /**
@@ -101,37 +151,21 @@ watch(
 const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
 const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
   const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
   const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType)
   const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType)
-  if (!imgType)
-    ElNotification({
-      title: '温馨提示',
-      message: '上传图片不符合所需的格式!',
-      type: 'warning'
-    })
-  if (!imgSize)
-    setTimeout(() => {
-      ElNotification({
-        title: '温馨提示',
-        message: `上传图片大小不能超过 ${props.fileSize}M!`,
-        type: 'warning'
-      })
-    }, 0)
-  return imgType && imgSize
-}
-
-/**
- * @description 图片上传
- * @param options upload 所有配置项
- * */
-const handleHttpUpload = async (options: UploadRequestOptions) => {
-  let formData = new FormData()
-  formData.append('file', options.file)
-  try {
-    const api = props.api ?? uploadImg
-    const { data } = await api(formData)
-    options.onSuccess(data)
-  } catch (error) {
-    options.onError(error as any)
+  if (!imgType) {
+    ElMessage.error(`上传图片不符合所需的格式,  请上传${props.fileType.join('/')}图片格式文件!`)
+    return false
   }
   }
+  if (!imgSize) {
+    ElMessage.error('图片大小不能超过 ' + props.fileSize + 'M!')
+    return false
+  }
+  if (props.compressSupport && rawFile.size / 1024 > props.compressTargetSize) {
+    return compressAccurately(rawFile, props.compressTargetSize)
+  }
+  showFullScreenLoading('正在上传图片,请稍候...')
+  number.value++
+  console.log('number', number.value)
+  return imgType && imgSize
 }
 }
 
 
 /**
 /**
@@ -140,19 +174,31 @@ const handleHttpUpload = async (options: UploadRequestOptions) => {
  * @param uploadFile 上传的文件
  * @param uploadFile 上传的文件
  * */
  * */
 const emit = defineEmits<{
 const emit = defineEmits<{
-  'update:fileList': [value: UploadUserFile[]]
+  'update:modelValue': [value: string]
 }>()
 }>()
-const uploadSuccess = (response: { fileUrl: string } | undefined, uploadFile: UploadFile) => {
-  if (!response) return
-  uploadFile.url = response.fileUrl
-  emit('update:fileList', _fileList.value)
-  // 调用 el-form 内部的校验方法(可自动校验)
+const uploadSuccess = (response: ResultData, uploadFile: UploadFile) => {
+  if (response.code !== 200) {
+    number.value--
+    ElMessage.error(response.msg)
+    imageUploadRef.value?.handleRemove(uploadFile)
+    uploadedSuccessfully()
+    return
+  }
+  uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+  uploadedSuccessfully()
+}
+
+// 上传结束处理
+const uploadedSuccessfully = () => {
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
+    uploadList.value = []
+    number.value = 0
+    emit('update:modelValue', listToString(_fileList.value))
+    tryHideFullScreenLoading()
+  }
+  // 监听表单验证
   formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
   formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
-  ElNotification({
-    title: '温馨提示',
-    message: '图片上传成功!',
-    type: 'success'
-  })
 }
 }
 
 
 /**
 /**
@@ -160,30 +206,30 @@ const uploadSuccess = (response: { fileUrl: string } | undefined, uploadFile: Up
  * @param file 删除的文件
  * @param file 删除的文件
  * */
  * */
 const handleRemove = (file: UploadFile) => {
 const handleRemove = (file: UploadFile) => {
-  _fileList.value = _fileList.value.filter(item => item.url !== file.url || item.name !== file.name)
-  emit('update:fileList', _fileList.value)
+  const fIndex = _fileList.value.map(f => f.name).indexOf(file.name)
+  if (fIndex > -1 && uploadList.value.length === number.value) {
+    let ossId = _fileList.value[fIndex].ossId
+    delOssApi(ossId)
+    _fileList.value.splice(fIndex, 1)
+    emit('update:modelValue', listToString(_fileList.value))
+    formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
+    return false
+  }
+  return true
 }
 }
 
 
 /**
 /**
  * @description 图片上传错误
  * @description 图片上传错误
  * */
  * */
 const uploadError = () => {
 const uploadError = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: '图片上传失败,请您重新上传!',
-    type: 'error'
-  })
+  ElMessage.error('图片上传失败,请您重新上传!')
 }
 }
 
 
 /**
 /**
  * @description 文件数超出
  * @description 文件数超出
  * */
  * */
 const handleExceed = () => {
 const handleExceed = () => {
-  ElNotification({
-    title: '温馨提示',
-    message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
-    type: 'warning'
-  })
+  ElMessage.warning(`当前最多只能上传 ${props.limit} 张图片,请移除后上传!`)
 }
 }
 
 
 /**
 /**
@@ -310,7 +356,6 @@ const handlePictureCardPreview: UploadProps['onPreview'] = file => {
   }
   }
   .el-upload__tip {
   .el-upload__tip {
     line-height: 15px;
     line-height: 15px;
-    text-align: center;
   }
   }
 }
 }
 </style>
 </style>

+ 361 - 0
src/components/Upload/ImgsS3.vue

@@ -0,0 +1,361 @@
+<template>
+  <div class="upload-box">
+    <el-upload
+      :file-list="_fileList"
+      :action="uploadImgUrl"
+      list-type="picture-card"
+      :before-upload="beforeUpload"
+      :on-success="uploadSuccess"
+      :on-exceed="handleExceed"
+      :on-error="uploadError"
+      :class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '', { hide: _fileList.length >= limit }]"
+      :multiple="true"
+      :headers="headers"
+      :disabled="self_disabled"
+      :limit="limit"
+      :drag="drag"
+      :accept="fileType.join(',')"
+    >
+      <div class="upload-empty">
+        <slot name="empty">
+          <el-icon><Plus /></el-icon>
+          <!-- <span>请上传图片</span> -->
+        </slot>
+      </div>
+      <template #file="{ file }">
+        <img :src="file.url" class="upload-image" />
+        <div class="upload-handle" @click.stop>
+          <div class="handle-icon" @click="handlePictureCardPreview(file)">
+            <el-icon><ZoomIn /></el-icon>
+            <span>查看</span>
+          </div>
+          <div v-if="!self_disabled" class="handle-icon" @click="handleRemove(file)">
+            <el-icon><Delete /></el-icon>
+            <span>删除</span>
+          </div>
+        </div>
+      </template>
+    </el-upload>
+    <div v-if="showTip" class="el-upload__tip">
+      请上传
+      <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>
+    <div class="el-upload__tip">
+      <slot name="tip"></slot>
+    </div>
+    <el-image-viewer v-if="imgViewVisible" :url-list="[viewImageUrl]" @close="imgViewVisible = false" />
+  </div>
+</template>
+
+<script setup lang="ts" name="UploadImgs">
+import { ref, computed, inject, watch } from 'vue'
+import { Plus } from '@element-plus/icons-vue'
+import type { UploadProps, UploadFile } from 'element-plus'
+import { ElMessage, formContextKey, formItemContextKey } from 'element-plus'
+import { listToString } from '@/utils/common'
+import { showFullScreenLoading, tryHideFullScreenLoading } from '@/components/Loading/fullScreen'
+import { compressAccurately } from 'image-conversion'
+import { OssVO } from '@/api/interface/system/oss'
+import { globalHeaders } from '@/api'
+import { getListByIdsApi, delOssApi } from '@/api/modules/system/oss'
+import { ResultData } from '@/api/interface'
+interface UploadFileProps {
+  modelValue: string | number
+  drag?: boolean // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  disabled?: boolean // 是否禁用上传组件 ==> 非必传(默认为 false)
+  limit?: number // 最大图片上传数 ==> 非必传(默认为 5张)
+  fileSize?: number // 图片大小限制 ==> 非必传(默认为 5M)
+  fileType?: File.ImageMimeType[] // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
+  height?: string // 组件高度 ==> 非必传(默认为 150px)
+  width?: string // 组件宽度 ==> 非必传(默认为 150px)
+  isShowTip?: boolean
+  borderRadius?: string // 组件边框圆角 ==> 非必传(默认为 8px)
+  compressSupport?: boolean // 是否支持图片压缩 ==> 非必传(默认为 false)
+  compressTargetSize?: number // 图片压缩目标大小 ==> 非必传(默认为 300kb)
+}
+
+const props = withDefaults(defineProps<UploadFileProps>(), {
+  modelValue: () => '',
+  drag: true,
+  disabled: false,
+  limit: 5,
+  fileSize: 5,
+  isShowTip: true,
+  compressSupport: false,
+  compressTargetSize: 300,
+  fileType: () => ['image/jpeg', 'image/png', 'image/gif'],
+  height: '150px',
+  width: '150px',
+  borderRadius: '8px'
+})
+
+const baseUrl = import.meta.env.VITE_API_URL
+const uploadImgUrl = ref(baseUrl + '/resource/oss/upload') // 上传的图片服务器地址
+const headers = ref(globalHeaders())
+// 获取 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 showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
+const _fileList = ref<any[]>([])
+const uploadList = ref<any[]>([])
+const number = ref(0)
+const imageUploadRef = ref<ElUploadInstance>()
+// 监听 props.modelValue 列表默认值改变
+watch(
+  () => props.modelValue,
+  async (val: string | number) => {
+    if (val) {
+      // 首先将值转为数组
+      let list: OssVO[] = []
+      if (Array.isArray(val)) {
+        list = val as OssVO[]
+      } else {
+        const res = await getListByIdsApi(val)
+        list = res.data
+      }
+      // 然后将数组转为对象数组
+      _fileList.value = list.map(item => {
+        // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
+        let itemData
+        if (typeof item === 'string') {
+          itemData = { name: item, url: item }
+        } else {
+          // 此处name使用ossId 防止删除出现重名
+          itemData = { name: item.ossId, url: item.url, ossId: item.ossId }
+        }
+        return itemData
+      })
+    } else {
+      _fileList.value = []
+      return []
+    }
+  },
+  { deep: true, immediate: true }
+)
+
+/**
+ * @description 文件上传之前判断
+ * @param rawFile 选择的文件
+ * */
+const beforeUpload: UploadProps['beforeUpload'] = rawFile => {
+  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+  const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType)
+  if (!imgType) {
+    ElMessage.error(`上传图片不符合所需的格式,  请上传${props.fileType.join('/')}图片格式文件!`)
+    return false
+  }
+  if (!imgSize) {
+    ElMessage.error('图片大小不能超过 ' + props.fileSize + 'M!')
+    return false
+  }
+  if (props.compressSupport && rawFile.size / 1024 > props.compressTargetSize) {
+    return compressAccurately(rawFile, props.compressTargetSize)
+  }
+  showFullScreenLoading('正在上传图片,请稍候...')
+  number.value++
+  console.log('number', number.value)
+  return imgType && imgSize
+}
+
+/**
+ * @description 图片上传成功
+ * @param response 上传响应结果
+ * @param uploadFile 上传的文件
+ * */
+const emit = defineEmits<{
+  'update:modelValue': [value: string]
+}>()
+const uploadSuccess = (response: ResultData, uploadFile: UploadFile) => {
+  if (response.code !== 200) {
+    number.value--
+    ElMessage.error(response.msg)
+    imageUploadRef.value?.handleRemove(uploadFile)
+    uploadedSuccessfully()
+    return
+  }
+  uploadList.value.push({ name: response.data.fileName, url: response.data.url, ossId: response.data.ossId })
+  uploadedSuccessfully()
+}
+
+// 上传结束处理
+const uploadedSuccessfully = () => {
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
+    uploadList.value = []
+    number.value = 0
+    emit('update:modelValue', listToString(_fileList.value))
+    tryHideFullScreenLoading()
+  }
+  // 监听表单验证
+  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
+}
+
+/**
+ * @description 删除图片
+ * @param file 删除的文件
+ * */
+const handleRemove = (file: UploadFile) => {
+  const fIndex = _fileList.value.map(f => f.name).indexOf(file.name)
+  if (fIndex > -1 && uploadList.value.length === number.value) {
+    let ossId = _fileList.value[fIndex].ossId
+    delOssApi(ossId)
+    _fileList.value.splice(fIndex, 1)
+    emit('update:modelValue', listToString(_fileList.value))
+    formItemContext?.prop && formContext?.validateField([formItemContext.prop as string])
+    return false
+  }
+  return true
+}
+
+/**
+ * @description 图片上传错误
+ * */
+const uploadError = () => {
+  ElMessage.error('图片上传失败,请您重新上传!')
+}
+
+/**
+ * @description 文件数超出
+ * */
+const handleExceed = () => {
+  ElMessage.warning(`当前最多只能上传 ${props.limit} 张图片,请移除后上传!`)
+}
+
+/**
+ * @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">
+.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;
+  }
+}
+</style>

+ 98 - 0
src/components/UploadDialog/index.vue

@@ -0,0 +1,98 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    :close-on-click-modal="false"
+    :title="parameter.title"
+    :destroy-on-close="true"
+    :width="parameter.width"
+    :top="parameter.top"
+    draggable
+  >
+    <ProFrom ref="proFormRef" :items-options="itemsOptions" :form-options="_options" :model="model" />
+    <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="ImageUploadDialog">
+import { ref, computed, ComputedRef } from 'vue'
+import ProFrom from '@/components/ProForm/index.vue'
+export interface DialogProps {
+  title: string // 标题
+  width?: number // 弹框宽度
+  top?: string // 离顶部距离
+  type?: File.FileUploadType // 弹框类型 默认file-upload-s3:s3服务器上传文件
+  getTableList?: () => void // 获取表格数据的Api
+}
+// 表单的数据
+const model = ref<Record<string, any>>({ file: undefined })
+// dialog状态
+const dialogVisible = ref(false)
+const butLoading = ref(false)
+// 父组件传过来的参数
+const parameter = ref<DialogProps>({
+  title: '',
+  width: 500,
+  top: '10vh',
+  type: 'file-upload-s3'
+})
+const _options: ComputedRef<ProForm.FormOptions> = computed(() => {
+  return {
+    labelWidth: 120,
+    hasFooter: false,
+    disabled: false
+  }
+})
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '上传',
+      prop: 'file',
+      rules: [{ required: true, message: '文件不能为空', trigger: 'change' }],
+      compOptions: {
+        elTagName: parameter.value.type
+      }
+    }
+  ]
+}
+
+const proFormRef = ref<InstanceType<typeof ProFrom> | null>(null)
+
+// 表单提交校验
+const handleSubmit = () => {
+  const formEl = proFormRef.value?.proFormRef
+  console.log(proFormRef.value?.formModel)
+  butLoading.value = true
+  if (!formEl) return
+  formEl.validate(valid => {
+    if (valid) {
+      parameter.value.getTableList && parameter.value.getTableList()
+      dialogVisible.value = false
+    }
+    butLoading.value = false
+  })
+}
+
+// 取消按钮,重置表单,关闭弹框
+const handleCancel = () => {
+  butLoading.value = false
+  dialogVisible.value = false
+}
+
+// 接收父组件参数
+const openDialog = (params: DialogProps) => {
+  parameter.value = { ...parameter.value, ...params }
+  butLoading.value = false
+  setItemsOptions()
+  dialogVisible.value = true
+}
+
+defineExpose({
+  openDialog
+})
+</script>

+ 30 - 0
src/components/iFrame/index.vue

@@ -0,0 +1,30 @@
+<template>
+  <div v-loading="loading" :style="'height:' + height">
+    <iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+
+interface IFrameProps {
+  src: string
+}
+
+const props = withDefaults(defineProps<IFrameProps>(), {
+  src: ''
+})
+
+const height = ref(document.documentElement.clientHeight - 94.5 + 'px;')
+const loading = ref(true)
+const url = computed(() => props.src)
+
+onMounted(() => {
+  setTimeout(() => {
+    loading.value = false
+  }, 300)
+  window.onresize = function temp() {
+    height.value = document.documentElement.clientHeight - 94.5 + 'px;'
+  }
+})
+</script>

+ 1 - 1
src/directives/modules/role.ts

@@ -8,7 +8,7 @@ import type { Directive, DirectiveBinding } from 'vue'
 const role: Directive = {
 const role: Directive = {
   mounted(el: HTMLElement, binding: DirectiveBinding) {
   mounted(el: HTMLElement, binding: DirectiveBinding) {
     const { value } = binding
     const { value } = binding
-    const super_admin = 'admin'
+    const super_admin = 'superadmin'
     const roles = useUserStore().roles
     const roles = useUserStore().roles
 
 
     if (value && value instanceof Array && value.length > 0) {
     if (value && value instanceof Array && value.length > 0) {

+ 15 - 0
src/enums/MenuTypeEnum.ts

@@ -0,0 +1,15 @@
+export enum MenuTypeEnum {
+  /**
+   * 目录
+   */
+  M = 'M',
+  /**
+   * 菜单
+   */
+  C = 'C',
+
+  /**
+   * 按钮
+   */
+  F = 'F'
+}

+ 99 - 2
src/hooks/useDownload.ts

@@ -1,6 +1,10 @@
-import { ElNotification, ElMessage } from 'element-plus'
-import { saveAs } from 'file-saver'
+import { ElNotification, ElMessage, ElLoading } from 'element-plus'
+import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'
 import { blobValidate } from '@/utils/common'
 import { blobValidate } from '@/utils/common'
+import axios from 'axios'
+import { globalHeaders } from '@/api'
+let downloadLoadingInstance: LoadingInstance
+import { saveAs } from 'file-saver'
 import errorCode from '@/utils/errorCode'
 import errorCode from '@/utils/errorCode'
 /**
 /**
  * @description 接收数据流生成 blob,创建链接,下载文件
  * @description 接收数据流生成 blob,创建链接,下载文件
@@ -51,3 +55,96 @@ export const useDownload = async (
     ElMessage.error('下载文件出现错误,请联系管理员!')
     ElMessage.error('下载文件出现错误,请联系管理员!')
   }
   }
 }
 }
+
+const baseURL = import.meta.env.VITE_API_URL
+
+export default {
+  async oss(ossId: string | number) {
+    const url = baseURL + '/resource/oss/download/' + ossId
+    downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' })
+    try {
+      const res = await axios({
+        method: 'get',
+        url: url,
+        responseType: 'blob',
+        headers: globalHeaders()
+      })
+      const isBlob = blobValidate(res.data)
+      if (isBlob) {
+        const blob = new Blob([res.data], { type: 'application/octet-stream' })
+        saveAs(blob, decodeURIComponent(res.headers['download-filename'] as string))
+      } else {
+        this.printErrMsg(res.data)
+      }
+      downloadLoadingInstance.close()
+    } catch (r) {
+      console.error(r)
+      ElMessage.error('下载文件出现错误,请联系管理员!')
+      downloadLoadingInstance.close()
+    }
+  },
+  async localResource(resource: string) {
+    let url = baseURL + '/common/download/resource?resource=' + encodeURI(resource)
+    axios({
+      method: 'get',
+      url: url,
+      responseType: 'blob',
+      headers: globalHeaders()
+    }).then(async res => {
+      const isLogin = await blobValidate(res.data)
+      if (isLogin) {
+        const blob = new Blob([res.data])
+        saveAs(blob, decodeURI(res.headers['download-filename']))
+      } else {
+        this.printErrMsg(res.data)
+      }
+    })
+  },
+  async localName(name: string, isDelete = true) {
+    let url = baseURL + '/common/download?fileName=' + encodeURI(name) + '&delete=' + isDelete
+    axios({
+      method: 'get',
+      url: url,
+      responseType: 'blob',
+      headers: globalHeaders()
+    }).then(async res => {
+      const isLogin = await blobValidate(res.data)
+      if (isLogin) {
+        const blob = new Blob([res.data])
+        saveAs(blob, decodeURI(res.headers['download-filename']))
+      } else {
+        this.printErrMsg(res.data)
+      }
+    })
+  },
+  async printErrMsg(data: any) {
+    const resText = await data.text()
+    const rspObj = JSON.parse(resText)
+    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
+    ElMessage.error(errMsg)
+  },
+  async zip(url: string, name: string) {
+    url = baseURL + url
+    downloadLoadingInstance = ElLoading.service({ text: '正在下载数据,请稍候', background: 'rgba(0, 0, 0, 0.7)' })
+    try {
+      const res = await axios({
+        method: 'get',
+        url: url,
+        responseType: 'blob',
+        headers: globalHeaders()
+      })
+      const isBlob = blobValidate(res.data)
+      if (isBlob) {
+        const blob = new Blob([res.data], { type: 'application/zip' })
+        saveAs(blob, name)
+      } else {
+        this.printErrMsg(res.data)
+      }
+      downloadLoadingInstance.close()
+    } catch (r) {
+      console.error(r)
+      ElMessage.error('下载文件出现错误,请联系管理员!')
+      downloadLoadingInstance.close()
+    }
+  }
+}

+ 1 - 1
src/languages/modules/en.ts

@@ -22,7 +22,7 @@ export default {
     weakMode: 'Weak mode',
     weakMode: 'Weak mode',
     fullScreen: 'Full Screen',
     fullScreen: 'Full Screen',
     exitFullScreen: 'Exit Full Screen',
     exitFullScreen: 'Exit Full Screen',
-    personalData: 'Personal Data',
+    personalCenter: 'Personal Center',
     changePassword: 'Change Password',
     changePassword: 'Change Password',
     logout: 'Logout'
     logout: 'Logout'
   }
   }

+ 1 - 1
src/languages/modules/zh.ts

@@ -22,7 +22,7 @@ export default {
     weakMode: '色弱模式',
     weakMode: '色弱模式',
     fullScreen: '全屏',
     fullScreen: '全屏',
     exitFullScreen: '退出全屏',
     exitFullScreen: '退出全屏',
-    personalData: '个人信息',
+    personalCenter: '个人中心',
     changePassword: '修改密码',
     changePassword: '修改密码',
     logout: '退出登录'
     logout: '退出登录'
   }
   }

+ 1 - 1
src/layouts/components/Footer/index.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div class="footer flx-center">
   <div class="footer flx-center">
-    <a href="https://gitee.com/gaokunw/kimi-web" target="_blank"> 2023 © km-Admin By gaokunw Technology. </a>
+    <a href="https://gitee.com/gaokunw/kimi-web" target="_blank"> 2023 © taais-Admin By gaokunw Technology. </a>
   </div>
   </div>
 </template>
 </template>
 
 

+ 21 - 18
src/layouts/components/Header/components/Avatar.vue

@@ -1,15 +1,15 @@
 <template>
 <template>
   <el-dropdown trigger="click">
   <el-dropdown trigger="click">
-    <div class="avatar">
+    <div class="avatar" v-if="userStore.avatar.indexOf('undefined') === -1">
+      <img :src="userStore.avatar" />
+    </div>
+    <div class="avatar-dft" v-else>
       <img src="@/assets/icons/avatar-user.svg" alt="avatar" />
       <img src="@/assets/icons/avatar-user.svg" alt="avatar" />
     </div>
     </div>
     <template #dropdown>
     <template #dropdown>
       <el-dropdown-menu>
       <el-dropdown-menu>
-        <el-dropdown-item @click="openDialog('infoRef')">
-          <el-icon><User /></el-icon>{{ $t('header.personalData') }}
-        </el-dropdown-item>
-        <el-dropdown-item @click="openDialog('passwordRef')">
-          <el-icon><Edit /></el-icon>{{ $t('header.changePassword') }}
+        <el-dropdown-item @click="toProfile()">
+          <el-icon><User /></el-icon>{{ $t('header.personalCenter') }}
         </el-dropdown-item>
         </el-dropdown-item>
         <el-dropdown-item divided @click="logout">
         <el-dropdown-item divided @click="logout">
           <el-icon><SwitchButton /></el-icon>{{ $t('header.logout') }}
           <el-icon><SwitchButton /></el-icon>{{ $t('header.logout') }}
@@ -17,17 +17,13 @@
       </el-dropdown-menu>
       </el-dropdown-menu>
     </template>
     </template>
   </el-dropdown>
   </el-dropdown>
-  <!-- infoDialog -->
   <InfoDialog ref="infoRef"></InfoDialog>
   <InfoDialog ref="infoRef"></InfoDialog>
-  <!-- passwordDialog -->
   <PasswordDialog ref="passwordRef"></PasswordDialog>
   <PasswordDialog ref="passwordRef"></PasswordDialog>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref } from 'vue'
 import { LOGIN_URL } from '@/config'
 import { LOGIN_URL } from '@/config'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-// import { logoutApi } from '@/api/modules/login'
 import { useUserStore } from '@/stores/modules/user'
 import { useUserStore } from '@/stores/modules/user'
 import { ElMessageBox, ElMessage } from 'element-plus'
 import { ElMessageBox, ElMessage } from 'element-plus'
 import InfoDialog from './InfoDialog.vue'
 import InfoDialog from './InfoDialog.vue'
@@ -35,7 +31,11 @@ import PasswordDialog from './PasswordDialog.vue'
 
 
 const router = useRouter()
 const router = useRouter()
 const userStore = useUserStore()
 const userStore = useUserStore()
+console.log('userStore.avatar', userStore.avatar.indexOf('undefined') !== -1)
 
 
+const toProfile = () => {
+  router.push('/system/user/profile')
+}
 // 退出登录
 // 退出登录
 const logout = () => {
 const logout = () => {
   ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
   ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
@@ -51,18 +51,21 @@ const logout = () => {
     ElMessage.success('退出登录成功!')
     ElMessage.success('退出登录成功!')
   })
   })
 }
 }
-
-// 打开修改密码和个人信息弹窗
-const infoRef = ref<InstanceType<typeof InfoDialog> | null>(null)
-const passwordRef = ref<InstanceType<typeof PasswordDialog> | null>(null)
-const openDialog = (ref: string) => {
-  if (ref == 'infoRef') infoRef.value?.openDialog()
-  if (ref == 'passwordRef') passwordRef.value?.openDialog()
-}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 .avatar {
 .avatar {
+  width: 40px;
+  height: 40px;
+  overflow: hidden;
+  cursor: pointer;
+  border-radius: 50%;
+  img {
+    width: 100%;
+    height: 100%;
+  }
+}
+.avatar-dft {
   width: 40px;
   width: 40px;
   height: 40px;
   height: 40px;
   overflow: hidden;
   overflow: hidden;

+ 51 - 6
src/routers/modules/routerData.json

@@ -3,11 +3,11 @@
   "data": [
   "data": [
     {
     {
       "path": "/index",
       "path": "/index",
-      "name": "home",
+      "name": "homeIndex",
       "component": "taais/homePage/index",
       "component": "taais/homePage/index",
       "hidden": false,
       "hidden": false,
       "meta": {
       "meta": {
-        "icon": "HomeFilled",
+        "icon": "",
         "title": "首页",
         "title": "首页",
         "link": "",
         "link": "",
         "full": false,
         "full": false,
@@ -36,7 +36,7 @@
       "component": "taais/homePage/logPage",
       "component": "taais/homePage/logPage",
       "hidden": true,
       "hidden": true,
       "meta": {
       "meta": {
-        "icon": "HomeFilled",
+        "icon": "",
         "title": "日志",
         "title": "日志",
         "link": "",
         "link": "",
         "full": false,
         "full": false,
@@ -51,7 +51,7 @@
       "component": "taais/homePage/trainResult",
       "component": "taais/homePage/trainResult",
       "hidden": true,
       "hidden": true,
       "meta": {
       "meta": {
-        "icon": "HomeFilled",
+        "icon": "",
         "title": "预测结果",
         "title": "预测结果",
         "link": "",
         "link": "",
         "full": false,
         "full": false,
@@ -66,7 +66,7 @@
       "component": "taais/homePage/inferResult",
       "component": "taais/homePage/inferResult",
       "hidden": true,
       "hidden": true,
       "meta": {
       "meta": {
-        "icon": "HomeFilled",
+        "icon": "",
         "title": "推理结果",
         "title": "推理结果",
         "link": "",
         "link": "",
         "full": false,
         "full": false,
@@ -81,7 +81,7 @@
       "component": "system/dict/data",
       "component": "system/dict/data",
       "hidden": true,
       "hidden": true,
       "meta": {
       "meta": {
-        "icon": "data",
+        "icon": "",
         "title": "字典数据",
         "title": "字典数据",
         "activeMenu": "/system/dict",
         "activeMenu": "/system/dict",
         "link": "",
         "link": "",
@@ -104,6 +104,51 @@
         "affix": false,
         "affix": false,
         "noCache": true
         "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
+      }
+    },
+    {
+      "path": "/system/user/profile",
+      "name": "UserProfile",
+      "component": "system/user/profile/index",
+      "hidden": true,
+      "meta": {
+        "icon": "",
+        "title": "个人中心",
+        "activeMenu": "/system/user",
+        "link": "",
+        "full": false,
+        "affix": false,
+        "noCache": true
+      }
+    },
+    {
+      "path": "/system/oss-config/index",
+      "name": "OssConfig",
+      "component": "system/oss/config",
+      "hidden": true,
+      "meta": {
+        "icon": "",
+        "title": "配置管理",
+        "activeMenu": "/system/oss",
+        "link": "",
+        "full": false,
+        "affix": false,
+        "noCache": true
+      }
     }
     }
   ],
   ],
   "msg": "成功"
   "msg": "成功"

+ 25 - 0
src/styles/common.scss

@@ -1,3 +1,25 @@
+/* image */
+.img-circle {
+  border-radius: 50%;
+}
+.img-lg {
+  width: 120px;
+  height: 120px;
+}
+.avatar-upload-preview {
+  position: absolute;
+  top: 50%;
+  width: 200px;
+  height: 200px;
+  overflow: hidden;
+  border-radius: 50%;
+  box-shadow: 0 0 4px #cccccc;
+  transform: translate(50%, -50%);
+}
+.pull-right {
+  float: right !important;
+}
+
 /* flex */
 /* flex */
 .flx-center {
 .flx-center {
   display: flex;
   display: flex;
@@ -29,6 +51,9 @@
   text-overflow: ellipsis;
   text-overflow: ellipsis;
   white-space: nowrap;
   white-space: nowrap;
 }
 }
+.text-center {
+  text-align: center;
+}
 
 
 /* 文字多行省略号 */
 /* 文字多行省略号 */
 .mle {
 .mle {

+ 18 - 0
src/styles/var.scss

@@ -1,2 +1,20 @@
 /* global css variable */
 /* global css variable */
 $primary-color: var(--el-color-primary);
 $primary-color: var(--el-color-primary);
+.list-group-striped > .list-group-item {
+  padding-right: 0;
+  padding-left: 0;
+  border-right: 0;
+  border-left: 0;
+  border-radius: 0;
+}
+.list-group {
+  padding-left: 0;
+  list-style: none;
+}
+.list-group-item {
+  padding: 11px 0;
+  margin-bottom: -1px;
+  font-size: 13px;
+  border-top: 1px solid #e7eaec;
+  border-bottom: 1px solid #e7eaec;
+}

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

@@ -25,6 +25,9 @@ declare namespace ProForm {
     | 'radio-group'
     | 'radio-group'
     | 'radio-button'
     | 'radio-button'
     | 'file-upload'
     | 'file-upload'
+    | 'img-upload'
+    | 'file-upload-s3'
+    | 'img-upload-s3'
     | 'slot'
     | 'slot'
     | 'rate'
     | 'rate'
     | 'slider'
     | 'slider'
@@ -33,7 +36,6 @@ declare namespace ProForm {
     | 'time-select'
     | 'time-select'
     | 'transfer' // label不显示 使用插槽吧
     | 'transfer' // label不显示 使用插槽吧
     | 'icon' // 图标
     | 'icon' // 图标
-    | 'tree'
   interface FormOptions {
   interface FormOptions {
     inline?: boolean
     inline?: boolean
     labelWidth?: string | number
     labelWidth?: string | number
@@ -41,7 +43,6 @@ declare namespace ProForm {
     disabled?: boolean // 不可编辑
     disabled?: boolean // 不可编辑
     hasFooter?: boolean // 是否显示底部操作按钮
     hasFooter?: boolean // 是否显示底部操作按钮
     labelSuffix?: ': ' | string
     labelSuffix?: ': ' | string
-    showSubmitButton?: boolean
     showResetButton?: boolean // 是否展示重置按钮
     showResetButton?: boolean // 是否展示重置按钮
     showCancelButton?: boolean // 是否展示取消按钮
     showCancelButton?: boolean // 是否展示取消按钮
     submitButtonText?: string
     submitButtonText?: string
@@ -55,7 +56,6 @@ declare namespace ProForm {
     label?: string
     label?: string
     labelWidth?: string | number // 标签宽度,例如 '50px'。 可以使用 auto。
     labelWidth?: string | number // 标签宽度,例如 '50px'。 可以使用 auto。
     prop: string // prop
     prop: string // prop
-    value?: any // 默认值
     tooltip?: string // 问号,tooltip提示
     tooltip?: string // 问号,tooltip提示
     required?: boolean
     required?: boolean
     hideLabelSuffix?: boolean // label后缀是否隐藏
     hideLabelSuffix?: boolean // label后缀是否隐藏
@@ -87,7 +87,7 @@ declare namespace ProForm {
     clearable?: boolean // 是否可清空
     clearable?: boolean // 是否可清空
     showPassword?: boolean // 是否显示切换密码图标
     showPassword?: boolean // 是否显示切换密码图标
     enum?: EnumProps[] | Ref<EnumProps[]> | ((params?: any) => Promise<any>) // 枚举字典
     enum?: EnumProps[] | Ref<EnumProps[]> | ((params?: any) => Promise<any>) // 枚举字典
-    onChange?: (value: any) => void
+    value?: string | number | boolean | any[] // 选项框值
     enumKey?: string
     enumKey?: string
     labelKey?: string
     labelKey?: string
     valueKey?: string
     valueKey?: string
@@ -115,8 +115,16 @@ declare namespace ProForm {
     checkStrictly?: boolean // 可选
     checkStrictly?: boolean // 可选
     renderAfterExpand?: boolean // 可选
     renderAfterExpand?: boolean // 可选
     controlsPosition?: 'left' | 'right' // 可选
     controlsPosition?: 'left' | 'right' // 可选
-    defaultExpandAll?: boolean
-    showCheckbox?: boolean
-    onChange?: (val: string) => void
+    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
   }
   }
 }
 }

+ 35 - 0
src/typings/element.d.ts

@@ -0,0 +1,35 @@
+import type * as ep from 'element-plus'
+declare global {
+  declare type ElTagType = 'success' | 'info' | 'warning' | 'danger' | ''
+  declare type ElFormInstance = ep.FormInstance
+  declare type ElTableInstance = ep.TableInstance
+  declare type ElUploadInstance = ep.UploadInstance
+  declare type ElScrollbarInstance = ep.ScrollbarInstance
+  declare type ElInputInstance = ep.InputInstance
+  declare type ElInputNumberInstance = ep.InputNumberInstance
+  declare type ElRadioInstance = ep.RadioInstance
+  declare type ElRadioGroupInstance = ep.RadioGroupInstance
+  declare type ElRadioButtonInstance = ep.RadioButtonInstance
+  declare type ElCheckboxInstance = ep.CheckboxInstance
+  declare type ElSwitchInstance = ep.SwitchInstance
+  declare type ElCascaderInstance = ep.CascaderInstance
+  declare type ElColorPickerInstance = ep.ColorPickerInstance
+  declare type ElRateInstance = ep.RateInstance
+  declare type ElSliderInstance = ep.SliderInstance
+
+  declare type ElTreeInstance = InstanceType<typeof ep.ElTree>
+  declare type ElTreeSelectInstance = InstanceType<typeof ep.ElTreeSelect>
+  declare type ElSelectInstance = InstanceType<typeof ep.ElSelect>
+  declare type ElCardInstance = InstanceType<typeof ep.ElCard>
+  declare type ElDialogInstance = InstanceType<typeof ep.ElDialog>
+  declare type ElCheckboxGroupInstance = InstanceType<typeof ep.ElCheckboxGroup>
+  declare type ElDatePickerInstance = InstanceType<typeof ep.ElDatePicker>
+  declare type ElTimePickerInstance = InstanceType<typeof ep.ElTimePicker>
+  declare type ElTimeSelectInstance = InstanceType<typeof ep.ElTimeSelect>
+
+  declare type TransferKey = ep.TransferKey
+  declare type CheckboxValueType = ep.CheckboxValueType
+  declare type ElFormRules = ep.FormRules
+  declare type DateModelType = ep.DateModelType
+  declare type UploadFile = ep.UploadFile
+}

+ 16 - 0
src/typings/global.d.ts

@@ -22,6 +22,7 @@ declare namespace Menu {
 
 
 /* FileType */
 /* FileType */
 declare namespace File {
 declare namespace File {
+  type FileUploadType = 'img-upload-s3' | 'img-upload' | 'file-upload-s3' | 'file-upload'
   type ImageMimeType =
   type ImageMimeType =
     | 'image/apng'
     | 'image/apng'
     | 'image/bmp'
     | 'image/bmp'
@@ -35,6 +36,12 @@ declare namespace File {
     | 'image/x-icon'
     | 'image/x-icon'
 
 
   type ExcelMimeType = 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
   type ExcelMimeType = 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+  type WordMimeType = 'application/msword' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+  type PdfMimeType = 'application/pdf'
+  type PptMimeType = 'application/vnd.ms-powerpoint' | 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
+  type TxtMimeType = 'text/plain'
+  type ZipMimeType = 'application/zip'
+  type FileMimeType = ExcelMimeType | WordMimeType | PdfMimeType | PptMimeType | TxtMimeType | ZipMimeType
 }
 }
 
 
 /* Vite */
 /* Vite */
@@ -70,3 +77,12 @@ declare const __APP_INFO__: {
   }
   }
   lastBuildTime: string
   lastBuildTime: string
 }
 }
+/**
+ * 界面字段隐藏属性
+ */
+declare interface FieldOption {
+  key: number
+  label: string
+  visible: boolean
+  children?: Array<FieldOption>
+}

+ 27 - 1
src/utils/common.ts

@@ -1,5 +1,19 @@
 import lodash from 'lodash'
 import lodash from 'lodash'
 import { unref } from 'vue'
 import { unref } from 'vue'
+
+export const addDateRange = (params: any, dateRange: any[], propName?: string) => {
+  const search = params
+  search.params = typeof search.params === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}
+  dateRange = Array.isArray(dateRange) ? dateRange : []
+  if (typeof propName === 'undefined') {
+    search.params['beginTime'] = dateRange[0]
+    search.params['endTime'] = dateRange[1]
+  } else {
+    search.params['begin' + propName] = dateRange[0]
+    search.params['end' + propName] = dateRange[1]
+  }
+  return search
+}
 /**
 /**
  * 通用防抖
  * 通用防抖
  *
  *
@@ -98,7 +112,7 @@ export function tansParams(params: { [x: string]: any }) {
 }
 }
 
 
 // 转换字符串,undefined,null等转化为""
 // 转换字符串,undefined,null等转化为""
-export function parseStrEmpty(str: string | undefined) {
+export function parseStrEmpty(str: string | number | undefined) {
   if (!str || str === 'undefined' || str === 'null') {
   if (!str || str === 'undefined' || str === 'null') {
     return ''
     return ''
   }
   }
@@ -141,3 +155,15 @@ export const handelEnum = (enumMap, item: any) => {
   }
   }
   return enumData
   return enumData
 }
 }
+
+// 对象转成指定字符串分隔
+export const listToString = (list: any[], separator?: string) => {
+  let strings = ''
+  separator = separator || ','
+  for (let i in list) {
+    if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
+      strings += list[i].ossId + separator
+    }
+  }
+  return strings != '' ? strings.substring(0, strings.length - 1) : ''
+}

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

@@ -25,8 +25,8 @@
     <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
     <el-checkbox v-model="loginForm.rememberMe">记住密码</el-checkbox>
   </el-form>
   </el-form>
   <div class="login-btn">
   <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="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>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -42,7 +42,6 @@ import { useUserStore } from '@/stores/modules/user'
 import { useTabsStore } from '@/stores/modules/tabs'
 import { useTabsStore } from '@/stores/modules/tabs'
 import { useKeepAliveStore } from '@/stores/modules/keepAlive'
 import { useKeepAliveStore } from '@/stores/modules/keepAlive'
 import { initDynamicRouter } from '@/routers/modules/dynamicRouter'
 import { initDynamicRouter } from '@/routers/modules/dynamicRouter'
-import { CircleClose, UserFilled } from '@element-plus/icons-vue'
 import type { ElForm } from 'element-plus'
 import type { ElForm } from 'element-plus'
 // import md5 from 'md5'
 // import md5 from 'md5'
 import { lodashFunc } from '@/utils/common'
 import { lodashFunc } from '@/utils/common'

+ 0 - 4
src/views/monitor/logininfor/index.vue

@@ -1,7 +1,6 @@
 <template>
 <template>
   <div class="table-box">
   <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" />
     <ImportExcel ref="dialogRef" />
   </div>
   </div>
 </template>
 </template>
@@ -10,7 +9,6 @@
 import { ref, reactive } from 'vue'
 import { ref, reactive } from 'vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { listLogininforApi } from '@/api/modules/monitor/logininfor'
 import { listLogininforApi } from '@/api/modules/monitor/logininfor'
 import { getDictsApi } from '@/api/modules/system/dictData'
 import { getDictsApi } from '@/api/modules/system/dictData'
@@ -24,8 +22,6 @@ const initParam = reactive({ type: 1 })
 // 批量添加系统访问记录
 // 批量添加系统访问记录
 const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
 const dialogRef = ref<InstanceType<typeof ImportExcel> | null>(null)
 
 
-const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
-
 // 表格配置项
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
 const columns = reactive<ColumnProps<any>[]>([
   { prop: 'infoId', label: '访问ID' },
   { prop: 'infoId', label: '访问ID' },

+ 64 - 60
src/views/system/dept/index.vue

@@ -8,18 +8,17 @@
       :request-api="listDeptApi"
       :request-api="listDeptApi"
       :init-param="initParam"
       :init-param="initParam"
       :data-callback="dataCallback"
       :data-callback="dataCallback"
+      :default-expand-all="true"
     >
     >
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader>
       <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>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <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>
-        <el-button v-if="scope.row.parentId != 0" type="primary" link :icon="Delete" v-auth="['system:dept:remove']" @click="deleteDept(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>
         </el-button>
       </template>
       </template>
@@ -29,16 +28,15 @@
   </div>
   </div>
 </template>
 </template>
 
 
-<script setup lang="tsx" name="DeptManage">
+<script setup lang="tsx" name="Dept">
 import { ref, reactive } from 'vue'
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, CirclePlus } from '@element-plus/icons-vue'
 import { handleTree } from '@/utils/common'
 import { handleTree } from '@/utils/common'
-import { listDeptApi, delDeptApi, addDeptApi, updateDeptApi, getDeptApi } from '@/api/modules/system/dept'
+import { listDeptApi, delDeptApi, addDeptApi, updateDeptApi } from '@/api/modules/system/dept'
 import { getDictsApi } from '@/api/modules/system/dictData'
 import { getDictsApi } from '@/api/modules/system/dictData'
 
 
 // ProTable 实例
 // ProTable 实例
@@ -51,7 +49,7 @@ const deptOptions = ref<any[]>([])
 const dataCallback = (res: any) => {
 const dataCallback = (res: any) => {
   const data = handleTree(res, 'deptId')
   const data = handleTree(res, 'deptId')
   deptOptions.value = data
   deptOptions.value = data
-  setFieldList()
+  setItemsOptions()
   return data
   return data
 }
 }
 
 
@@ -64,28 +62,22 @@ const deleteDept = async (params: any) => {
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
 const openDialog = async (type: number, title: string, row?: any) => {
-  let res = { data: {} }
-  if (row?.deptId) {
-    res = await getDeptApi(row?.deptId || null)
-  }
-
   // 重置表单项
   // 重置表单项
-  setFieldList()
+  setItemsOptions()
   if (row?.parentId == 0 && type == 2) {
   if (row?.parentId == 0 && type == 2) {
-    fieldList.splice(0, 1)
+    itemsOptions.splice(0, 1)
   }
   }
   // 增加子节点
   // 增加子节点
-  if (type == 4 && row?.parentId) {
-    res.data = {
-      parentId: row?.parentId
-    }
+  if (type == 4) {
+    row = { parentId: row?.deptId }
   }
   }
+
   const params = {
   const params = {
     title,
     title,
-    width: 500,
+    width: 700,
     isEdit: type !== 3,
     isEdit: type !== 3,
-    fieldList: fieldList,
-    model: type == 1 ? {} : res.data,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : row,
     api: type == 1 || type == 4 ? addDeptApi : updateDeptApi,
     api: type == 1 || type == 4 ? addDeptApi : updateDeptApi,
     getTableList: proTable.value?.getTableList
     getTableList: proTable.value?.getTableList
   }
   }
@@ -94,7 +86,6 @@ const openDialog = async (type: number, title: string, row?: any) => {
 
 
 // 表格配置项
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
 const columns = reactive<ColumnProps<any>[]>([
-  // { type: 'selection', fixed: 'left', width: 70 },
   {
   {
     prop: 'deptName',
     prop: 'deptName',
     label: '部门名称',
     label: '部门名称',
@@ -127,71 +118,84 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   },
   { prop: 'operation', label: '操作' }
   { prop: 'operation', label: '操作' }
 ])
 ])
-let fieldList: Form.FieldItem[] = []
-const setFieldList = () => {
-  fieldList = [
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
     {
     {
       label: '上级部门',
       label: '上级部门',
-      field: 'parentId',
-      enum: deptOptions.value,
-      type: 'select-tree',
-      rules: [{ required: true, message: '请选择上级部门', trigger: 'change' }],
-      options: {
+      prop: 'parentId',
+      compOptions: {
+        enum: deptOptions.value,
+        elTagName: 'tree-select',
         valueKey: 'deptId',
         valueKey: 'deptId',
-        labelKey: 'deptName',
-        children: 'children'
-      },
-      placeholder: '请输入上级部门'
+        checkStrictly: true,
+        props: {
+          value: 'deptId',
+          label: 'deptName'
+        },
+        placeholder: '请输入上级部门'
+      }
     },
     },
     {
     {
       label: '部门名称',
       label: '部门名称',
-      field: 'deptName',
+      prop: 'deptName',
       rules: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
       span: 12,
       span: 12,
-      placeholder: '请输入部门名称'
+      compOptions: {
+        placeholder: '请输入部门名称'
+      }
     },
     },
     {
     {
       label: '显示顺序',
       label: '显示顺序',
-      field: 'orderNum',
-      type: 'input-number',
+      prop: 'orderNum',
       rules: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
-      options: {
-        min: 0
-      },
       span: 12,
       span: 12,
-      placeholder: '请输入显示顺序'
+      compOptions: {
+        value: 0,
+        elTagName: 'input-number',
+        min: 0
+      }
     },
     },
     {
     {
       label: '负责人',
       label: '负责人',
-      field: 'leader',
+      prop: 'leader',
       span: 12,
       span: 12,
-      placeholder: '请输入负责人'
+      compOptions: {
+        placeholder: '请输入负责人'
+      }
     },
     },
     {
     {
       label: '联系电话',
       label: '联系电话',
-      field: 'phone',
+      prop: 'phone',
       span: 12,
       span: 12,
       rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
       rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
-      placeholder: '请输入联系电话'
+      compOptions: {
+        placeholder: '请输入联系电话'
+      }
     },
     },
     {
     {
       label: '邮箱',
       label: '邮箱',
-      field: 'email',
+      prop: 'email',
       span: 12,
       span: 12,
       rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
       rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
-      placeholder: '请输入邮箱'
+      compOptions: {
+        placeholder: '请输入邮箱'
+      }
     },
     },
     {
     {
       label: '部门状态',
       label: '部门状态',
-      field: 'status',
-      enum: () => getDictsApi('sys_normal_disable'),
-      type: 'radio',
-      options: {
-        labelKey: 'dictLabel',
-        valueKey: 'dictValue'
-      },
+      prop: 'status',
       span: 12,
       span: 12,
-      placeholder: '请选择部门状态'
+      compOptions: {
+        value: '0',
+        elTagName: 'radio-group',
+        enum: () => getDictsApi('sys_normal_disable'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue',
+        placeholder: '请选择部门状态'
+      }
     }
     }
   ]
   ]
 }
 }

+ 77 - 83
src/views/system/dict/data.vue

@@ -1,14 +1,14 @@
 <template>
 <template>
   <div class="table-box">
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam">
+    <ProTable ref="proTable" :columns="columns" row-key="dictCode" :request-api="getTableList" :init-param="initParam">
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <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: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
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:user:remove']"
           v-auth="['system:user:remove']"
-          :icon="Delete"
+          icon="Delete"
           plain
           plain
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
           @click="batchDelete(scope.selectedListIds)"
           @click="batchDelete(scope.selectedListIds)"
@@ -18,13 +18,13 @@
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <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>
-        <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>
-        <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>
       </template>
     </ProTable>
     </ProTable>
     <FormDialog ref="formDialogRef" />
     <FormDialog ref="formDialogRef" />
@@ -32,17 +32,16 @@
   </div>
   </div>
 </template>
 </template>
 
 
-<script setup lang="tsx" name="DictData">
+<script setup lang="tsx" name="Data">
 import { ref, reactive } from 'vue'
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { useDownload } from '@/hooks/useDownload'
 import { ElMessageBox } from 'element-plus'
 import { ElMessageBox } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 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 } from '@/api/modules/system/dictData'
 import { getDictApi } from '@/api/modules/system/dict'
 import { getDictApi } from '@/api/modules/system/dict'
 
 
 import { useRoute } from 'vue-router'
 import { useRoute } from 'vue-router'
@@ -64,14 +63,14 @@ const listClassOptions = ref([
 
 
 const defaultDictType = ref('')
 const defaultDictType = ref('')
 const getTableList = async (params: any) => {
 const getTableList = async (params: any) => {
-  const res = await getDictApi(route.params.dictId)
+  const res = await getDictApi(route.params && (route.params.dictId as string))
   params.dictType = res.data.dictType
   params.dictType = res.data.dictType
   defaultDictType.value = res.data.dictType
   defaultDictType.value = res.data.dictType
   return listDataApi(params)
   return listDataApi(params)
 }
 }
 // 删除用户信息
 // 删除用户信息
 const deleteDictData = async (params: any) => {
 const deleteDictData = async (params: any) => {
-  await useHandleData(delDataApi, params.dictCode, `删除【${params.dictName}】字典数据`)
+  await useHandleData(delDataApi, params.dictCode, `删除【${params.dictLabel}】字典数据`)
   proTable.value?.getTableList()
   proTable.value?.getTableList()
 }
 }
 
 
@@ -96,12 +95,13 @@ const openDialog = async (type: number, title: string, row?: any) => {
   if (row?.dictCode) {
   if (row?.dictCode) {
     res = await getDataApi(row?.dictCode || null)
     res = await getDataApi(row?.dictCode || null)
   }
   }
+  setItemsOptions()
   const params = {
   const params = {
     title,
     title,
     width: 500,
     width: 500,
     isEdit: type !== 3,
     isEdit: type !== 3,
-    fieldList: fieldList,
-    model: type == 1 ? { dictType: defaultDictType.value } : res.data,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? { dictType: defaultDictType.value, version: 0 } : { ...res.data, version: 0 },
     api: type == 1 ? addDataApi : updateDataApi,
     api: type == 1 ? addDataApi : updateDataApi,
     getTableList: proTable.value?.getTableList
     getTableList: proTable.value?.getTableList
   }
   }
@@ -144,16 +144,6 @@ const columns = reactive<ColumnProps<any>[]>([
     },
     },
     width: 120
     width: 120
   },
   },
-  {
-    prop: 'status',
-    label: '状态',
-    tag: true,
-    enum: () => getDictsApi('sys_normal_disable'),
-    search: {
-      el: 'tree-select'
-    },
-    fieldNames: { label: 'dictLabel', value: 'dictValue' }
-  },
   {
   {
     prop: 'createTime',
     prop: 'createTime',
     label: '创建时间',
     label: '创建时间',
@@ -166,66 +156,70 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   },
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
 ])
 ])
-const fieldList: Form.FieldItem[] = [
-  {
-    label: '标签类型',
-    field: 'dictType',
-    disabled: true
-  },
-  {
-    label: '数据标签',
-    field: 'dictLabel',
-    rules: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
-    placeholder: '请输入数据标签'
-  },
-  {
-    label: '数据键值',
-    field: 'dictValue',
-    rules: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
-    placeholder: '请输入数据键值'
-  },
-  {
-    label: '显示排序',
-    field: 'dictSort',
-    rules: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
-    type: 'input-number',
-    options: {
-      min: 0
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '标签类型',
+      prop: 'dictType',
+      compOptions: {
+        disabled: true
+      }
     },
     },
-    placeholder: '请输入显示排序'
-  },
-  {
-    label: '样式属性',
-    field: 'cssClass',
-    placeholder: '请输入样式属性'
-  },
-  {
-    label: '回显样式',
-    field: 'listClass',
-    type: 'select',
-    enum: listClassOptions,
-    options: {
-      valueKey: 'value',
-      labelKey: 'label'
+    {
+      label: '数据标签',
+      prop: 'dictLabel',
+      rules: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入数据标签'
+      }
     },
     },
-    placeholder: '请输入回显样式'
-  },
-  {
-    label: '状态',
-    field: 'status',
-    enum: () => getDictsApi('sys_normal_disable'),
-    type: 'radio',
-    options: {
-      labelKey: 'dictLabel',
-      valueKey: 'dictValue'
+    {
+      label: '数据键值',
+      prop: 'dictValue',
+      rules: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入数据键值'
+      }
     },
     },
-    placeholder: '请选择状态'
-  },
-  {
-    label: '备注',
-    field: 'remark',
-    type: 'textarea',
-    placeholder: '请输入备注'
-  }
-]
+    {
+      label: '显示排序',
+      prop: 'dictSort',
+      rules: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
+      compOptions: {
+        elTagName: 'input-number',
+        min: 0,
+        placeholder: '请输入显示排序'
+      }
+    },
+    {
+      label: '样式属性',
+      prop: 'cssClass',
+      compOptions: {
+        placeholder: '请输入样式属性'
+      }
+    },
+    {
+      label: '回显样式',
+      prop: 'listClass',
+      compOptions: {
+        elTagName: 'select',
+        enum: listClassOptions,
+        valueKey: 'value',
+        labelKey: 'label',
+        placeholder: '请输入回显样式'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remark',
+      compOptions: {
+        type: 'textarea',
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
 </script>
 </script>

+ 39 - 51
src/views/system/dict/index.vue

@@ -4,11 +4,11 @@
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <template #tableHeader="scope">
         <el-button type="primary" v-auth="['system:dict:add']" @click="openDialog(1, '字典类型新增')">新增</el-button>
         <el-button type="primary" v-auth="['system:dict:add']" @click="openDialog(1, '字典类型新增')">新增</el-button>
-        <el-button type="primary" v-auth="['system:dict:export']" :icon="Download" plain @click="downloadFile">导出</el-button>
+        <el-button type="primary" v-auth="['system:dict:export']" icon="Download" plain @click="downloadFile">导出</el-button>
         <el-button
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:user:remove']"
           v-auth="['system:user:remove']"
-          :icon="Delete"
+          icon="Delete"
           plain
           plain
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
           @click="batchDelete(scope.selectedListIds)"
           @click="batchDelete(scope.selectedListIds)"
@@ -18,10 +18,10 @@
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <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>
-        <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>
       </template>
     </ProTable>
     </ProTable>
     <FormDialog ref="formDialogRef" />
     <FormDialog ref="formDialogRef" />
@@ -29,7 +29,7 @@
   </div>
   </div>
 </template>
 </template>
 
 
-<script setup lang="tsx" name="DictManage">
+<script setup lang="tsx" name="Dict">
 import { ref, reactive } from 'vue'
 import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useHandleData } from '@/hooks/useHandleData'
 import { useDownload } from '@/hooks/useDownload'
 import { useDownload } from '@/hooks/useDownload'
@@ -37,11 +37,9 @@ import { useRouter } from 'vue-router'
 import { ElMessageBox } from 'element-plus'
 import { ElMessageBox } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, Download } from '@element-plus/icons-vue'
 import { listDictApi, delDictApi, addDictApi, updateDictApi, exportApi, getDictApi } from '@/api/modules/system/dict'
 import { listDictApi, delDictApi, addDictApi, updateDictApi, exportApi, getDictApi } from '@/api/modules/system/dict'
-import { getDictsApi } from '@/api/modules/system/dictData'
 
 
 // ProTable 实例
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 const proTable = ref<ProTableInstance>()
@@ -73,12 +71,13 @@ const openDialog = async (type: number, title: string, row?: any) => {
   if (row?.dictId) {
   if (row?.dictId) {
     res = await getDictApi(row?.dictId || null)
     res = await getDictApi(row?.dictId || null)
   }
   }
+  setItemsOptions()
   const params = {
   const params = {
     title,
     title,
     width: 500,
     width: 500,
     isEdit: type !== 3,
     isEdit: type !== 3,
-    fieldList: fieldList,
-    model: type == 1 ? {} : res.data,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? { version: 0 } : { ...res.data, version: 0 },
     api: type == 1 ? addDictApi : updateDictApi,
     api: type == 1 ? addDictApi : updateDictApi,
     getTableList: proTable.value?.getTableList
     getTableList: proTable.value?.getTableList
   }
   }
@@ -117,17 +116,6 @@ const columns = reactive<ColumnProps<any>[]>([
     },
     },
     width: 150
     width: 150
   },
   },
-  {
-    prop: 'status',
-    label: '状态',
-    tag: true,
-    enum: () => getDictsApi('sys_normal_disable'),
-    search: {
-      el: 'tree-select'
-    },
-    fieldNames: { label: 'dictLabel', value: 'dictValue' },
-    width: 80
-  },
   {
   {
     prop: 'createTime',
     prop: 'createTime',
     label: '创建时间'
     label: '创建时间'
@@ -141,35 +129,35 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   },
   { prop: 'operation', label: '操作', width: 230 }
   { prop: 'operation', label: '操作', width: 230 }
 ])
 ])
-const fieldList: Form.FieldItem[] = [
-  {
-    label: '字典名称',
-    field: 'dictName',
-    rules: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
-    placeholder: '请输入字典名称'
-  },
-  {
-    label: '字典类型',
-    field: 'dictType',
-    rules: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],
-    placeholder: '请输入字典类型'
-  },
-  {
-    label: '状态',
-    field: 'status',
-    enum: () => getDictsApi('sys_normal_disable'),
-    type: 'radio',
-    options: {
-      labelKey: 'dictLabel',
-      valueKey: 'dictValue'
+
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '字典名称',
+      prop: 'dictName',
+      rules: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入字典名称'
+      }
     },
     },
-    placeholder: '请选择状态'
-  },
-  {
-    label: '备注',
-    field: 'remark',
-    type: 'textarea',
-    placeholder: '请输入备注'
-  }
-]
+    {
+      label: '字典类型',
+      prop: 'dictType',
+      rules: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入字典类型'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remark',
+      compOptions: {
+        type: 'textarea',
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
 </script>
 </script>

+ 11 - 11
src/views/system/menu/index.vue

@@ -22,9 +22,9 @@
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <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>
       </template>
     </ProTable>
     </ProTable>
     <FormDialog ref="formDialogRef" />
     <FormDialog ref="formDialogRef" />
@@ -37,7 +37,6 @@ import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, CirclePlus } from '@element-plus/icons-vue'
 import { handleTree } from '@/utils/common'
 import { handleTree } from '@/utils/common'
 import { listMenuApi, delMenuApi, addMenuApi, updateMenuApi, getMenuApi } from '@/api/modules/system/menu'
 import { listMenuApi, delMenuApi, addMenuApi, updateMenuApi, getMenuApi } from '@/api/modules/system/menu'
 import { getDictsApi } from '@/api/modules/system/dictData'
 import { getDictsApi } from '@/api/modules/system/dictData'
@@ -60,7 +59,7 @@ const dataCallback = (res: any) => {
   const menu: any = { menuId: 0, menuName: '主类目', children: [] }
   const menu: any = { menuId: 0, menuName: '主类目', children: [] }
   menu.children = data
   menu.children = data
   menuOptions.value.push(menu)
   menuOptions.value.push(menu)
-  setFieldList()
+  setItemsOptions()
   return data
   return data
 }
 }
 
 
@@ -91,7 +90,6 @@ const openDialog = async (type: number, title: string, row?: any) => {
 
 
 // 表格配置项
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
 const columns = reactive<ColumnProps<any>[]>([
-  { type: 'selection', fixed: 'left', width: 70 },
   {
   {
     prop: 'menuName',
     prop: 'menuName',
     label: '菜单名称',
     label: '菜单名称',
@@ -155,7 +153,7 @@ const columns = reactive<ColumnProps<any>[]>([
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
 ])
 ])
 let itemsOptions: ProForm.ItemsOptions[] = []
 let itemsOptions: ProForm.ItemsOptions[] = []
-const setFieldList = () => {
+const setItemsOptions = () => {
   itemsOptions = [
   itemsOptions = [
     {
     {
       label: '菜单类型',
       label: '菜单类型',
@@ -174,6 +172,7 @@ const setFieldList = () => {
       span: 12,
       span: 12,
       compOptions: {
       compOptions: {
         elTagName: 'radio-button',
         elTagName: 'radio-button',
+        value: '0',
         enum: () => getDictsApi('sys_normal_disable'),
         enum: () => getDictsApi('sys_normal_disable'),
         labelKey: 'dictLabel',
         labelKey: 'dictLabel',
         valueKey: 'dictValue'
         valueKey: 'dictValue'
@@ -247,6 +246,7 @@ const setFieldList = () => {
       },
       },
       compOptions: {
       compOptions: {
         elTagName: 'radio-button',
         elTagName: 'radio-button',
+        value: '0',
         enum: () => getDictsApi('sys_show_hide'),
         enum: () => getDictsApi('sys_show_hide'),
         labelKey: 'dictLabel',
         labelKey: 'dictLabel',
         valueKey: 'dictValue'
         valueKey: 'dictValue'
@@ -271,7 +271,7 @@ const setFieldList = () => {
       show: val => {
       show: val => {
         return val?.menuType !== 'F'
         return val?.menuType !== 'F'
       },
       },
-      tooltip: '访问路由的默认传递参数,如:`{"id": 1, "name": "km"}`',
+      tooltip: '访问路由的默认传递参数,如:`{"id": 1, "name": "taais"}`',
       compOptions: {
       compOptions: {
         elTagName: 'input',
         elTagName: 'input',
         placeholder: '请输入路由地址'
         placeholder: '请输入路由地址'
@@ -292,10 +292,10 @@ const setFieldList = () => {
       }
       }
     },
     },
     {
     {
-      label: '组件名称',
-      rules: [{ required: true, message: '路由名称不能为空', trigger: 'blur' }],
+      label: '路由名称',
+      // rules: [{ required: true, message: '路由名称不能为空', trigger: 'blur' }],
       prop: 'componentName',
       prop: 'componentName',
-      tooltip: '组件名称,匹配组件内Name属性,如:User',
+      tooltip: '路由名称,匹配路由内Name属性,如:UserManage',
       span: 12,
       span: 12,
       show: val => {
       show: val => {
         return val?.menuType == 'C'
         return val?.menuType == 'C'

+ 312 - 0
src/views/system/oss/config.vue

@@ -0,0 +1,312 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="ossConfigId" :request-api="listOssConfigApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['system:ossConfig:add']" icon="CirclePlus" @click="openDialog(1, '对象存储配置新增')"> 新增 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['system:ossConfig:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-tooltip content="编辑" placement="top">
+          <el-button type="primary" link icon="EditPen" v-auth="['system:ossConfig:edit']" @click="openDialog(2, '对象存储配置编辑', scope.row)">
+          </el-button>
+        </el-tooltip>
+        <el-tooltip content="删除" placement="top">
+          <el-button type="primary" link icon="Delete" v-auth="['system:ossConfig:remove']" @click="deleteOssConfig(scope.row)"> </el-button>
+        </el-tooltip>
+      </template>
+    </ProTable>
+    <FormDialog ref="formDialogRef" />
+    <ImportExcel ref="dialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="OssConfig">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import ProTable from '@/components/ProTable/index.vue'
+import ImportExcel from '@/components/ImportExcel/index.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import {
+  listOssConfigApi,
+  delOssConfigApi,
+  addOssConfigApi,
+  updateOssConfigApi,
+  getOssConfigApi,
+  changeOssConfigStatusApi
+} from '@/api/modules/system/ossConfig'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import { OssConfigVO } from '@/api/interface/system/ossConfig'
+
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 删除对象存储配置信息
+const deleteOssConfig = async (params: any) => {
+  await useHandleData(delOssConfigApi, params.ossConfigId, `删除【params.ossConfigId】对象存储配置`)
+  proTable.value?.getTableList()
+}
+
+// 批量删除对象存储配置信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delOssConfigApi, ids, '删除所选对象存储配置信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+// 切换用户状态
+const changeStatus = async (row: OssConfigVO) => {
+  let text = row.status === '1' ? '启用' : '停用'
+  await useHandleData(
+    changeOssConfigStatusApi,
+    { ossConfigId: row.ossConfigId, version: row.version, status: row.status == '1' ? 0 : 1, configKey: row.configKey },
+    `'要${text}"${row.configKey}'"配置吗?`
+  )
+  proTable.value?.getTableList()
+}
+
+const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
+// 打开弹框的功能
+const openDialog = async (type: number, title: string, row?: any) => {
+  let res = { data: {} }
+  if (row?.ossConfigId) {
+    res = await getOssConfigApi(row?.ossConfigId || null)
+  }
+  // 重置表单
+  setItemsOptions()
+  const params = {
+    title,
+    width: 680,
+    top: '8vh',
+    isEdit: type !== 3,
+    itemsOptions: itemsOptions,
+    model: type == 1 ? {} : res.data,
+    api: type == 1 ? addOssConfigApi : updateOssConfigApi,
+    getTableList: proTable.value?.getTableList
+  }
+  formDialogRef.value?.openDialog(params)
+}
+
+const types = [
+  {
+    label: 'private',
+    value: '0'
+  },
+  {
+    label: 'public',
+    value: '1'
+  },
+  {
+    label: 'custom',
+    value: '2'
+  }
+]
+const statusTypes = [
+  {
+    label: '启用',
+    value: '1'
+  },
+  {
+    label: '停用',
+    value: '0'
+  }
+]
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: 'ossConfigId',
+    label: '主建'
+  },
+  {
+    prop: 'endpoint',
+    label: '访问站点'
+  },
+  {
+    prop: 'domainName',
+    label: '自定义域名'
+  },
+  {
+    prop: 'configKey',
+    label: '配置key',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'bucketName',
+    label: '桶名称',
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'prefix',
+    label: '前缀'
+  },
+  {
+    prop: 'region',
+    label: '域'
+  },
+  {
+    prop: 'accessPolicy',
+    label: '桶权限类型',
+    tag: true,
+    enum: types
+  },
+  {
+    prop: 'status',
+    label: '是否默认',
+    enum: statusTypes,
+    search: { el: 'tree-select' },
+    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: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '配置key',
+      prop: 'configKey',
+      rules: [{ required: true, message: '配置key不能为空', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入配置key'
+      }
+    },
+    {
+      label: '访问站点',
+      prop: 'endpoint',
+      rules: [
+        { required: true, message: '访问站点不能为空', trigger: 'blur' },
+        {
+          min: 2,
+          max: 100,
+          message: '访问站点名称长度必须介于 2 和 100 之间',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        placeholder: '请输入访问站点'
+      }
+    },
+    {
+      label: '自定义域名',
+      prop: 'domainName',
+      compOptions: {
+        placeholder: '请输入自定义域名'
+      }
+    },
+    {
+      label: 'accessKey',
+      prop: 'accessKey',
+      rules: [
+        { required: true, message: 'accessKey不能为空', trigger: 'blur' },
+        {
+          min: 2,
+          max: 200,
+          message: 'accessKey长度必须介于 2 和 100 之间',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        placeholder: '请输入accessKey'
+      }
+    },
+    {
+      label: 'secretKey',
+      prop: 'secretKey',
+      rules: [
+        { required: true, message: 'secretKey不能为空', trigger: 'blur' },
+        {
+          min: 2,
+          max: 100,
+          message: 'secretKey长度必须介于 2 和 100 之间',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        showPassword: true,
+        placeholder: '请输入secretKey'
+      }
+    },
+    {
+      label: '桶名称',
+      prop: 'bucketName',
+      rules: [
+        { required: true, message: '桶名称不能为空', trigger: 'blur' },
+        {
+          min: 2,
+          max: 100,
+          message: '桶名称长度必须介于 2 和 100 之间',
+          trigger: 'blur'
+        }
+      ],
+      compOptions: {
+        placeholder: '请输入桶名称'
+      }
+    },
+    {
+      label: '前缀',
+      prop: 'prefix',
+      compOptions: {
+        placeholder: '请输入前缀'
+      }
+    },
+    {
+      label: '是否https',
+      prop: 'isHttps',
+      compOptions: {
+        elTagName: 'radio-group',
+        enum: () => getDictsApi('sys_yes_no'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue'
+      }
+    },
+    {
+      label: '桶权限类型',
+      prop: 'accessPolicy',
+      compOptions: {
+        elTagName: 'radio-group',
+        enum: types,
+        placeholder: '请输入桶权限类型'
+      }
+    },
+    {
+      label: '域',
+      prop: 'region',
+      compOptions: {
+        placeholder: '请输入域'
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remark',
+      compOptions: {
+        placeholder: '请输入备注'
+      }
+    }
+  ]
+}
+</script>

+ 183 - 0
src/views/system/oss/index.vue

@@ -0,0 +1,183 @@
+<template>
+  <div class="table-box">
+    <ProTable ref="proTable" :columns="columns" row-key="ossId" :data-callback="dataCallback" :request-api="listOssApi">
+      <!-- 表格 header 按钮 -->
+      <template #tableHeader="scope">
+        <el-button type="primary" v-auth="['system:oss:upload']" icon="Upload" plain @click="handleFile('file-upload-s3')"> 上传文件 </el-button>
+        <el-button type="primary" v-auth="['system:oss:upload']" icon="Upload" plain @click="handleFile('img-upload-s3')"> 上传图片 </el-button>
+        <el-button type="primary" v-auth="['system:oss:upload']" icon="Upload" plain @click="handleFile('img-upload')"> 本地图片 </el-button>
+        <el-button type="primary" v-auth="['system:oss:upload']" icon="Upload" plain @click="handleFile('file-upload')"> 本地上传 </el-button>
+        <el-button
+          type="danger"
+          v-auth="['system:oss:remove']"
+          icon="Delete"
+          plain
+          :disabled="!scope.isSelected"
+          @click="batchDelete(scope.selectedListIds)"
+        >
+          批量删除
+        </el-button>
+        <el-button
+          :type="previewListResource ? 'danger' : 'warning'"
+          v-auth="['system:oss:edit']"
+          plain
+          @click="handlePreviewListResource(!previewListResource)"
+        >
+          预览开关 : {{ previewListResource ? '禁用' : '启用' }}
+        </el-button>
+        <el-button type="info" v-auth="['system:oss:list']" icon="Operation" @click="handleOssConfig()"> 配置管理 </el-button>
+      </template>
+      <!-- 菜单图标 -->
+      <template #url="scope">
+        <ImagePreview
+          v-if="previewListResource && checkFileSuffix(scope.row.fileSuffix)"
+          :width="100"
+          :height="100"
+          :src="scope.row.url"
+          :preview-src-list="[scope.row.url]"
+        />
+        <span v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" v-text="scope.row.url" />
+      </template>
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-tooltip content="下载" placement="top">
+          <el-button type="primary" link icon="Download" v-auth="['system:oss:download']" @click="handleDownload(scope.row)"> </el-button>
+        </el-tooltip>
+        <el-tooltip content="删除" placement="top">
+          <el-button type="primary" link icon="Delete" v-auth="['system:oss:remove']" @click="deleteOss(scope.row)"> </el-button>
+        </el-tooltip>
+      </template>
+    </ProTable>
+    <UploadDialog ref="uploadDialogRef" />
+  </div>
+</template>
+
+<script setup lang="tsx" name="Oss">
+import { ref, reactive } from 'vue'
+import { useHandleData } from '@/hooks/useHandleData'
+import useDownload from '@/hooks/useDownload'
+import UploadDialog from '@/components/UploadDialog/index.vue'
+import { useRouter } from 'vue-router'
+import ProTable from '@/components/ProTable/index.vue'
+import ImagePreview from '@/components/ImagePreview/index.vue'
+import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
+import { listOssApi, delOssApi } from '@/api/modules/system/oss'
+
+import { getConfigKeyApi, updateConfigByKeyApi } from '@/api/modules/system/config'
+import { OssVO } from '@/api/interface/system/oss'
+const router = useRouter()
+// ProTable 实例
+const proTable = ref<ProTableInstance>()
+
+// 预览开关
+const previewListResource = ref(true)
+
+// dataCallback 是对于返回的表格数据做处理
+const dataCallback = (res: any) => {
+  getConfigKeyApi('sys.oss.previewListResource').then(
+    res => (previewListResource.value = res?.data === undefined ? true : res.data.configValue === 'true')
+  )
+  return res
+}
+
+const handleOssConfig = () => {
+  router.push('/system/oss-config/index')
+}
+
+const checkFileSuffix = (fileSuffix: string | string[]) => {
+  const arr = ['.png', '.jpg', '.jpeg']
+  const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix]
+  return suffixArray.some(suffix => arr.includes(suffix.toLowerCase()))
+}
+const handlePreviewListResource = async (preview: boolean) => {
+  const res = await getConfigKeyApi('sys.oss.previewListResource')
+  let text = preview ? '启用' : '停用'
+  getConfigKeyApi
+  await useHandleData(
+    updateConfigByKeyApi,
+    { configKey: 'sys.oss.previewListResource', configValue: preview, version: res.data.version },
+    `要${text}预览列表图片"配置吗?`
+  )
+  proTable.value?.getTableList()
+}
+
+// 删除OSS对象存储信息
+const deleteOss = async (params: any) => {
+  await useHandleData(delOssApi, params.ossId, `删除【${params.ossId}】OSS对象存储`)
+  proTable.value?.getTableList()
+}
+
+// 批量删除OSS对象存储信息
+const batchDelete = async (ids: string[]) => {
+  await useHandleData(delOssApi, ids, '删除所选OSS对象存储信息')
+  proTable.value?.clearSelection()
+  proTable.value?.getTableList()
+}
+
+/** 下载按钮操作 */
+const handleDownload = (row: OssVO) => {
+  console.log(row)
+  if (row.service === 'Local') {
+    useDownload.localResource(row.url)
+    return
+  }
+  useDownload.oss(row.ossId)
+}
+
+// 上传文件
+const uploadDialogRef = ref<InstanceType<typeof UploadDialog> | null>(null)
+const handleFile = (type: File.FileUploadType) => {
+  const params = {
+    title: '上传文件',
+    type,
+    getTableList: proTable.value?.getTableList
+  }
+  uploadDialogRef.value?.openDialog(params)
+}
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  { type: 'selection', fixed: 'left', width: 70 },
+  {
+    prop: 'fileName',
+    label: '文件名',
+    search: {
+      el: 'input'
+    },
+    width: 420
+  },
+  {
+    prop: 'originalName',
+    label: '原名',
+    search: {
+      el: 'input'
+    },
+    width: 220
+  },
+  {
+    prop: 'fileSuffix',
+    label: '文件后缀名',
+    search: {
+      el: 'input'
+    },
+    width: 100
+  },
+  {
+    prop: 'url',
+    label: 'URL地址',
+    showOverflowTooltip: false,
+    search: {
+      el: 'input'
+    }
+  },
+  {
+    prop: 'service',
+    label: '服务商',
+    search: {
+      el: 'input'
+    },
+    width: 120
+  },
+  { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
+])
+</script>

+ 47 - 38
src/views/system/post/index.vue

@@ -3,13 +3,13 @@
     <ProTable ref="proTable" :columns="columns" row-key="postId" :request-api="listPostApi">
     <ProTable ref="proTable" :columns="columns" row-key="postId" :request-api="listPostApi">
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <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: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 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
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:user:remove']"
           v-auth="['system:user:remove']"
-          :icon="Delete"
+          icon="Delete"
           plain
           plain
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
           @click="batchDelete(scope.selectedListIds)"
           @click="batchDelete(scope.selectedListIds)"
@@ -19,13 +19,11 @@
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <template #operation="scope">
-        <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="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>
         </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>
       </template>
     </ProTable>
     </ProTable>
     <FormDialog ref="formDialogRef" />
     <FormDialog ref="formDialogRef" />
@@ -40,9 +38,8 @@ import { useDownload } from '@/hooks/useDownload'
 import { ElMessageBox } from 'element-plus'
 import { ElMessageBox } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
 import ImportExcel from '@/components/ImportExcel/index.vue'
-import FormDialog from '@/components/DialogOld/form.vue'
+import FormDialog from '@/components/FormDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, Download, Upload, View, CirclePlus } from '@element-plus/icons-vue'
 import {
 import {
   listPostApi,
   listPostApi,
   delPostApi,
   delPostApi,
@@ -53,13 +50,14 @@ import {
   exportApi,
   exportApi,
   getPostApi
   getPostApi
 } from '@/api/modules/system/post'
 } from '@/api/modules/system/post'
+import { getDictsApi } from '@/api/modules/system/dictData'
 
 
 // ProTable 实例
 // ProTable 实例
 const proTable = ref<ProTableInstance>()
 const proTable = ref<ProTableInstance>()
 
 
 // 删除岗位信息信息
 // 删除岗位信息信息
 const deletePost = async (params: any) => {
 const deletePost = async (params: any) => {
-  await useHandleData(delPostApi, params.postId, `删除【${params.postId}】岗位信息`)
+  await useHandleData(delPostApi, params.postId, `删除【${params.postName}】岗位信息`)
   proTable.value?.getTableList()
   proTable.value?.getTableList()
 }
 }
 
 
@@ -97,12 +95,12 @@ const openDialog = async (type: number, title: string, row?: any) => {
     res = await getPostApi(row?.postId || null)
     res = await getPostApi(row?.postId || null)
   }
   }
   // 重置表单
   // 重置表单
-  setFieldList()
+  setItemsOptions()
   const params = {
   const params = {
     title,
     title,
     width: 580,
     width: 580,
     isEdit: type !== 3,
     isEdit: type !== 3,
-    fieldList: fieldList,
+    itemsOptions: itemsOptions,
     model: type == 1 ? {} : res.data,
     model: type == 1 ? {} : res.data,
     api: type == 1 ? addPostApi : updatePostApi,
     api: type == 1 ? addPostApi : updatePostApi,
     getTableList: proTable.value?.getTableList
     getTableList: proTable.value?.getTableList
@@ -140,11 +138,14 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   },
   {
   {
     prop: 'status',
     prop: 'status',
-    label: '状态',
+    label: '岗位状态',
+    tag: true,
+    enum: () => getDictsApi('sys_normal_disable'),
     search: {
     search: {
-      el: 'input'
+      el: 'tree-select'
     },
     },
-    width: 120
+    fieldNames: { label: 'dictLabel', value: 'dictValue' },
+    width: 180
   },
   },
   {
   {
     prop: 'createBy',
     prop: 'createBy',
@@ -163,14 +164,6 @@ const columns = reactive<ColumnProps<any>[]>([
     },
     },
     width: 120
     width: 120
   },
   },
-  {
-    prop: 'updateBy',
-    label: '更新者',
-    search: {
-      el: 'input'
-    },
-    width: 120
-  },
   {
   {
     prop: 'updateTime',
     prop: 'updateTime',
     label: '更新时间',
     label: '更新时间',
@@ -190,38 +183,54 @@ const columns = reactive<ColumnProps<any>[]>([
   },
   },
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
   { prop: 'operation', label: '操作', width: 230, fixed: 'right' }
 ])
 ])
+
 // 表单配置项
 // 表单配置项
-let fieldList: Form.FieldItem[] = []
-const setFieldList = () => {
-  fieldList = [
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
     {
     {
       label: '岗位编码',
       label: '岗位编码',
-      field: 'postCode',
+      prop: 'postCode',
       rules: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
-      placeholder: '请输入岗位编码'
+      compOptions: {
+        placeholder: '请输入岗位编码'
+      }
     },
     },
     {
     {
       label: '岗位名称',
       label: '岗位名称',
-      field: 'postName',
+      prop: 'postName',
       rules: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
-      placeholder: '请输入岗位名称'
+      compOptions: {
+        placeholder: '请输入岗位名称'
+      }
     },
     },
     {
     {
       label: '显示顺序',
       label: '显示顺序',
-      field: 'postSort',
+      prop: 'postSort',
       rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
-      placeholder: '请输入显示顺序'
+      compOptions: {
+        elTagName: 'input-number',
+        min: 0
+      }
     },
     },
     {
     {
       label: '状态',
       label: '状态',
-      field: 'status',
+      prop: 'status',
       rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
-      placeholder: '请输入状态'
+      compOptions: {
+        elTagName: 'radio-group',
+        value: '0',
+        enum: () => getDictsApi('sys_normal_disable'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue'
+      }
     },
     },
     {
     {
       label: '备注',
       label: '备注',
-      field: 'remark',
-      placeholder: '请输入备注'
+      prop: 'remark',
+      compOptions: {
+        placeholder: '请输入备注'
+      }
     }
     }
   ]
   ]
 }
 }

+ 4 - 5
src/views/system/role/authUser.vue

@@ -3,22 +3,22 @@
     <ProTable ref="proTable" :columns="columns" row-key="userId" :request-api="allocatedUserListApi" :init-param="initParam">
     <ProTable ref="proTable" :columns="columns" row-key="userId" :request-api="allocatedUserListApi" :init-param="initParam">
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <template #tableHeader="scope">
-        <el-button type="primary" v-auth="['system:role:add']" :icon="CirclePlus" @click="addUser"> 添加用户 </el-button>
+        <el-button type="primary" v-auth="['system:role:add']" icon="CirclePlus" @click="addUser"> 添加用户 </el-button>
         <el-button
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:role:import']"
           v-auth="['system:role:import']"
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
-          :icon="CircleClose"
+          icon="CircleClose"
           plain
           plain
           @click="cancelAuthUserAll(scope.selectedListIds)"
           @click="cancelAuthUserAll(scope.selectedListIds)"
         >
         >
           批量取消授权
           批量取消授权
         </el-button>
         </el-button>
-        <el-button type="warning" v-auth="['system:role:export']" :icon="Close" plain @click="closeCurrentTab"> 关闭 </el-button>
+        <el-button type="warning" v-auth="['system:role:export']" icon="Close" plain @click="closeCurrentTab"> 关闭 </el-button>
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <template #operation="scope">
-        <el-button type="primary" link :icon="CircleClose" v-auth="['system:role:query']" @click="cancelAuthUser(scope.row)"> 取消授权 </el-button>
+        <el-button type="primary" link icon="CircleClose" v-auth="['system:role:query']" @click="cancelAuthUser(scope.row)"> 取消授权 </el-button>
       </template>
       </template>
     </ProTable>
     </ProTable>
     <TableDialog ref="tableDialogRef" @submit-form="submitForm" />
     <TableDialog ref="tableDialogRef" @submit-form="submitForm" />
@@ -31,7 +31,6 @@ import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import TableDialog from '@/components/TableDialog/index.vue'
 import TableDialog from '@/components/TableDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { CircleClose, Close, CirclePlus } from '@element-plus/icons-vue'
 import {
 import {
   allocatedUserListApi,
   allocatedUserListApi,
   unallocatedUserListApi,
   unallocatedUserListApi,

+ 10 - 10
src/views/system/role/index.vue

@@ -3,13 +3,13 @@
     <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi">
     <ProTable ref="proTable" :columns="columns" row-key="roleId" :request-api="listRoleApi">
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <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="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
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:user:remove']"
           v-auth="['system:user:remove']"
-          :icon="Delete"
+          icon="Delete"
           plain
           plain
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
           @click="batchDelete(scope.selectedListIds)"
           @click="batchDelete(scope.selectedListIds)"
@@ -23,26 +23,26 @@
           type="primary"
           type="primary"
           v-if="scope.row.roleId !== 1"
           v-if="scope.row.roleId !== 1"
           link
           link
-          :icon="EditPen"
+          icon="EditPen"
           v-auth="['system:role:edit']"
           v-auth="['system:role:edit']"
           @click="openDialog(2, '角色编辑', scope.row)"
           @click="openDialog(2, '角色编辑', scope.row)"
         >
         >
           编辑
           编辑
         </el-button>
         </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 type="primary" v-if="scope.row.roleId !== 1" link icon="Delete" v-auth="['system:role:remove']" @click="deleteRole(scope.row)">
           删除
           删除
         </el-button>
         </el-button>
         <el-button
         <el-button
           type="primary"
           type="primary"
           v-if="scope.row.roleId !== 1"
           v-if="scope.row.roleId !== 1"
           link
           link
-          :icon="CircleCheck"
+          icon="CircleCheck"
           v-auth="['system:role:edit']"
           v-auth="['system:role:edit']"
           @click="openDataDialog(3, '数据权限', scope.row)"
           @click="openDataDialog(3, '数据权限', scope.row)"
         >
         >
           数据权限
           数据权限
         </el-button>
         </el-button>
-        <el-button type="primary" v-if="scope.row.roleId !== 1" link :icon="User" v-auth="['system:role:edit']" @click="handleAuthUser(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>
         </el-button>
       </template>
       </template>
@@ -112,7 +112,6 @@ import ImportExcel from '@/components/ImportExcel/index.vue'
 import FormDialog from '@/components/CustomDialog/index.vue'
 import FormDialog from '@/components/CustomDialog/index.vue'
 import ProFrom from '@/components/ProForm/index.vue'
 import ProFrom from '@/components/ProForm/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
-import { Delete, EditPen, Download, Upload, CircleCheck, User, CirclePlus } from '@element-plus/icons-vue'
 import {
 import {
   listRoleApi,
   listRoleApi,
   delRoleApi,
   delRoleApi,
@@ -385,7 +384,7 @@ const reset = () => {
 // 表格配置项
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
 const columns = reactive<ColumnProps<any>[]>([
   { type: 'selection', fixed: 'left', width: 70 },
   { type: 'selection', fixed: 'left', width: 70 },
-
+  { prop: 'roleId', label: '角色编号' },
   {
   {
     prop: 'roleName',
     prop: 'roleName',
     label: '角色名称'
     label: '角色名称'
@@ -469,6 +468,7 @@ const setItemsOptions = () => {
       rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '角色状态不能为空', trigger: 'blur' }],
       compOptions: {
       compOptions: {
         elTagName: 'radio-group',
         elTagName: 'radio-group',
+        value: '0',
         enum: () => getDictsApi('sys_normal_disable'),
         enum: () => getDictsApi('sys_normal_disable'),
         labelKey: 'dictLabel',
         labelKey: 'dictLabel',
         valueKey: 'dictValue'
         valueKey: 'dictValue'

+ 127 - 108
src/views/system/user/index.vue

@@ -14,13 +14,13 @@
       >
       >
         <!-- 表格 header 按钮 -->
         <!-- 表格 header 按钮 -->
         <template #tableHeader="scope">
         <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="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
           <el-button
             type="danger"
             type="danger"
             v-auth="['system:user:remove']"
             v-auth="['system:user:remove']"
-            :icon="Delete"
+            icon="Delete"
             plain
             plain
             :disabled="!scope.isSelected"
             :disabled="!scope.isSelected"
             @click="batchDelete(scope.selectedListIds)"
             @click="batchDelete(scope.selectedListIds)"
@@ -30,9 +30,9 @@
         </template>
         </template>
         <!-- 表格操作 -->
         <!-- 表格操作 -->
         <template #operation="scope">
         <template #operation="scope">
-          <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>
-          <el-button type="primary" link v-if="scope.row.userId !== 1" :icon="View" @click="handleResetPwd(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>
+          <el-button type="primary" link v-if="scope.row.userId !== 1" icon="View" @click="handleResetPwd(scope.row)">重置密码</el-button>
         </template>
         </template>
       </ProTable>
       </ProTable>
       <FormDialog ref="formDialogRef" />
       <FormDialog ref="formDialogRef" />
@@ -50,8 +50,7 @@ import { ElMessageBox, ElMessage } from 'element-plus'
 import ProTable from '@/components/ProTable/index.vue'
 import ProTable from '@/components/ProTable/index.vue'
 import TreeFilter from '@/components/TreeFilter/index.vue'
 import TreeFilter from '@/components/TreeFilter/index.vue'
 import ImportExcel from '@/components/ImportExcel/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 FormDialog from '@/components/FormDialog/index.vue'
 import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
 import { ColumnProps, ProTableInstance } from '@/components/ProTable/interface'
 import {
 import {
   listUserApi,
   listUserApi,
@@ -154,123 +153,40 @@ const roleOptions = ref<any[]>([])
 // 打开弹框的功能
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
 const openDialog = async (type: number, title: string, row?: any) => {
   let res = await getUserApi(row?.userId || null)
   let res = await getUserApi(row?.userId || null)
-
+  debugger
   postOptions.value = res.data.posts
   postOptions.value = res.data.posts
   roleOptions.value = res.data.roles
   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'
-    }
-  ]
+  setItemsOptions()
   if (type == 1) {
   if (type == 1) {
-    fieldList.splice(
+    itemsOptions.splice(
       4,
       4,
       0,
       0,
       {
       {
         label: '用户名称',
         label: '用户名称',
-        placeholder: '请输入用户名称',
-        field: 'userName',
+        prop: 'userName',
         span: 12,
         span: 12,
         rules: [
         rules: [
           { required: true, message: '用户名称不能为空' },
           { required: true, message: '用户名称不能为空' },
           { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
           { min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
-        ]
+        ],
+        compOptions: {
+          placeholder: '请输入用户名称'
+        }
       },
       },
       {
       {
         label: '用户密码',
         label: '用户密码',
-        placeholder: '请输入密码',
-        field: 'password',
-        type: 'password',
-        showPassword: true,
+        prop: 'password',
         span: 12,
         span: 12,
         rules: [
         rules: [
           { required: true, message: '密码不能为空' },
           { required: true, message: '密码不能为空' },
           { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
           { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
-        ]
+        ],
+        compOptions: {
+          showPassword: true,
+          type: 'password',
+          placeholder: '请输入密码'
+        }
       }
       }
     )
     )
   }
   }
@@ -283,7 +199,7 @@ const openDialog = async (type: number, title: string, row?: any) => {
     title,
     title,
     width: 680,
     width: 680,
     isEdit: type !== 3,
     isEdit: type !== 3,
-    fieldList: fieldList,
+    itemsOptions: itemsOptions,
     model: type == 1 ? {} : res.data.user,
     model: type == 1 ? {} : res.data.user,
     api: type == 1 ? addUserApi : updateUserApi,
     api: type == 1 ? addUserApi : updateUserApi,
     getTableList: proTable.value?.getTableList
     getTableList: proTable.value?.getTableList
@@ -331,4 +247,107 @@ const columns = reactive<ColumnProps<User.ResUserList>[]>([
   { prop: 'createTime', label: '创建时间', width: 180 },
   { prop: 'createTime', label: '创建时间', width: 180 },
   { prop: 'operation', label: '操作', width: 240, fixed: 'right' }
   { prop: 'operation', label: '操作', width: 240, fixed: 'right' }
 ])
 ])
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = []
+const setItemsOptions = () => {
+  itemsOptions = [
+    {
+      label: '用户昵称',
+      span: 12,
+      prop: 'nickName',
+      rules: [{ required: true, message: '用户昵称不能为空' }],
+      compOptions: {
+        placeholder: '请输入用户昵称'
+      }
+    },
+    {
+      label: '归属部门',
+      span: 12,
+      prop: 'deptId',
+      compOptions: {
+        elTagName: 'tree-select',
+        enum: () => deptTreeSelectApi(),
+        valueKey: 'id',
+        labelKey: 'label',
+        checkStrictly: true,
+        placeholder: '请选择归属部门'
+      }
+    },
+    {
+      label: '手机号码',
+      span: 12,
+      prop: 'phonenumber',
+      rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
+      compOptions: {
+        placeholder: '请输入手机号码'
+      }
+    },
+    {
+      label: '邮箱',
+      span: 12,
+      prop: 'email',
+      rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
+      compOptions: {
+        placeholder: '请输入邮箱'
+      }
+    },
+    {
+      label: '性别',
+      span: 12,
+      prop: 'sex',
+      compOptions: {
+        elTagName: 'radio-group',
+        enum: () => getDictsApi('sys_user_gender'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue',
+        placeholder: '请输入邮箱'
+      }
+    },
+    {
+      label: '状态',
+      span: 12,
+      prop: 'status',
+      compOptions: {
+        elTagName: 'radio-group',
+        value: '0',
+        enum: () => getDictsApi('sys_normal_disable'),
+        labelKey: 'dictLabel',
+        valueKey: 'dictValue'
+      }
+    },
+    {
+      label: '岗位',
+      span: 12,
+      prop: 'postIds',
+      compOptions: {
+        elTagName: 'select',
+        valueKey: 'postId',
+        labelKey: 'postName',
+        multiple: true,
+        enum: postOptions.value,
+        placeholder: '请选择岗位'
+      }
+    },
+    {
+      label: '角色',
+      span: 12,
+      prop: 'roleIds',
+      compOptions: {
+        elTagName: 'select',
+        enum: roleOptions.value,
+        valueKey: 'roleId',
+        labelKey: 'roleName',
+        multiple: true
+      }
+    },
+    {
+      label: '备注',
+      prop: 'remark',
+      compOptions: {
+        type: 'textarea',
+        placeholder: '请输入内容'
+      }
+    }
+  ]
+}
 </script>
 </script>

+ 9 - 0
src/views/system/user/profile/index.scss

@@ -0,0 +1,9 @@
+.form-box {
+  // display: flex;
+  // flex-direction: column; /* 竖直方向布局 */
+  // height: 100%;
+  .box-card {
+    // flex: 1;
+    height: calc(100vh - 110px);
+  }
+}

+ 222 - 0
src/views/system/user/profile/index.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="form-box">
+    <el-row :gutter="20">
+      <el-col :span="6" :xs="24">
+        <el-card class="box-card">
+          <template #header>
+            <div class="clearfix">
+              <span>个人信息</span>
+            </div>
+          </template>
+          <div class="text-center">
+            <userAvatar />
+          </div>
+          <ul class="list-group list-group-striped">
+            <li class="list-group-item">
+              用户名称
+              <div class="pull-right">{{ state.user.userName }}</div>
+            </li>
+            <li class="list-group-item">
+              手机号码
+              <div class="pull-right">{{ state.user.phonenumber }}</div>
+            </li>
+            <li class="list-group-item">
+              用户邮箱
+              <div class="pull-right">{{ state.user.email }}</div>
+            </li>
+            <li class="list-group-item">
+              所属部门
+              <div v-if="state.user.dept" class="pull-right">{{ state.user.dept?.deptName }} / {{ state.postGroup }}</div>
+            </li>
+            <li class="list-group-item">
+              所属角色
+              <div class="pull-right">{{ state.roleGroup }}</div>
+            </li>
+            <li class="list-group-item">
+              创建日期
+              <div class="pull-right">{{ state.user.createTime }}</div>
+            </li>
+          </ul>
+        </el-card>
+      </el-col>
+      <el-col :span="18" :xs="24">
+        <el-card class="box-card">
+          <template #header>
+            <div class="clearfix">
+              <span>基本信息</span>
+            </div>
+          </template>
+          <el-tabs v-model="activeTab">
+            <el-tab-pane label="基本资料" name="userInfo">
+              <ProFrom
+                ref="proFormRef"
+                :items-options="itemsOptions"
+                :form-options="_options"
+                :model="userForm"
+                @submit="submit"
+                @cancel="closeCurrentTab"
+              />
+            </el-tab-pane>
+            <el-tab-pane label="修改密码" name="resetPwd">
+              <ProFrom
+                ref="proFormRef"
+                :items-options="itemsOptions1"
+                :form-options="_options"
+                :model="{}"
+                @submit="submitPwd"
+                @cancel="closeCurrentTab"
+              />
+            </el-tab-pane>
+          </el-tabs>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup lang="ts" name="uploadFile">
+import userAvatar from './userAvatar.vue'
+import { ref, onMounted } from 'vue'
+import { ElMessage } from 'element-plus'
+import { getUserProfileApi, updateUserPwdApi, updateUserProfileApi } from '@/api/modules/system/user'
+import { UserVO } from '@/api/interface/system/user'
+import ProFrom from '@/components/ProForm/index.vue'
+import { getDictsApi } from '@/api/modules/system/dictData'
+import { useRoute } from 'vue-router'
+import { useTabsStore } from '@/stores/modules/tabs'
+const tabStore = useTabsStore()
+const route = useRoute()
+const activeTab = ref('userInfo')
+interface State {
+  user: Partial<UserVO>
+  roleGroup: string
+  postGroup: string
+}
+const state = ref<State>({
+  user: {},
+  roleGroup: '',
+  postGroup: ''
+})
+const proFormRef = ref<InstanceType<typeof ProFrom> | null>(null)
+const userForm = ref({})
+const getUser = async () => {
+  const res = await getUserProfileApi()
+  state.value.user = res.data.user
+  userForm.value = { ...res.data.user }
+  state.value.roleGroup = res.data.roleGroup
+  state.value.postGroup = res.data.postGroup
+}
+const submit = async form => {
+  const res = await updateUserProfileApi(form)
+  if (res.code === 200) {
+    ElMessage.success('修改成功')
+    proFormRef.value?.resetForm
+  }
+}
+
+const submitPwd = async form => {
+  const res = await updateUserPwdApi(form)
+  if (res.code === 200) {
+    ElMessage.success('修改成功')
+    proFormRef.value?.resetForm
+  }
+}
+const closeCurrentTab = () => {
+  if (route.meta.affix) return
+  tabStore.removeTabs(route.fullPath)
+}
+const _options: ProForm.FormOptions = {
+  labelWidth: 120,
+  hasFooter: true,
+  disabled: false,
+  showCancelButton: true,
+  cancelButtonText: '关闭'
+}
+// 表单配置项
+let itemsOptions: ProForm.ItemsOptions[] = [
+  {
+    label: '用户昵称',
+    prop: 'nickName',
+    rules: [{ required: true, message: '用户昵称不能为空' }],
+    compOptions: {
+      placeholder: '请输入用户昵称'
+    }
+  },
+  {
+    label: '手机号码',
+    prop: 'phonenumber',
+    rules: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
+    compOptions: {
+      placeholder: '请输入手机号码'
+    }
+  },
+  {
+    label: '邮箱',
+    prop: 'email',
+    rules: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
+    compOptions: {
+      placeholder: '请输入邮箱'
+    }
+  },
+  {
+    label: '性别',
+    span: 12,
+    prop: 'gender',
+    compOptions: {
+      elTagName: 'radio-group',
+      enum: () => getDictsApi('sys_user_gender'),
+      labelKey: 'dictLabel',
+      valueKey: 'dictValue',
+      placeholder: '请输入邮箱'
+    }
+  }
+]
+let itemsOptions1: ProForm.ItemsOptions[] = [
+  {
+    label: '旧密码',
+    prop: 'oldPassword',
+    rules: [
+      { required: true, message: '密码不能为空' },
+      { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
+    ],
+    compOptions: {
+      showPassword: true,
+      type: 'newPassword',
+      placeholder: '请输入旧密码'
+    }
+  },
+  {
+    label: '新密码',
+    prop: 'confirmPassword',
+    rules: [
+      { required: true, message: '密码不能为空' },
+      { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
+    ],
+    compOptions: {
+      showPassword: true,
+      type: 'password',
+      placeholder: '请输入新密码'
+    }
+  },
+  {
+    label: '确认密码',
+    prop: 'password',
+    rules: [
+      { required: true, message: '密码不能为空' },
+      { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
+    ],
+    compOptions: {
+      showPassword: true,
+      type: 'password',
+      placeholder: '请输入新密码'
+    }
+  }
+]
+onMounted(() => {
+  getUser()
+})
+</script>
+
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 190 - 0
src/views/system/user/profile/userAvatar.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="user-info-head" @click="editCropper()">
+    <img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
+    <el-dialog v-model="open" :title="title" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
+      <el-row>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <vue-cropper
+            v-if="visible"
+            ref="cropper"
+            :img="options.img"
+            :info="true"
+            :auto-crop="options.autoCrop"
+            :auto-crop-width="options.autoCropWidth"
+            :auto-crop-height="options.autoCropHeight"
+            :fixed-box="options.fixedBox"
+            :output-type="options.outputType"
+            @real-time="realTime"
+          />
+        </el-col>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <div class="avatar-upload-preview">
+            <img :src="options.previews.url" :style="options.previews.img" />
+          </div>
+        </el-col>
+      </el-row>
+      <br />
+      <el-row>
+        <el-col :lg="2" :md="2">
+          <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
+            <el-button>
+              选择
+              <el-icon class="el-icon--right">
+                <Upload />
+              </el-icon>
+            </el-button>
+          </el-upload>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
+          <el-button icon="Plus" @click="changeScale(1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="Minus" @click="changeScale(-1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
+          <el-button type="primary" @click="uploadImg()">提 交</el-button>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive } from 'vue'
+import 'vue-cropper/dist/index.css'
+import { VueCropper } from 'vue-cropper'
+import { uploadAvatarApi } from '@/api/modules/system/user'
+import { useUserStore } from '@/stores/modules/user'
+import { UploadRawFile } from 'element-plus'
+import { ElMessage } from 'element-plus'
+
+interface Options {
+  img: string | any // 裁剪图片的地址
+  autoCrop: boolean // 是否默认生成截图框
+  autoCropWidth: number // 默认生成截图框宽度
+  autoCropHeight: number // 默认生成截图框高度
+  fixedBox: boolean // 固定截图框大小 不允许改变
+  fileName: string
+  previews: any // 预览数据
+  outputType: string
+  visible: boolean
+}
+
+const userStore = useUserStore()
+
+// 弹出层
+const open = ref(false)
+
+// 是否显示cropper
+const visible = ref(false)
+
+// 弹出层标题
+const title = ref('修改头像')
+
+const cropper = ref<any>({})
+
+//图片裁剪数据
+const options = reactive<Options>({
+  img: userStore.avatar,
+  autoCrop: true,
+  autoCropWidth: 200,
+  autoCropHeight: 200,
+  fixedBox: true,
+  outputType: 'png',
+  fileName: '',
+  previews: {},
+  visible: false
+})
+
+/** 编辑头像 */
+const editCropper = () => {
+  open.value = true
+}
+/** 打开弹出层结束时的回调 */
+const modalOpened = () => {
+  visible.value = true
+}
+/** 覆盖默认上传行为 */
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const requestUpload: any = () => {}
+/** 向左旋转 */
+const rotateLeft = () => {
+  cropper.value.rotateLeft()
+}
+/** 向右旋转 */
+const rotateRight = () => {
+  cropper.value.rotateRight()
+}
+/** 图片缩放 */
+const changeScale = (num: number) => {
+  num = num || 1
+  cropper.value.changeScale(num)
+}
+/** 上传预处理 */
+const beforeUpload = (file: UploadRawFile): any => {
+  if (file.type.indexOf('image/') == -1) {
+    ElMessage.error('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。')
+  } else {
+    const reader = new FileReader()
+    reader.readAsDataURL(file)
+    reader.onload = () => {
+      options.img = reader.result
+      options.fileName = file.name
+    }
+  }
+}
+/** 上传图片 */
+const uploadImg = async () => {
+  cropper.value.getCropBlob(async (data: any) => {
+    let formData = new FormData()
+    formData.append('avatarFile', data, options.fileName)
+    const res = await uploadAvatarApi(formData)
+    open.value = false
+    options.img = res.data.imgUrl
+    userStore.avatar = options.img
+    ElMessage.success('修改成功')
+    visible.value = false
+  })
+}
+/** 实时预览 */
+const realTime = (data: any) => {
+  options.previews = data
+}
+/** 关闭窗口 */
+const closeDialog = () => {
+  options.img = userStore.avatar
+  options.visible = false
+}
+</script>
+
+<style lang="scss" scoped>
+.user-info-head {
+  position: relative;
+  display: inline-block;
+
+  // width: 200px;
+  // height: 200px;
+
+  height: 120px;
+}
+.user-info-head:hover::after {
+  position: absolute;
+  inset: 0;
+  font-size: 24px;
+  font-style: normal;
+  line-height: 110px;
+  color: #eeeeee;
+  cursor: pointer;
+  content: '+';
+  background: rgb(0 0 0 / 50%);
+  border-radius: 50%;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+</style>

+ 1 - 1
src/views/tool/gen/components/genInfoForm.vue

@@ -62,7 +62,7 @@ const setItem = () => {
     {
     {
       label: '生成包路径',
       label: '生成包路径',
       prop: 'packageName',
       prop: 'packageName',
-      tooltip: '生成在哪个java包下,例如 com.km.system',
+      tooltip: '生成在哪个java包下,例如 com.taais.system',
       span: 12,
       span: 12,
       rules: [{ required: true, message: '生成包路径不能为空', trigger: 'blur' }],
       rules: [{ required: true, message: '生成包路径不能为空', trigger: 'blur' }],
       compOptions: {
       compOptions: {

+ 9 - 10
src/views/tool/gen/index.vue

@@ -3,14 +3,14 @@
     <ProTable ref="proTable" :columns="columns" row-key="tableId" :request-api="listTableApi">
     <ProTable ref="proTable" :columns="columns" row-key="tableId" :request-api="listTableApi">
       <!-- 表格 header 按钮 -->
       <!-- 表格 header 按钮 -->
       <template #tableHeader="scope">
       <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>
-        <el-button type="primary" v-auth="['tool:gen:import']" :icon="Upload" plain @click="batchAdd"> 导入表 </el-button>
+        <el-button type="primary" v-auth="['tool:gen:import']" icon="Upload" plain @click="batchAdd"> 导入表 </el-button>
         <el-button
         <el-button
           type="danger"
           type="danger"
           v-auth="['system:user:remove']"
           v-auth="['system:user:remove']"
-          :icon="Delete"
+          icon="Delete"
           plain
           plain
           :disabled="!scope.isSelected"
           :disabled="!scope.isSelected"
           @click="batchDelete(scope.selectedListIds)"
           @click="batchDelete(scope.selectedListIds)"
@@ -20,11 +20,11 @@
       </template>
       </template>
       <!-- 表格操作 -->
       <!-- 表格操作 -->
       <template #operation="scope">
       <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="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="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>
       </template>
       </template>
     </ProTable>
     </ProTable>
     <FormDialog ref="formDialogRef">
     <FormDialog ref="formDialogRef">
@@ -70,7 +70,6 @@ import ProTable from '@/components/ProTable/index.vue'
 import FormDialog from '@/components/CustomDialog/index.vue'
 import FormDialog from '@/components/CustomDialog/index.vue'
 import TableDialog from '@/components/TableDialog/index.vue'
 import TableDialog from '@/components/TableDialog/index.vue'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 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 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 实例
 // ProTable 实例
@@ -124,7 +123,7 @@ const batchAdd = () => {
 const handleGenTable = (_row?: { tableId: string }) => {
 const handleGenTable = (_row?: { tableId: string }) => {
   let tableIdArr: any[] | undefined = proTable.value?.selectedList.map(item => item.tableId)
   let tableIdArr: any[] | undefined = proTable.value?.selectedList.map(item => item.tableId)
   let tableIds = _row?.tableId || tableIdArr?.join(',')
   let tableIds = _row?.tableId || tableIdArr?.join(',')
-  useDownload(batchGenCodeApi, '生成代码', tableIds, false, 'zip', 'km.zip')
+  useDownload(batchGenCodeApi, '生成代码', tableIds, false, 'zip', 'taais.zip')
 }
 }
 
 
 // 同步表数据
 // 同步表数据

+ 5 - 1
tsconfig.json

@@ -4,6 +4,7 @@
     "useDefineForClassFields": true,
     "useDefineForClassFields": true,
     "module": "ESNext",
     "module": "ESNext",
     "moduleResolution": "Node",
     "moduleResolution": "Node",
+    // "moduleResolution": "Node",
     "types": ["vite/client"],
     "types": ["vite/client"],
 
 
     /* Strict Type-Checking Options */
     /* Strict Type-Checking Options */
@@ -29,7 +30,10 @@
       "@/*": ["src/*"],
       "@/*": ["src/*"],
       "#/*": ["types/*"]
       "#/*": ["types/*"]
     },
     },
-    "typeRoots": ["./node_modules/@types/", "./types"]
+    "typeRoots": ["./node_modules/@types/", "./types"],
+    "allowSyntheticDefaultImports": true,
+    // 禁止对同一个文件的不一致的引用。
+    "forceConsistentCasingInFileNames": true
   },
   },
   "include": [
   "include": [
     "src/**/*.ts",
     "src/**/*.ts",

+ 74 - 1
vite.config.ts

@@ -23,9 +23,11 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     root,
     root,
     resolve: {
     resolve: {
       alias: {
       alias: {
+        '~': resolve(__dirname, './'),
         '@': resolve(__dirname, './src'),
         '@': resolve(__dirname, './src'),
         'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
         'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
-      }
+      },
+      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
     },
     },
     define: {
     define: {
       __APP_INFO__: JSON.stringify(__APP_INFO__)
       __APP_INFO__: JSON.stringify(__APP_INFO__)
@@ -33,8 +35,23 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
     css: {
     css: {
       preprocessorOptions: {
       preprocessorOptions: {
         scss: {
         scss: {
+          javascriptEnabled: true,
           additionalData: `@import "@/styles/var.scss";`
           additionalData: `@import "@/styles/var.scss";`
         }
         }
+      },
+      postcss: {
+        plugins: [
+          {
+            postcssPlugin: 'internal:charset-removal',
+            AtRule: {
+              charset: atRule => {
+                if (atRule.name === 'charset') {
+                  atRule.remove()
+                }
+              }
+            }
+          }
+        ]
       }
       }
     },
     },
     server: {
     server: {
@@ -73,6 +90,62 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
           assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
           assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
         }
         }
       }
       }
+    },
+    // 预编译
+    optimizeDeps: {
+      include: [
+        'vue',
+        'vue-router',
+        'pinia',
+        'axios',
+        '@vueuse/core',
+        'path-to-regexp',
+        'echarts',
+        'vue-i18n',
+        'element-plus/es/components/form/style/css',
+        'element-plus/es/components/form-item/style/css',
+        'element-plus/es/components/button/style/css',
+        'element-plus/es/components/input/style/css',
+        'element-plus/es/components/input-number/style/css',
+        'element-plus/es/components/switch/style/css',
+        'element-plus/es/components/upload/style/css',
+        'element-plus/es/components/menu/style/css',
+        'element-plus/es/components/col/style/css',
+        'element-plus/es/components/icon/style/css',
+        'element-plus/es/components/row/style/css',
+        'element-plus/es/components/tag/style/css',
+        'element-plus/es/components/dialog/style/css',
+        'element-plus/es/components/loading/style/css',
+        'element-plus/es/components/radio/style/css',
+        'element-plus/es/components/radio-group/style/css',
+        'element-plus/es/components/popover/style/css',
+        'element-plus/es/components/scrollbar/style/css',
+        'element-plus/es/components/tooltip/style/css',
+        'element-plus/es/components/dropdown/style/css',
+        'element-plus/es/components/dropdown-menu/style/css',
+        'element-plus/es/components/dropdown-item/style/css',
+        'element-plus/es/components/sub-menu/style/css',
+        'element-plus/es/components/menu-item/style/css',
+        'element-plus/es/components/divider/style/css',
+        'element-plus/es/components/card/style/css',
+        'element-plus/es/components/link/style/css',
+        'element-plus/es/components/breadcrumb/style/css',
+        'element-plus/es/components/breadcrumb-item/style/css',
+        'element-plus/es/components/table/style/css',
+        'element-plus/es/components/tree-select/style/css',
+        'element-plus/es/components/table-column/style/css',
+        'element-plus/es/components/select/style/css',
+        'element-plus/es/components/option/style/css',
+        'element-plus/es/components/pagination/style/css',
+        'element-plus/es/components/tree/style/css',
+        'element-plus/es/components/alert/style/css',
+        'element-plus/es/components/checkbox/style/css',
+        'element-plus/es/components/date-picker/style/css',
+        'element-plus/es/components/transfer/style/css',
+        'element-plus/es/components/tabs/style/css',
+        'element-plus/es/components/image/style/css',
+        'element-plus/es/components/tab-pane/style/css'
+      ]
     }
     }
   }
   }
 })
 })

+ 22 - 5
yarn.lock

@@ -4604,6 +4604,11 @@ ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.1:
   resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
   resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
   integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
   integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
 
 
+image-conversion@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/image-conversion/-/image-conversion-2.1.1.tgz#8213fc19033f7dfdee20bb19792e8dcebd052417"
+  integrity sha512-hnMOmP7q2jxA+52FZ+wHNhg3fdFRlgfngsQH2JQHEQkafY7tj/8F15e6Rv/RxDegc872jvyaRHwMbkTZK1Cjbg==
+
 image-size@^0.5.1:
 image-size@^0.5.1:
   version "0.5.5"
   version "0.5.5"
   resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
   resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
@@ -4867,6 +4872,11 @@ is-plain-obj@^1.1, is-plain-obj@^1.1.0:
   resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
   integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
 
 
+is-plain-object@5.0.0, is-plain-object@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
 is-plain-object@^2.0.3, is-plain-object@^2.0.4:
 is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
   resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
@@ -4874,11 +4884,6 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   dependencies:
   dependencies:
     isobject "^3.0.1"
     isobject "^3.0.1"
 
 
-is-plain-object@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
-  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
-
 is-regex@^1.1.4:
 is-regex@^1.1.4:
   version "1.1.4"
   version "1.1.4"
   resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
   resolved "https://registry.npmmirror.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
@@ -7748,6 +7753,11 @@ vite@^5.0.8:
   optionalDependencies:
   optionalDependencies:
     fsevents "~2.3.3"
     fsevents "~2.3.3"
 
 
+vue-cropper@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.1.2.tgz#eff4437fe67989f8b9aaa74ba6046f0223a91ffa"
+  integrity sha512-S4cmvQzdNiVpNGigFwgULSwxWp55bdD1xf+RIEH+4mLDpD9/MUjNoHf970V3nFX7SerUrPZbOXG/ZBaD6pxfWQ==
+
 vue-demi@*, vue-demi@>=0.14.5, vue-demi@>=0.14.7:
 vue-demi@*, vue-demi@>=0.14.5, vue-demi@>=0.14.7:
   version "0.14.7"
   version "0.14.7"
   resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.7.tgz#8317536b3ef74c5b09f268f7782e70194567d8f2"
   resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.7.tgz#8317536b3ef74c5b09f268f7782e70194567d8f2"
@@ -7799,6 +7809,13 @@ vue-tsc@^1.8.25:
     "@vue/language-core" "1.8.27"
     "@vue/language-core" "1.8.27"
     semver "^7.5.4"
     semver "^7.5.4"
 
 
+vue-types@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.npmmirror.com/vue-types/-/vue-types-5.1.1.tgz#1052b85f440a90ad4ea8249d5aa6f231b92d062e"
+  integrity sha512-FMY/JCLWePXgGIcMDqYdJsQm1G0CDxEjq6W0+tZMJZlX37q/61eSGSIa/XFRwa9T7kkKXuxxl94/2kgxyWQqKw==
+  dependencies:
+    is-plain-object "5.0.0"
+
 vue@^3.3.11:
 vue@^3.3.11:
   version "3.4.25"
   version "3.4.25"
   resolved "https://registry.npmmirror.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"
   resolved "https://registry.npmmirror.com/vue/-/vue-3.4.25.tgz#e59d4ed36389647b52ff2fd7aa84bb6691f4205b"