Ver código fonte

Merge remote-tracking branch 'yudao/dev'

puhui999 2 anos atrás
pai
commit
5057e755e2
100 arquivos alterados com 5710 adições e 4026 exclusões
  1. 1 1
      .gitignore
  2. 4 1
      build/vite/index.ts
  3. 6 6
      src/api/bpm/userGroup/index.ts
  4. 3 21
      src/api/infra/apiErrorLog/index.ts
  5. 1 8
      src/api/infra/config/index.ts
  6. 15 15
      src/api/infra/dataSourceConfig/index.ts
  7. 2 13
      src/api/infra/file/index.ts
  8. 8 13
      src/api/infra/fileConfig/index.ts
  9. 11 0
      src/api/system/area/index.ts
  10. 21 8
      src/api/system/dict/dict.data.ts
  11. 16 8
      src/api/system/dict/dict.type.ts
  12. 0 46
      src/api/system/dict/types.ts
  13. 2 2
      src/api/system/errorCode/index.ts
  14. 6 11
      src/api/system/mail/account/index.ts
  15. 2 12
      src/api/system/mail/log/index.ts
  16. 6 14
      src/api/system/mail/template/index.ts
  17. 2 8
      src/api/system/oauth2/token.ts
  18. 2 10
      src/api/system/operatelog/index.ts
  19. 7 19
      src/api/system/post/index.ts
  20. 1 1
      src/api/system/user/index.ts
  21. 1 1
      src/components/ContentWrap/src/ContentWrap.vue
  22. 6 2
      src/components/Form/src/Form.vue
  23. 1 1
      src/components/Pagination/index.vue
  24. 10 2
      src/components/Search/src/Search.vue
  25. 4 2
      src/components/Table/src/Table.vue
  26. 10 0
      src/hooks/web/useCrudSchemas.ts
  27. 3 1
      src/hooks/web/useTable.ts
  28. 2 1
      src/locales/en.ts
  29. 2 1
      src/locales/zh-CN.ts
  30. 25 0
      src/router/modules/remaining.ts
  31. 3 3
      src/store/modules/dict.ts
  32. 2 0
      src/types/auto-components.d.ts
  33. 2 0
      src/types/auto-imports.d.ts
  34. 2 2
      src/types/descriptions.d.ts
  35. 1 51
      src/utils/formatTime.ts
  36. 18 0
      src/utils/index.ts
  37. 132 0
      src/views/bpm/group/UserGroupForm.vue
  38. 0 64
      src/views/bpm/group/group.data.ts
  39. 158 156
      src/views/bpm/group/index.vue
  40. 1 1
      src/views/bpm/processInstance/detail.vue
  41. 6 6
      src/views/bpm/taskAssignRule/index.vue
  42. 79 0
      src/views/infra/apiErrorLog/ApiErrorLogDetail.vue
  43. 0 76
      src/views/infra/apiErrorLog/apiErrorLog.data.ts
  44. 230 81
      src/views/infra/apiErrorLog/index.vue
  45. 2 2
      src/views/infra/codegen/components/CloumInfoForm.vue
  46. 21 6
      src/views/infra/config/index.vue
  47. 0 52
      src/views/infra/dataSourceConfig/dataSourceConfig.data.ts
  48. 111 0
      src/views/infra/dataSourceConfig/form.vue
  49. 86 129
      src/views/infra/dataSourceConfig/index.vue
  50. 83 0
      src/views/infra/file/form.vue
  51. 154 0
      src/views/infra/file/index.vue
  52. 0 77
      src/views/infra/fileConfig/fileConfig.data.ts
  53. 195 0
      src/views/infra/fileConfig/form.vue
  54. 166 262
      src/views/infra/fileConfig/index.vue
  55. 0 52
      src/views/infra/fileList/fileList.data.ts
  56. 0 173
      src/views/infra/fileList/index.vue
  57. 71 0
      src/views/system/area/form.vue
  58. 71 0
      src/views/system/area/index.vue
  59. 0 84
      src/views/system/dept/dept.data.ts
  60. 190 0
      src/views/system/dept/form.vue
  61. 149 146
      src/views/system/dept/index.vue
  62. 177 0
      src/views/system/dict/data.form.vue
  63. 202 0
      src/views/system/dict/data.vue
  64. 0 104
      src/views/system/dict/dict.data.ts
  65. 0 65
      src/views/system/dict/dict.type.ts
  66. 123 0
      src/views/system/dict/form.vue
  67. 187 233
      src/views/system/dict/index.vue
  68. 0 54
      src/views/system/errorCode/errorCode.data.ts
  69. 112 0
      src/views/system/errorCode/form.vue
  70. 207 123
      src/views/system/errorCode/index.vue
  71. 52 54
      src/views/system/mail/account/account.data.ts
  72. 66 0
      src/views/system/mail/account/form.vue
  73. 66 133
      src/views/system/mail/account/index.vue
  74. 31 0
      src/views/system/mail/log/detail.vue
  75. 44 83
      src/views/system/mail/log/index.vue
  76. 127 115
      src/views/system/mail/log/log.data.ts
  77. 66 0
      src/views/system/mail/template/form.vue
  78. 78 249
      src/views/system/mail/template/index.vue
  79. 115 0
      src/views/system/mail/template/send.vue
  80. 94 79
      src/views/system/mail/template/template.data.ts
  81. 18 4
      src/views/system/notice/index.vue
  82. 2 2
      src/views/system/notify/template/index.vue
  83. 148 53
      src/views/system/oauth2/token/index.vue
  84. 0 48
      src/views/system/oauth2/token/token.data.ts
  85. 80 0
      src/views/system/operatelog/detail.vue
  86. 198 57
      src/views/system/operatelog/index.vue
  87. 0 106
      src/views/system/operatelog/operatelog.data.ts
  88. 122 0
      src/views/system/post/PostForm.vue
  89. 0 91
      src/views/system/post/form.vue
  90. 183 56
      src/views/system/post/index.vue
  91. 0 58
      src/views/system/post/post.data.ts
  92. 130 0
      src/views/system/sensitiveWord/form.vue
  93. 198 164
      src/views/system/sensitiveWord/index.vue
  94. 0 74
      src/views/system/sensitiveWord/sensitiveWord.data.ts
  95. 137 0
      src/views/system/sms/smsChannel/form.vue
  96. 198 120
      src/views/system/sms/smsChannel/index.vue
  97. 0 76
      src/views/system/sms/smsChannel/sms.channel.data.ts
  98. 204 0
      src/views/system/tenant/form.vue
  99. 230 172
      src/views/system/tenant/index.vue
  100. 2 2
      src/views/system/user/index.vue

+ 1 - 1
.gitignore

@@ -5,4 +5,4 @@ dist-ssr
 *.local
 /dist*
 *-lock.*
-pnpm-debug
+pnpm-debug

+ 4 - 1
build/vite/index.ts

@@ -39,11 +39,14 @@ export function createVitePlugins(VITE_APP_TITLE: string) {
       imports: [
         'vue',
         'vue-router',
+        // 可额外添加需要 autoImport 的组件
         {
           '@/hooks/web/useI18n': ['useI18n'],
-          '@/hooks/web/useXTable': ['useXTable'],
           '@/hooks/web/useMessage': ['useMessage'],
+          '@/hooks/web/useXTable': ['useXTable'],
           '@/hooks/web/useVxeCrudSchemas': ['useVxeCrudSchemas'],
+          '@/hooks/web/useTable': ['useTable'],
+          '@/hooks/web/useCrudSchemas': ['useCrudSchemas'],
           '@/utils/formRules': ['required'],
           '@/utils/dict': ['DICT_TYPE']
         }

+ 6 - 6
src/api/bpm/userGroup/index.ts

@@ -11,7 +11,7 @@ export type UserGroupVO = {
 }
 
 // 创建用户组
-export const createUserGroupApi = async (data: UserGroupVO) => {
+export const createUserGroup = async (data: UserGroupVO) => {
   return await request.post({
     url: '/bpm/user-group/create',
     data: data
@@ -19,7 +19,7 @@ export const createUserGroupApi = async (data: UserGroupVO) => {
 }
 
 // 更新用户组
-export const updateUserGroupApi = async (data: UserGroupVO) => {
+export const updateUserGroup = async (data: UserGroupVO) => {
   return await request.put({
     url: '/bpm/user-group/update',
     data: data
@@ -27,21 +27,21 @@ export const updateUserGroupApi = async (data: UserGroupVO) => {
 }
 
 // 删除用户组
-export const deleteUserGroupApi = async (id: number) => {
+export const deleteUserGroup = async (id: number) => {
   return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
 }
 
 // 获得用户组
-export const getUserGroupApi = async (id: number) => {
+export const getUserGroup = async (id: number) => {
   return await request.get({ url: '/bpm/user-group/get?id=' + id })
 }
 
 // 获得用户组分页
-export const getUserGroupPageApi = async (params) => {
+export const getUserGroupPage = async (params) => {
   return await request.get({ url: '/bpm/user-group/page', params })
 }
 
 // 获取用户组精简信息列表
-export const listSimpleUserGroupsApi = async () => {
+export const listSimpleUserGroup = async () => {
   return await request.get({ url: '/bpm/user-group/list-all-simple' })
 }

+ 3 - 21
src/api/infra/apiErrorLog/index.ts

@@ -27,38 +27,20 @@ export interface ApiErrorLogVO {
   createTime: Date
 }
 
-export interface ApiErrorLogPageReqVO extends PageParam {
-  userId?: number
-  userType?: number
-  applicationName?: string
-  requestUrl?: string
-  exceptionTime?: Date[]
-  processStatus: number
-}
-
-export interface ApiErrorLogExportReqVO {
-  userId?: number
-  userType?: number
-  applicationName?: string
-  requestUrl?: string
-  exceptionTime?: Date[]
-  processStatus: number
-}
-
 // 查询列表API 访问日志
-export const getApiErrorLogPageApi = (params: ApiErrorLogPageReqVO) => {
+export const getApiErrorLogPage = (params: PageParam) => {
   return request.get({ url: '/infra/api-error-log/page', params })
 }
 
 // 更新 API 错误日志的处理状态
-export const updateApiErrorLogPageApi = (id: number, processStatus: number) => {
+export const updateApiErrorLogPage = (id: number, processStatus: number) => {
   return request.put({
     url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus
   })
 }
 
 // 导出API 访问日志
-export const exportApiErrorLogApi = (params: ApiErrorLogExportReqVO) => {
+export const exportApiErrorLog = (params) => {
   return request.download({
     url: '/infra/api-error-log/export-excel',
     params

+ 1 - 8
src/api/infra/config/index.ts

@@ -12,13 +12,6 @@ export interface ConfigVO {
   createTime: Date
 }
 
-export interface ConfigExportReqVO {
-  name?: string
-  key?: string
-  type?: number
-  createTime?: Date[]
-}
-
 // 查询参数列表
 export const getConfigPage = (params: PageParam) => {
   return request.get({ url: '/infra/config/page', params })
@@ -50,6 +43,6 @@ export const deleteConfig = (id: number) => {
 }
 
 // 导出参数
-export const exportConfigApi = (params: ConfigExportReqVO) => {
+export const exportConfigApi = (params) => {
   return request.download({ url: '/infra/config/export', params })
 }

+ 15 - 15
src/api/infra/dataSourceConfig/index.ts

@@ -1,35 +1,35 @@
 import request from '@/config/axios'
 
 export interface DataSourceConfigVO {
-  id: number
+  id: number | undefined
   name: string
   url: string
   username: string
   password: string
-  createTime: Date
-}
-
-// 查询数据源配置列表
-export const getDataSourceConfigListApi = () => {
-  return request.get({ url: '/infra/data-source-config/list' })
-}
-
-// 查询数据源配置详情
-export const getDataSourceConfigApi = (id: number) => {
-  return request.get({ url: '/infra/data-source-config/get?id=' + id })
+  createTime?: Date
 }
 
 // 新增数据源配置
-export const createDataSourceConfigApi = (data: DataSourceConfigVO) => {
+export const createDataSourceConfig = (data: DataSourceConfigVO) => {
   return request.post({ url: '/infra/data-source-config/create', data })
 }
 
 // 修改数据源配置
-export const updateDataSourceConfigApi = (data: DataSourceConfigVO) => {
+export const updateDataSourceConfig = (data: DataSourceConfigVO) => {
   return request.put({ url: '/infra/data-source-config/update', data })
 }
 
 // 删除数据源配置
-export const deleteDataSourceConfigApi = (id: number) => {
+export const deleteDataSourceConfig = (id: number) => {
   return request.delete({ url: '/infra/data-source-config/delete?id=' + id })
 }
+
+// 查询数据源配置详情
+export const getDataSourceConfig = (id: number) => {
+  return request.get({ url: '/infra/data-source-config/get?id=' + id })
+}
+
+// 查询数据源配置列表
+export const getDataSourceConfigList = () => {
+  return request.get({ url: '/infra/data-source-config/list' })
+}

+ 2 - 13
src/api/infra/fileList/index.ts → src/api/infra/file/index.ts

@@ -1,16 +1,5 @@
 import request from '@/config/axios'
 
-export interface FileVO {
-  id: number
-  configId: number
-  path: string
-  name: string
-  url: string
-  size: string
-  type: string
-  createTime: Date
-}
-
 export interface FilePageReqVO extends PageParam {
   path?: string
   type?: string
@@ -18,11 +7,11 @@ export interface FilePageReqVO extends PageParam {
 }
 
 // 查询文件列表
-export const getFilePageApi = (params: FilePageReqVO) => {
+export const getFilePage = (params: FilePageReqVO) => {
   return request.get({ url: '/infra/file/page', params })
 }
 
 // 删除文件
-export const deleteFileApi = (id: number) => {
+export const deleteFile = (id: number) => {
   return request.delete({ url: '/infra/file/delete?id=' + id })
 }

+ 8 - 13
src/api/infra/fileConfig/index.ts

@@ -13,6 +13,7 @@ export interface FileClientConfig {
   accessSecret?: string
   domain: string
 }
+
 export interface FileConfigVO {
   id: number
   name: string
@@ -24,43 +25,37 @@ export interface FileConfigVO {
   createTime: Date
 }
 
-export interface FileConfigPageReqVO extends PageParam {
-  name?: string
-  storage?: number
-  createTime?: Date[]
-}
-
 // 查询文件配置列表
-export const getFileConfigPageApi = (params: FileConfigPageReqVO) => {
+export const getFileConfigPage = (params: PageParam) => {
   return request.get({ url: '/infra/file-config/page', params })
 }
 
 // 查询文件配置详情
-export const getFileConfigApi = (id: number) => {
+export const getFileConfig = (id: number) => {
   return request.get({ url: '/infra/file-config/get?id=' + id })
 }
 
 // 更新文件配置为主配置
-export const updateFileConfigMasterApi = (id: number) => {
+export const updateFileConfigMaster = (id: number) => {
   return request.put({ url: '/infra/file-config/update-master?id=' + id })
 }
 
 // 新增文件配置
-export const createFileConfigApi = (data: FileConfigVO) => {
+export const createFileConfig = (data: FileConfigVO) => {
   return request.post({ url: '/infra/file-config/create', data })
 }
 
 // 修改文件配置
-export const updateFileConfigApi = (data: FileConfigVO) => {
+export const updateFileConfig = (data: FileConfigVO) => {
   return request.put({ url: '/infra/file-config/update', data })
 }
 
 // 删除文件配置
-export const deleteFileConfigApi = (id: number) => {
+export const deleteFileConfig = (id: number) => {
   return request.delete({ url: '/infra/file-config/delete?id=' + id })
 }
 
 // 测试文件配置
-export const testFileConfigApi = (id: number) => {
+export const testFileConfig = (id: number) => {
   return request.get({ url: '/infra/file-config/test?id=' + id })
 }

+ 11 - 0
src/api/system/area/index.ts

@@ -0,0 +1,11 @@
+import request from '@/config/axios'
+
+// 获得地区树
+export const getAreaTree = async () => {
+  return await request.get({ url: '/system/area/tree' })
+}
+
+// 获得 IP 对应的地区名
+export const getAreaByIp = async (ip: string) => {
+  return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
+}

+ 21 - 8
src/api/system/dict/dict.data.ts

@@ -1,36 +1,49 @@
 import request from '@/config/axios'
-import type { DictDataVO, DictDataPageReqVO, DictDataExportReqVO } from './types'
+
+export type DictDataVO = {
+  id: number | undefined
+  sort: number | undefined
+  label: string
+  value: string
+  dictType: string
+  status: number
+  colorType: string
+  cssClass: string
+  remark: string
+  createTime: Date
+}
 
 // 查询字典数据(精简)列表
-export const listSimpleDictDataApi = () => {
+export const listSimpleDictData = () => {
   return request.get({ url: '/system/dict-data/list-all-simple' })
 }
 
 // 查询字典数据列表
-export const getDictDataPageApi = (params: DictDataPageReqVO) => {
+export const getDictDataPage = (params: PageParam) => {
   return request.get({ url: '/system/dict-data/page', params })
 }
 
 // 查询字典数据详情
-export const getDictDataApi = (id: number) => {
+export const getDictData = (id: number) => {
   return request.get({ url: '/system/dict-data/get?id=' + id })
 }
 
 // 新增字典数据
-export const createDictDataApi = (data: DictDataVO) => {
+export const createDictData = (data: DictDataVO) => {
   return request.post({ url: '/system/dict-data/create', data })
 }
 
 // 修改字典数据
-export const updateDictDataApi = (data: DictDataVO) => {
+export const updateDictData = (data: DictDataVO) => {
   return request.put({ url: '/system/dict-data/update', data })
 }
 
 // 删除字典数据
-export const deleteDictDataApi = (id: number) => {
+export const deleteDictData = (id: number) => {
   return request.delete({ url: '/system/dict-data/delete?id=' + id })
 }
+
 // 导出字典类型数据
-export const exportDictDataApi = (params: DictDataExportReqVO) => {
+export const exportDictDataApi = (params) => {
   return request.get({ url: '/system/dict-data/export', params })
 }

+ 16 - 8
src/api/system/dict/dict.type.ts

@@ -1,36 +1,44 @@
 import request from '@/config/axios'
-import type { DictTypeVO, DictTypePageReqVO, DictTypeExportReqVO } from './types'
+
+export type DictTypeVO = {
+  id: number | undefined
+  name: string
+  type: string
+  status: number
+  remark: string
+  createTime: Date
+}
 
 // 查询字典(精简)列表
-export const listSimpleDictTypeApi = () => {
+export const listSimpleDictType = () => {
   return request.get({ url: '/system/dict-type/list-all-simple' })
 }
 
 // 查询字典列表
-export const getDictTypePageApi = (params: DictTypePageReqVO) => {
+export const getDictTypePage = (params: PageParam) => {
   return request.get({ url: '/system/dict-type/page', params })
 }
 
 // 查询字典详情
-export const getDictTypeApi = (id: number) => {
+export const getDictType = (id: number) => {
   return request.get({ url: '/system/dict-type/get?id=' + id })
 }
 
 // 新增字典
-export const createDictTypeApi = (data: DictTypeVO) => {
+export const createDictType = (data: DictTypeVO) => {
   return request.post({ url: '/system/dict-type/create', data })
 }
 
 // 修改字典
-export const updateDictTypeApi = (data: DictTypeVO) => {
+export const updateDictType = (data: DictTypeVO) => {
   return request.put({ url: '/system/dict-type/update', data })
 }
 
 // 删除字典
-export const deleteDictTypeApi = (id: number) => {
+export const deleteDictType = (id: number) => {
   return request.delete({ url: '/system/dict-type/delete?id=' + id })
 }
 // 导出字典类型
-export const exportDictTypeApi = (params: DictTypeExportReqVO) => {
+export const exportDictType = (params) => {
   return request.get({ url: '/system/dict-type/export', params })
 }

+ 0 - 46
src/api/system/dict/types.ts

@@ -1,46 +0,0 @@
-export type DictTypeVO = {
-  id: number
-  name: string
-  type: string
-  status: number
-  remark: string
-  createTime: Date
-}
-
-export type DictTypePageReqVO = {
-  name: string
-  type: string
-  status: number
-  createTime: Date[]
-}
-
-export type DictTypeExportReqVO = {
-  name: string
-  type: string
-  status: number
-  createTime: Date[]
-}
-
-export type DictDataVO = {
-  id: number
-  sort: number
-  label: string
-  value: string
-  dictType: string
-  status: number
-  colorType: string
-  cssClass: string
-  remark: string
-  createTime: Date
-}
-export type DictDataPageReqVO = {
-  label: string
-  dictType: string
-  status: number
-}
-
-export type DictDataExportReqVO = {
-  label: string
-  dictType: string
-  status: number
-}

+ 2 - 2
src/api/system/errorCode/index.ts

@@ -1,10 +1,10 @@
 import request from '@/config/axios'
 
 export interface ErrorCodeVO {
-  id: number
+  id: number | undefined
   type: number
   applicationName: string
-  code: number
+  code: number | undefined
   message: string
   memo: string
   createTime: Date

+ 6 - 11
src/api/system/mail/account/index.ts

@@ -10,37 +10,32 @@ export interface MailAccountVO {
   sslEnable: boolean
 }
 
-export interface MailAccountPageReqVO extends PageParam {
-  mail?: string
-  username?: string
-}
-
 // 查询邮箱账号列表
-export const getMailAccountPageApi = async (params: MailAccountPageReqVO) => {
+export const getMailAccountPage = async (params: PageParam) => {
   return await request.get({ url: '/system/mail-account/page', params })
 }
 
 // 查询邮箱账号详情
-export const getMailAccountApi = async (id: number) => {
+export const getMailAccount = async (id: number) => {
   return await request.get({ url: '/system/mail-account/get?id=' + id })
 }
 
 // 新增邮箱账号
-export const createMailAccountApi = async (data: MailAccountVO) => {
+export const createMailAccount = async (data: MailAccountVO) => {
   return await request.post({ url: '/system/mail-account/create', data })
 }
 
 // 修改邮箱账号
-export const updateMailAccountApi = async (data: MailAccountVO) => {
+export const updateMailAccount = async (data: MailAccountVO) => {
   return await request.put({ url: '/system/mail-account/update', data })
 }
 
 // 删除邮箱账号
-export const deleteMailAccountApi = async (id: number) => {
+export const deleteMailAccount = async (id: number) => {
   return await request.delete({ url: '/system/mail-account/delete?id=' + id })
 }
 
 // 获得邮箱账号精简列表
-export const getSimpleMailAccounts = async () => {
+export const getSimpleMailAccountList = async () => {
   return request.get({ url: '/system/mail-account/list-all-simple' })
 }

+ 2 - 12
src/api/system/mail/log/index.ts

@@ -19,22 +19,12 @@ export interface MailLogVO {
   sendException: string
 }
 
-export interface MailLogPageReqVO extends PageParam {
-  userId?: number
-  userType?: number
-  toMail?: string
-  accountId?: number
-  templateId?: number
-  sendStatus?: number
-  sendTime?: Date[]
-}
-
 // 查询邮件日志列表
-export const getMailLogPageApi = async (params: MailLogPageReqVO) => {
+export const getMailLogPage = async (params: PageParam) => {
   return await request.get({ url: '/system/mail-log/page', params })
 }
 
 // 查询邮件日志详情
-export const getMailLogApi = async (id: number) => {
+export const getMailLog = async (id: number) => {
   return await request.get({ url: '/system/mail-log/get?id=' + id })
 }

+ 6 - 14
src/api/system/mail/template/index.ts

@@ -13,14 +13,6 @@ export interface MailTemplateVO {
   remark: string
 }
 
-export interface MailTemplatePageReqVO extends PageParam {
-  name?: string
-  code?: string
-  accountId?: number
-  status?: number
-  createTime?: Date[]
-}
-
 export interface MailSendReqVO {
   mail: string
   templateCode: string
@@ -28,31 +20,31 @@ export interface MailSendReqVO {
 }
 
 // 查询邮件模版列表
-export const getMailTemplatePageApi = async (params: MailTemplatePageReqVO) => {
+export const getMailTemplatePage = async (params: PageParam) => {
   return await request.get({ url: '/system/mail-template/page', params })
 }
 
 // 查询邮件模版详情
-export const getMailTemplateApi = async (id: number) => {
+export const getMailTemplate = async (id: number) => {
   return await request.get({ url: '/system/mail-template/get?id=' + id })
 }
 
 // 新增邮件模版
-export const createMailTemplateApi = async (data: MailTemplateVO) => {
+export const createMailTemplate = async (data: MailTemplateVO) => {
   return await request.post({ url: '/system/mail-template/create', data })
 }
 
 // 修改邮件模版
-export const updateMailTemplateApi = async (data: MailTemplateVO) => {
+export const updateMailTemplate = async (data: MailTemplateVO) => {
   return await request.put({ url: '/system/mail-template/update', data })
 }
 
 // 删除邮件模版
-export const deleteMailTemplateApi = async (id: number) => {
+export const deleteMailTemplate = async (id: number) => {
   return await request.delete({ url: '/system/mail-template/delete?id=' + id })
 }
 
 // 发送邮件
-export const sendMailApi = (data: MailSendReqVO) => {
+export const sendMail = (data: MailSendReqVO) => {
   return request.post({ url: '/system/mail-template/send-mail', data })
 }

+ 2 - 8
src/api/system/oauth2/token.ts

@@ -11,18 +11,12 @@ export interface OAuth2TokenVO {
   expiresTime: Date
 }
 
-export interface OAuth2TokenPageReqVO extends PageParam {
-  userId?: number
-  userType?: number
-  clientId?: string
-}
-
 // 查询 token列表
-export const getAccessTokenPageApi = (params: OAuth2TokenPageReqVO) => {
+export const getAccessTokenPage = (params: PageParam) => {
   return request.get({ url: '/system/oauth2-token/page', params })
 }
 
 // 删除 token
-export const deleteAccessTokenApi = (accessToken: number) => {
+export const deleteAccessToken = (accessToken: number) => {
   return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
 }

+ 2 - 10
src/api/system/operatelog/index.ts

@@ -23,19 +23,11 @@ export type OperateLogVO = {
   resultData: string
 }
 
-export interface OperateLogPageReqVO extends PageParam {
-  module?: string
-  userNickname?: string
-  type?: number
-  success?: boolean
-  startTime?: Date[]
-}
-
 // 查询操作日志列表
-export const getOperateLogPageApi = (params: OperateLogPageReqVO) => {
+export const getOperateLogPage = (params: PageParam) => {
   return request.get({ url: '/system/operate-log/page', params })
 }
 // 导出操作日志
-export const exportOperateLogApi = (params: OperateLogPageReqVO) => {
+export const exportOperateLog = (params) => {
   return request.download({ url: '/system/operate-log/export', params })
 }

+ 7 - 19
src/api/system/post/index.ts

@@ -10,49 +10,37 @@ export interface PostVO {
   createTime?: Date
 }
 
-export interface PostPageReqVO extends PageParam {
-  code?: string
-  name?: string
-  status?: number
-}
-
-export interface PostExportReqVO {
-  code?: string
-  name?: string
-  status?: number
-}
-
 // 查询岗位列表
-export const getPostPageApi = async (params: PostPageReqVO) => {
+export const getPostPage = async (params: PageParam) => {
   return await request.get({ url: '/system/post/page', params })
 }
 
 // 获取岗位精简信息列表
-export const listSimplePostsApi = async () => {
+export const getSimplePostList = async () => {
   return await request.get({ url: '/system/post/list-all-simple' })
 }
 
 // 查询岗位详情
-export const getPostApi = async (id: number) => {
+export const getPost = async (id: number) => {
   return await request.get({ url: '/system/post/get?id=' + id })
 }
 
 // 新增岗位
-export const createPostApi = async (data: PostVO) => {
+export const createPost = async (data: PostVO) => {
   return await request.post({ url: '/system/post/create', data })
 }
 
 // 修改岗位
-export const updatePostApi = async (data: PostVO) => {
+export const updatePost = async (data: PostVO) => {
   return await request.put({ url: '/system/post/update', data })
 }
 
 // 删除岗位
-export const deletePostApi = async (id: number) => {
+export const deletePost = async (id: number) => {
   return await request.delete({ url: '/system/post/delete?id=' + id })
 }
 
 // 导出岗位
-export const exportPostApi = async (params: PostExportReqVO) => {
+export const exportPost = async (params) => {
   return await request.download({ url: '/system/post/export', params })
 }

+ 1 - 1
src/api/system/user/index.ts

@@ -86,6 +86,6 @@ export const updateUserStatusApi = (id: number, status: number) => {
 }
 
 // 获取用户精简信息列表
-export const getListSimpleUsersApi = () => {
+export const getSimpleUserList = () => {
   return request.get({ url: '/system/user/list-all-simple' })
 }

+ 1 - 1
src/components/ContentWrap/src/ContentWrap.vue

@@ -13,7 +13,7 @@ defineProps({
 </script>
 
 <template>
-  <ElCard :class="[prefixCls, 'mb-20px']" shadow="never">
+  <ElCard :class="[prefixCls, 'mb-15px']" shadow="never">
     <template v-if="title" #header>
       <div class="flex items-center">
         <span class="text-16px font-700">{{ title }}</span>

+ 6 - 2
src/components/Form/src/Form.vue

@@ -35,7 +35,8 @@ export default defineComponent({
       default: () => []
     },
     // 是否需要栅格布局
-    isCol: propTypes.bool.def(true),
+    // update by 芋艿:将 true 改成 false,因为项目更常用这种方式
+    isCol: propTypes.bool.def(false),
     // 表单数据对象
     model: {
       type: Object as PropType<Recordable>,
@@ -46,7 +47,9 @@ export default defineComponent({
     // 是否自定义内容
     isCustom: propTypes.bool.def(false),
     // 表单label宽度
-    labelWidth: propTypes.oneOfType([String, Number]).def('auto')
+    labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
+    // 是否 loading 数据中 add by 芋艿
+    vLoading: propTypes.bool.def(false)
   },
   emits: ['register'],
   setup(props, { slots, expose, emit }) {
@@ -280,6 +283,7 @@ export default defineComponent({
         {...getFormBindValue()}
         model={props.isCustom ? props.model : formModel}
         class={prefixCls}
+        v-loading={props.vLoading}
       >
         {{
           // 如果需要自定义,就什么都不渲染,而是提供默认插槽

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

@@ -5,7 +5,7 @@
     class="float-right mt-15px mb-15px"
     :background="true"
     layout="total, sizes, prev, pager, next, jumper"
-    :page-sizes="[10, 20, 30, 50]"
+    :page-sizes="[10, 20, 30, 50, 100]"
     v-model:current-page="currentPage"
     v-model:page-size="pageSize"
     :pager-count="pagerCount"

+ 10 - 2
src/components/Search/src/Search.vue

@@ -98,6 +98,7 @@ const setVisible = () => {
 </script>
 
 <template>
+  <!-- update by 芋艿:class="-mb-15px" 用于降低和 ContentWrap 组件的底部距离,避免空隙过大 -->
   <Form
     :is-custom="false"
     :label-width="labelWidth"
@@ -106,21 +107,26 @@ const setVisible = () => {
     :is-col="isCol"
     :schema="newSchema"
     @register="register"
+    class="-mb-15px"
   >
     <template #action>
       <div v-if="layout === 'inline'">
-        <ElButton v-if="showSearch" type="primary" @click="search">
+        <!-- update by 芋艿:去除搜索的 type="primary",颜色变淡一点 -->
+        <ElButton v-if="showSearch" @click="search">
           <Icon icon="ep:search" class="mr-5px" />
           {{ t('common.query') }}
         </ElButton>
+        <!-- update by 芋艿:将 icon="ep:refresh-right" 修改成 icon="ep:refresh",和 ruoyi-vue 搜索保持一致  -->
         <ElButton v-if="showReset" @click="reset">
-          <Icon icon="ep:refresh-right" class="mr-5px" />
+          <Icon icon="ep:refresh" class="mr-5px" />
           {{ t('common.reset') }}
         </ElButton>
         <ElButton v-if="expand" text @click="setVisible">
           {{ t(visible ? 'common.shrink' : 'common.expand') }}
           <Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" />
         </ElButton>
+        <!-- add by 芋艿:补充在搜索后的按钮 -->
+        <slot name="actionMore"></slot>
       </div>
     </template>
     <template #[name] v-for="name in Object.keys($slots)" :key="name"
@@ -142,6 +148,8 @@ const setVisible = () => {
         {{ t(visible ? 'common.shrink' : 'common.expand') }}
         <Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" />
       </ElButton>
+      <!-- add by 芋艿:补充在搜索后的按钮 -->
+      <slot name="actionMore"></slot>
     </div>
   </template>
 </template>

+ 4 - 2
src/components/Table/src/Table.vue

@@ -104,11 +104,12 @@ export default defineComponent({
     })
 
     const pagination = computed(() => {
+      // update by 芋艿:保持和 Pagination 组件的逻辑一致
       return Object.assign(
         {
           small: false,
           background: true,
-          pagerCount: 5,
+          pagerCount: document.body.clientWidth < 992 ? 5 : 7,
           layout: 'total, sizes, prev, pager, next, jumper',
           pageSizes: [10, 20, 30, 50, 100],
           disabled: false,
@@ -283,10 +284,11 @@ export default defineComponent({
           }}
         </ElTable>
         {unref(getProps).pagination ? (
+          // update by 芋艿:保持和 Pagination 组件一致
           <ElPagination
             v-model:pageSize={pageSizeRef.value}
             v-model:currentPage={currentPageRef.value}
-            class="mt-10px"
+            class="float-right mt-15px mb-15px"
             {...unref(pagination)}
           ></ElPagination>
         ) : undefined}

+ 10 - 0
src/hooks/web/useCrudSchemas.ts

@@ -8,6 +8,7 @@ import { FormSchema } from '@/types/form'
 import { TableColumn } from '@/types/table'
 import { DescriptionsSchema } from '@/types/descriptions'
 import { ComponentOptions, ComponentProps } from '@/types/components'
+import { DictTag } from '@/components/DictTag'
 
 export type CrudSchema = Omit<TableColumn, 'children'> & {
   isSearch?: boolean // 是否在查询显示
@@ -151,6 +152,15 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
   const tableColumns = treeMap<CrudSchema>(crudSchema, {
     conversion: (schema: CrudSchema) => {
       if (schema?.isTable !== false && schema?.table?.show !== false) {
+        // add by 芋艿:增加对 dict 字典数据的支持
+        if (!schema.formatter && schema.dictType) {
+          schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => {
+            return h(DictTag, {
+              type: schema.dictType!, // ! 表示一定不为空
+              value: cellValue
+            })
+          }
+        }
         return {
           ...schema.table,
           ...schema

+ 3 - 1
src/hooks/web/useTable.ts

@@ -218,6 +218,8 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
     register,
     elTableRef,
     tableObject,
-    methods
+    methods,
+    // add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一
+    tableMethods: methods
   }
 }

+ 2 - 1
src/locales/en.ts

@@ -297,7 +297,8 @@ export default {
     typeCreate: 'Dict Type Create',
     typeUpdate: 'Dict Type Eidt',
     dataCreate: 'Dict Data Create',
-    dataUpdate: 'Dict Data Eidt'
+    dataUpdate: 'Dict Data Eidt',
+    fileUpload: 'File Upload'
   },
   dialog: {
     dialog: 'Dialog',

+ 2 - 1
src/locales/zh-CN.ts

@@ -297,7 +297,8 @@ export default {
     typeCreate: '字典类型新增',
     typeUpdate: '字典类型编辑',
     dataCreate: '字典数据新增',
-    dataUpdate: '字典数据编辑'
+    dataUpdate: '字典数据编辑',
+    fileUpload: '上传文件'
   },
   dialog: {
     dialog: '弹窗',

+ 25 - 0
src/router/modules/remaining.ts

@@ -104,6 +104,31 @@ const remainingRouter: AppRouteRecordRaw[] = [
       }
     ]
   },
+
+  {
+    path: '/dict',
+    component: Layout,
+    name: 'dict',
+    meta: {
+      hidden: true
+    },
+    children: [
+      {
+        path: 'type/data/:dictType',
+        component: () => import('@/views/system/dict/data.vue'),
+        name: 'data',
+        meta: {
+          title: '字典数据',
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: '',
+          activeMenu: 'system/dict/data'
+        }
+      }
+    ]
+  },
+
   {
     path: '/codegen',
     component: Layout,

+ 3 - 3
src/store/modules/dict.ts

@@ -3,7 +3,7 @@ import { store } from '../index'
 import { DictDataVO } from '@/api/system/dict/types'
 import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
 const { wsCache } = useCache('sessionStorage')
-import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
+import { listSimpleDictData } from '@/api/system/dict/dict.data'
 
 export interface DictValueType {
   value: any
@@ -44,7 +44,7 @@ export const useDictStore = defineStore('dict', {
         this.dictMap = dictMap
         this.isSetDict = true
       } else {
-        const res = await listSimpleDictDataApi()
+        const res = await listSimpleDictData()
         // 设置数据
         const dictDataMap = new Map<string, any>()
         res.forEach((dictData: DictDataVO) => {
@@ -74,7 +74,7 @@ export const useDictStore = defineStore('dict', {
     },
     async resetDict() {
       wsCache.delete(CACHE_KEY.DICT_CACHE)
-      const res = await listSimpleDictDataApi()
+      const res = await listSimpleDictData()
       // 设置数据
       const dictDataMap = new Map<string, any>()
       res.forEach((dictData: DictDataVO) => {

+ 2 - 0
src/types/auto-components.d.ts

@@ -26,6 +26,7 @@ declare module '@vue/runtime-core' {
     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@@ -67,6 +68,7 @@ declare module '@vue/runtime-core' {
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTableV2: typeof import('element-plus/es')['ElTableV2']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']

+ 2 - 0
src/types/auto-imports.d.ts

@@ -52,6 +52,7 @@ declare global {
   const triggerRef: typeof import('vue')['triggerRef']
   const unref: typeof import('vue')['unref']
   const useAttrs: typeof import('vue')['useAttrs']
+  const useCrudSchemas: typeof import('@/hooks/web/useCrudSchemas')['useCrudSchemas']
   const useCssModule: typeof import('vue')['useCssModule']
   const useCssVars: typeof import('vue')['useCssVars']
   const useI18n: typeof import('@/hooks/web/useI18n')['useI18n']
@@ -60,6 +61,7 @@ declare global {
   const useRoute: typeof import('vue-router')['useRoute']
   const useRouter: typeof import('vue-router')['useRouter']
   const useSlots: typeof import('vue')['useSlots']
+  const useTable: typeof import('@/hooks/web/useTable')['useTable']
   const useVxeCrudSchemas: typeof import('@/hooks/web/useVxeCrudSchemas')['useVxeCrudSchemas']
   const useXTable: typeof import('@/hooks/web/useXTable')['useXTable']
   const watch: typeof import('vue')['watch']

+ 2 - 2
src/types/descriptions.d.ts

@@ -8,6 +8,6 @@ export interface DescriptionsSchema {
   labelAlign?: 'left' | 'center' | 'right'
   className?: string
   labelClassName?: string
-  dateFormat?: string
-  dictType?: string
+  dateFormat?: string // add by 星语:支持时间的格式化
+  dictType?: string // add by 星语:支持 dict 字典数据
 }

+ 1 - 51
src/utils/formatTime.ts

@@ -12,57 +12,7 @@ import dayjs from 'dayjs'
  * @returns 返回拼接后的时间字符串
  */
 export function formatDate(date: Date, format: string): string {
-  const we = date.getDay() // 星期
-  const z = getWeek(date) // 周
-  const qut = Math.floor((date.getMonth() + 3) / 3).toString() // 季度
-  const opt: { [key: string]: string } = {
-    'Y+': date.getFullYear().toString(), // 年
-    'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
-    'd+': date.getDate().toString(), // 日
-    'H+': date.getHours().toString(), // 时
-    'M+': date.getMinutes().toString(), // 分
-    'S+': date.getSeconds().toString(), // 秒
-    'q+': qut // 季度
-  }
-  // 中文数字 (星期)
-  const week: { [key: string]: string } = {
-    '0': '日',
-    '1': '一',
-    '2': '二',
-    '3': '三',
-    '4': '四',
-    '5': '五',
-    '6': '六'
-  }
-  // 中文数字(季度)
-  const quarter: { [key: string]: string } = {
-    '1': '一',
-    '2': '二',
-    '3': '三',
-    '4': '四'
-  }
-  if (/(W+)/.test(format))
-    format = format.replace(
-      RegExp.$1,
-      RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]
-    )
-  if (/(Q+)/.test(format))
-    format = format.replace(
-      RegExp.$1,
-      RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]
-    )
-  if (/(Z+)/.test(format))
-    format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '')
-  for (const k in opt) {
-    const r = new RegExp('(' + k + ')').exec(format)
-    // 若输入的长度不为1,则前面补零
-    if (r)
-      format = format.replace(
-        r[1],
-        RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0')
-      )
-  }
-  return format
+  return dayjs(date).format(format)
 }
 // 日期格式化
 export function parseTime(time: any, pattern?: string) {

+ 18 - 0
src/utils/index.ts

@@ -137,3 +137,21 @@ export const generateUUID = () => {
     return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
   })
 }
+
+/**
+ * element plus 的文件大小 Formatter 实现
+ *
+ * @param row 行数据
+ * @param column 字段
+ * @param cellValue 字段值
+ */
+// @ts-ignore
+export const fileSizeFormatter = (row, column, cellValue) => {
+  const fileSize = cellValue
+  const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+  const srcSize = parseFloat(fileSize)
+  const index = Math.floor(Math.log(srcSize) / Math.log(1024))
+  const size = srcSize / Math.pow(1024, index)
+  const sizeStr = size.toFixed(2) //保留的小数位数
+  return sizeStr + ' ' + unitArr[index]
+}

+ 132 - 0
src/views/bpm/group/UserGroupForm.vue

@@ -0,0 +1,132 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="组名" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入组名" />
+      </el-form-item>
+      <el-form-item label="描述">
+        <el-input type="textarea" v-model="formData.name" placeholder="请输入描述" />
+      </el-form-item>
+      <el-form-item label="成员" prop="memberUserIds">
+        <el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员">
+          <el-option
+            v-for="user in userList"
+            :key="user.id"
+            :label="user.nickname"
+            :value="user.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as UserGroupApi from '@/api/bpm/userGroup'
+import * as UserApi from '@/api/system/user'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: undefined,
+  description: undefined,
+  memberUserIds: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive({
+  name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
+  description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
+  memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const userList = ref([]) // 用户列表
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await UserGroupApi.getUserGroup(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载用户列表
+  userList.value = await UserApi.getSimpleUserList()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as UserGroupApi.UserGroupVO
+    if (formType.value === 'create') {
+      await UserGroupApi.createUserGroup(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await UserGroupApi.updateUserGroup(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: undefined,
+    description: undefined,
+    memberUserIds: undefined,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 0 - 64
src/views/bpm/group/group.data.ts

@@ -1,64 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  description: [required],
-  memberUserIds: [required],
-  status: [required]
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '编号',
-  action: true,
-  searchSpan: 8,
-  columns: [
-    {
-      title: '组名',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '成员',
-      field: 'memberUserIds',
-      table: {
-        slots: {
-          default: 'memberUserIds_default'
-        }
-      }
-    },
-    {
-      title: '描述',
-      field: 'description'
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      isSearch: true,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      },
-      table: {
-        width: 180
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 158 - 156
src/views/bpm/group/index.vue

@@ -1,182 +1,184 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['bpm:user-group:create']"
-          @click="handleCreate()"
-        />
-      </template>
-      <template #memberUserIds_default="{ row }">
-        <span v-for="userId in row.memberUserIds" :key="userId">
-          {{ getUserNickname(userId) }} &nbsp;
-        </span>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['bpm:user-group:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['bpm:user-group:query']"
-          @click="handleDetail(row.id)"
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="组名" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入组名"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['bpm:user-group:delete']"
-          @click="deleteData(row.id)"
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
         />
-      </template>
-    </XTable>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          @click="openForm('create')"
+          v-hasPermi="['bpm:user-group:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
 
-  <XModal v-model="dialogVisible" :title="dialogTitle" :mask-closable="false">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    >
-      <template #memberUserIds="form">
-        <el-select v-model="form.memberUserIds" multiple>
-          <el-option v-for="item in users" :key="item.id" :label="item.nickname" :value="item.id" />
-        </el-select>
-      </template>
-    </Form>
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    >
-      <template #memberUserIds="{ row }">
-        <span v-for="userId in row.memberUserIds" :key="userId">
-          {{ getUserNickname(userId) }} &nbsp;
-        </span>
-      </template>
-    </Descriptions>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm"
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="组名" align="center" prop="name" />
+      <el-table-column label="描述" align="center" prop="description" />
+      <el-table-column label="成员" align="center">
+        <template #default="scope">
+          <span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px">
+            {{ userList.find((user) => user.id === userId)?.nickname }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['bpm:user-group:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['bpm:user-group:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <UserGroupForm ref="formRef" @success="getList" />
 </template>
 
-<script setup lang="ts">
-// 业务相关的 import
+<script setup lang="ts" name="UserGroup">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 import * as UserGroupApi from '@/api/bpm/userGroup'
-import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
-import { allSchemas, rules } from './group.data'
-import { FormExpose } from '@/components/Form'
-
-const { t } = useI18n() // 国际化
+import * as UserApi from '@/api/system/user'
+import UserGroupForm from './UserGroupForm.vue'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: UserGroupApi.getUserGroupPageApi,
-  deleteApi: UserGroupApi.deleteUserGroupApi
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: null,
+  status: null,
+  createTime: []
 })
-// 用户列表
-const users = ref<UserVO[]>([])
+const queryFormRef = ref() // 搜索的表单
+const userList = ref([]) // 用户列表
 
-const getUserNickname = (userId) => {
-  for (const user of users.value) {
-    if (user.id === userId) {
-      return user.nickname
-    }
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await UserGroupApi.getUserGroupPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
-  return '未知(' + userId + ')'
-}
-
-// ========== CRUD 相关 ==========
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await UserGroupApi.getUserGroupApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  detailData.value = await UserGroupApi.getUserGroupApi(rowId)
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as UserGroupApi.UserGroupVO
-        if (actionType.value === 'create') {
-          await UserGroupApi.createUserGroupApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await UserGroupApi.updateUserGroupApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await UserGroupApi.deleteUserGroup(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// ========== 初始化 ==========
-onMounted(() => {
-  getListSimpleUsersApi().then((data) => {
-    users.value = data
-  })
+/** 初始化 **/
+onMounted(async () => {
+  await getList()
+  // 加载用户列表
+  userList.value = await UserApi.getSimpleUserList()
 })
 </script>

+ 1 - 1
src/views/bpm/processInstance/detail.vue

@@ -378,7 +378,7 @@ onMounted(() => {
   // 加载详情
   getDetail()
   // 加载用户的列表
-  UserApi.getListSimpleUsersApi().then((data) => {
+  UserApi.getSimpleUserList().then((data) => {
     userOptions.value.push(...data)
   })
 })

+ 6 - 6
src/views/bpm/taskAssignRule/index.vue

@@ -139,9 +139,9 @@ import { FormInstance } from 'element-plus'
 // 业务相关的 import
 import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
 import { listSimpleRolesApi } from '@/api/system/role'
-import { listSimplePostsApi } from '@/api/system/post'
-import { getListSimpleUsersApi } from '@/api/system/user'
-import { listSimpleUserGroupsApi } from '@/api/bpm/userGroup'
+import { getSimplePostList } from '@/api/system/post'
+import { getSimpleUserList } from '@/api/system/user'
+import { listSimpleUserGroup } from '@/api/bpm/userGroup'
 import { listSimpleDeptApi } from '@/api/system/dept'
 import { DICT_TYPE, getDictOptions } from '@/utils/dict'
 import { handleTree, defaultProps } from '@/utils/tree'
@@ -336,17 +336,17 @@ onMounted(() => {
   })
   // 获得岗位列表
   postOptions.value = []
-  listSimplePostsApi().then((data) => {
+  getSimplePostList().then((data) => {
     postOptions.value.push(...data)
   })
   // 获得用户列表
   userOptions.value = []
-  getListSimpleUsersApi().then((data) => {
+  getSimpleUserList().then((data) => {
     userOptions.value.push(...data)
   })
   // 获得用户组列表
   userGroupOptions.value = []
-  listSimpleUserGroupsApi().then((data) => {
+  listSimpleUserGroup().then((data) => {
     userGroupOptions.value.push(...data)
   })
   if (!isShow) {

+ 79 - 0
src/views/infra/apiErrorLog/ApiErrorLogDetail.vue

@@ -0,0 +1,79 @@
+<template>
+  <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
+    <el-descriptions border :column="1">
+      <el-descriptions-item label="日志主键" min-width="120">
+        {{ detailData.id }}
+      </el-descriptions-item>
+      <el-descriptions-item label="链路追踪">
+        {{ detailData.traceId }}
+      </el-descriptions-item>
+      <el-descriptions-item label="应用名">
+        {{ detailData.applicationName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="用户编号">
+        {{ detailData.userId }}
+        <dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
+      </el-descriptions-item>
+      <el-descriptions-item label="用户 IP">
+        {{ detailData.userIp }}
+      </el-descriptions-item>
+      <el-descriptions-item label="用户 UA">
+        {{ detailData.userAgent }}
+      </el-descriptions-item>
+      <el-descriptions-item label="请求信息">
+        {{ detailData.requestMethod }} {{ detailData.requestUrl }}
+      </el-descriptions-item>
+      <el-descriptions-item label="请求参数">
+        {{ detailData.requestParams }}
+      </el-descriptions-item>
+      <el-descriptions-item label="异常时间">
+        {{ formatDate(detailData.exceptionTime, 'YYYY-MM-DD HH:mm:ss') }}
+      </el-descriptions-item>
+      <el-descriptions-item label="异常名">
+        {{ detailData.exceptionName }}
+      </el-descriptions-item>
+      <el-descriptions-item label="异常堆栈" v-if="detailData.exceptionStackTrace">
+        <el-input
+          type="textarea"
+          :readonly="true"
+          :autosize="{ maxRows: 20 }"
+          v-model="detailData.exceptionStackTrace"
+        />
+      </el-descriptions-item>
+      <el-descriptions-item label="处理状态">
+        <dict-tag
+          :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
+          :value="detailData.processStatus"
+        />
+      </el-descriptions-item>
+      <el-descriptions-item label="处理人" v-if="detailData.processUserId">
+        {{ detailData.processUserId }}
+      </el-descriptions-item>
+      <el-descriptions-item label="处理时间" v-if="detailData.processTime">
+        {{ formatDate(detailData.processTime, 'YYYY-MM-DD HH:mm:ss') }}
+      </el-descriptions-item>
+    </el-descriptions>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE } from '@/utils/dict'
+import { formatDate } from '@/utils/formatTime'
+import * as ApiErrorLog from '@/api/infra/apiErrorLog'
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const detailLoading = ref(false) // 表单的加载中
+const detailData = ref() // 详情数据
+
+/** 打开弹窗 */
+const open = async (data: ApiErrorLog.ApiErrorLogVO) => {
+  modelVisible.value = true
+  // 设置数据
+  detailLoading.value = true
+  try {
+    detailData.value = data
+  } finally {
+    detailLoading.value = false
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 0 - 76
src/views/infra/apiErrorLog/apiErrorLog.data.ts

@@ -1,76 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '日志编号',
-  action: true,
-  actionWidth: '300',
-  columns: [
-    {
-      title: '链路追踪',
-      field: 'traceId',
-      isTable: false
-    },
-    {
-      title: '用户编号',
-      field: 'userId',
-      isSearch: true
-    },
-    {
-      title: '用户类型',
-      field: 'userType',
-      dictType: DICT_TYPE.USER_TYPE,
-      isSearch: true
-    },
-    {
-      title: '应用名',
-      field: 'applicationName',
-      isSearch: true
-    },
-    {
-      title: '请求方法名',
-      field: 'requestMethod'
-    },
-    {
-      title: '请求地址',
-      field: 'requestUrl',
-      isSearch: true
-    },
-    {
-      title: '异常发生时间',
-      field: 'exceptionTime',
-      formatter: 'formatDate',
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '异常名',
-      field: 'exceptionName'
-    },
-    {
-      title: '处理状态',
-      field: 'processStatus',
-      dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '处理人',
-      field: 'processUserId',
-      isTable: false
-    },
-    {
-      title: '处理时间',
-      field: 'processTime',
-      formatter: 'formatDate',
-      isTable: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 230 - 81
src/views/infra/apiErrorLog/index.vue

@@ -1,99 +1,248 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <!-- 操作:导出 -->
-      <template #toolbar_buttons>
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          @click="exportList('错误数据.xls')"
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="用户编号" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-      </template>
-      <template #duration_default="{ row }">
-        <span>{{ row.duration + 'ms' }}</span>
-      </template>
-      <template #resultCode_default="{ row }">
-        <span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['infra:api-access-log:query']"
-          @click="handleDetail(row)"
+      </el-form-item>
+      <el-form-item label="用户类型" prop="userType">
+        <el-select
+          v-model="queryParams.userType"
+          placeholder="请选择用户类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="应用名" prop="applicationName">
+        <el-input
+          v-model="queryParams.applicationName"
+          placeholder="请输入应用名"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-        <XTextButton
-          preIcon="ep:cpu"
-          title="已处理"
-          v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
-          v-hasPermi="['infra:api-error-log:update-status']"
-          @click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')"
+      </el-form-item>
+      <el-form-item label="异常时间" prop="exceptionTime">
+        <el-date-picker
+          v-model="queryParams.exceptionTime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
         />
-        <XTextButton
-          preIcon="ep:mute-notification"
-          title="已忽略"
-          v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
-          v-hasPermi="['infra:api-error-log:update-status']"
-          @click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')"
-        />
-      </template>
-    </XTable>
+      </el-form-item>
+      <el-form-item label="处理状态" prop="processStatus">
+        <el-select
+          v-model="queryParams.processStatus"
+          placeholder="请选择处理状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['infra:api-error-log:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="日志编号" align="center" prop="id" />
+      <el-table-column label="用户编号" align="center" prop="userId" />
+      <el-table-column label="用户类型" align="center" prop="userType">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="应用名" align="center" prop="applicationName" width="200" />
+      <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
+      <el-table-column label="请求地址" align="center" prop="requestUrl" width="180" />
+      <el-table-column
+        label="异常发生时间"
+        align="center"
+        prop="exceptionTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="异常名" align="center" prop="exceptionName" width="180" />
+      <el-table-column label="处理状态" align="center" prop="processStatus">
+        <template #default="scope">
+          <dict-tag
+            :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
+            :value="scope.row.processStatus"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="200">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openDetail(scope.row)"
+            v-hasPermi="['infra:api-access-log:query']"
+          >
+            详细
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
+            @click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)"
+            v-hasPermi="['infra:api-error-log:update-status']"
+          >
+            已处理
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
+            @click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)"
+            v-hasPermi="['infra:api-error-log:update-status']"
+          >
+            已忽略
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
   </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <XButton :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+
+  <!-- 表单弹窗:详情 -->
+  <ApiErrorLogDetail ref="detailRef" />
 </template>
+
 <script setup lang="ts" name="ApiErrorLog">
-import { allSchemas } from './apiErrorLog.data'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
 import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
+import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
 import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
 
-const { t } = useI18n() // 国际化
-const message = useMessage()
+const message = useMessage() // 消息弹窗
 
-// ========== 列表相关 ==========
-const [registerTable, { reload, exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: ApiErrorLogApi.getApiErrorLogPageApi,
-  exportListApi: ApiErrorLogApi.exportApiErrorLogApi
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: null,
+  userType: null,
+  applicationName: null,
+  requestUrl: null,
+  processStatus: null,
+  exceptionTime: []
 })
-// ========== 详情相关 ==========
-const detailData = ref() // 详情 Ref
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('') // 弹出层标题
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
 
-// 详情操作
-const handleDetail = (row: ApiErrorLogApi.ApiErrorLogVO) => {
-  // 设置数据
-  detailData.value = row
-  dialogTitle.value = t('action.detail')
-  dialogVisible.value = true
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 异常处理操作
-const handleProcessClick = (
-  row: ApiErrorLogApi.ApiErrorLogVO,
-  processSttatus: number,
-  type: string
-) => {
-  message
-    .confirm('确认标记为' + type + '?', t('common.reminder'))
-    .then(async () => {
-      await ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus)
-      message.success(t('common.updateSuccess'))
-    })
-    .finally(async () => {
-      // 刷新列表
-      await reload()
-    })
-    .catch(() => {})
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
+
+/** 详情操作 */
+const detailRef = ref()
+const openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => {
+  detailRef.value.open(data)
+}
+
+/** 处理已处理 / 已忽略的操作 **/
+const handleProcess = async (id: number, processStatus: number) => {
+  try {
+    // 操作的二次确认
+    const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'
+    await message.confirm('确认标记为' + type + '?')
+    // 执行操作
+    await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus)
+    await message.success(type)
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await ApiErrorLogApi.exportApiErrorLog(queryParams)
+    download.excel(data, '异常日志.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 2 - 2
src/views/infra/codegen/components/CloumInfoForm.vue

@@ -114,7 +114,7 @@
 import { PropType } from 'vue'
 import { DictTypeVO } from '@/api/system/dict/types'
 import { CodegenColumnVO } from '@/api/infra/codegen/types'
-import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type'
+import { listSimpleDictType } from '@/api/system/dict/dict.type'
 
 const props = defineProps({
   info: {
@@ -125,7 +125,7 @@ const props = defineProps({
 /** 查询字典下拉列表 */
 const dictOptions = ref<DictTypeVO[]>()
 const getDictOptions = async () => {
-  const res = await listSimpleDictTypeApi()
+  const res = await listSimpleDictType()
   dictOptions.value = res
 }
 onMounted(async () => {

+ 21 - 6
src/views/infra/config/index.vue

@@ -1,13 +1,20 @@
 <template>
+  <!-- 搜索 -->
   <content-wrap>
-    <!-- 搜索工作栏 -->
-    <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="参数名称" prop="name">
         <el-input
           v-model="queryParams.name"
           placeholder="请输入参数名称"
           clearable
           @keyup.enter="handleQuery"
+          class="!w-240px"
         />
       </el-form-item>
       <el-form-item label="参数键名" prop="key">
@@ -16,10 +23,16 @@
           placeholder="请输入参数键名"
           clearable
           @keyup.enter="handleQuery"
+          class="!w-240px"
         />
       </el-form-item>
       <el-form-item label="系统内置" prop="type">
-        <el-select v-model="queryParams.type" placeholder="请选择系统内置" clearable>
+        <el-select
+          v-model="queryParams.type"
+          placeholder="请选择系统内置"
+          clearable
+          class="!w-240px"
+        >
           <el-option
             v-for="dict in getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)"
             :key="parseInt(dict.value)"
@@ -33,10 +46,10 @@
           v-model="queryParams.createTime"
           value-format="YYYY-MM-DD HH:mm:ss"
           type="daterange"
-          range-separator="-"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
         />
       </el-form-item>
       <el-form-item>
@@ -56,9 +69,11 @@
         </el-button>
       </el-form-item>
     </el-form>
+  </content-wrap>
 
-    <!-- 列表 -->
-    <el-table v-loading="loading" :data="list" align="center">
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
       <el-table-column label="参数主键" align="center" prop="id" />
       <el-table-column label="参数分类" align="center" prop="category" />
       <el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />

+ 0 - 52
src/views/infra/dataSourceConfig/dataSourceConfig.data.ts

@@ -1,52 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-// 国际化
-const { t } = useI18n()
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  url: [required],
-  username: [required],
-  password: [required]
-})
-// 新增 + 修改
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'seq',
-  action: true,
-  columns: [
-    {
-      title: '数据源名称',
-      field: 'name'
-    },
-    {
-      title: '数据源连接',
-      field: 'url',
-      form: {
-        component: 'Input',
-        componentProps: {
-          type: 'textarea',
-          rows: 4
-        },
-        colProps: {
-          span: 24
-        }
-      }
-    },
-    {
-      title: '用户名',
-      field: 'username'
-    },
-    {
-      title: '密码',
-      field: 'password',
-      isTable: false
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 111 - 0
src/views/infra/dataSourceConfig/form.vue

@@ -0,0 +1,111 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="数据源名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入参数名称" />
+      </el-form-item>
+      <el-form-item label="数据源连接" prop="url">
+        <el-input v-model="formData.url" placeholder="请输入数据源连接" />
+      </el-form-item>
+      <el-form-item label="用户名" prop="username">
+        <el-input v-model="formData.username" placeholder="请输入用户名" />
+      </el-form-item>
+      <el-form-item label="密码" prop="password">
+        <el-input v-model="formData.password" placeholder="请输入密码" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({
+  id: undefined,
+  name: '',
+  url: '',
+  username: '',
+  password: ''
+})
+const formRules = reactive({
+  name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }],
+  url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }],
+  username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
+  password: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DataSourceConfigApi.getDataSourceConfig(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as DataSourceConfigApi.DataSourceConfigVO
+    if (formType.value === 'create') {
+      await DataSourceConfigApi.createDataSourceConfig(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DataSourceConfigApi.updateDataSourceConfig(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    url: '',
+    username: '',
+    password: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 86 - 129
src/views/infra/dataSourceConfig/index.vue

@@ -1,145 +1,102 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <XButton
+  <content-wrap>
+    <!-- 搜索工作栏 -->
+    <el-form class="-mb-15px" :inline="true">
+      <el-form-item>
+        <el-button
           type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
+          @click="openModal('create')"
           v-hasPermi="['infra:data-source-config:create']"
-          @click="handleCreate()"
-        />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['infra:data-source-config:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['infra:data-source-config:query']"
-          @click="handleDetail(row.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['infra:data-source-config:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    />
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="loading"
-        @click="submitForm()"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="主键编号" align="center" prop="id" />
+      <el-table-column label="数据源名称" align="center" prop="name" />
+      <el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" />
+      <el-table-column label="用户名" align="center" prop="username" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['infra:data-source-config:update']"
+            :disabled="scope.row.id === 0"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['infra:data-source-config:delete']"
+            :disabled="scope.row.id === 0"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <data-source-config-form ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="DataSourceConfig">
-import type { FormExpose } from '@/components/Form'
-// 业务相关的 import
-import * as DataSourceConfiggApi from '@/api/infra/dataSourceConfig'
-import { rules, allSchemas } from './dataSourceConfig.data'
-
-const { t } = useI18n() // 国际化
+import { dateFormatter } from '@/utils/formatTime'
+import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
+import DataSourceConfigForm from './form.vue'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  isList: true,
-  getListApi: DataSourceConfiggApi.getDataSourceConfigListApi,
-  deleteApi: DataSourceConfiggApi.deleteDataSourceConfigApi
-})
-// ========== CRUD 相关 ==========
-const loading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
+const { t } = useI18n() // 国际化
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
-}
+const loading = ref(true) // 列表的加载中
+const list = ref([]) // 列表的数据
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    list.value = await DataSourceConfigApi.getDataSourceConfigList()
+  } finally {
+    loading.value = false
+  }
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  // 设置数据
-  const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId)
-  detailData.value = res
-  setDialogTile('detail')
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await DataSourceConfigApi.deleteDataSourceConfig(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      loading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as DataSourceConfiggApi.DataSourceConfigVO
-        if (actionType.value === 'create') {
-          await DataSourceConfiggApi.createDataSourceConfigApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await DataSourceConfiggApi.updateDataSourceConfigApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        loading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
-}
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 83 - 0
src/views/infra/file/form.vue

@@ -0,0 +1,83 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-upload
+      ref="uploadRef"
+      :limit="1"
+      accept=".jpg, .png, .gif"
+      :auto-upload="false"
+      drag
+      :headers="headers"
+      :action="url"
+      :data="data"
+      :disabled="formLoading"
+      :on-change="handleFileChange"
+      :on-progress="handleFileUploadProgress"
+      :on-success="handleFileSuccess"
+    >
+      <i class="el-icon-upload"></i>
+      <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div>
+      <template #tip>
+        <div class="el-upload__tip" style="color: red">
+          提示:仅允许导入 jpg、png、gif 格式文件!
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitFileForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { Dialog } from '@/components/Dialog'
+import { getAccessToken } from '@/utils/auth'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const url = import.meta.env.VITE_UPLOAD_URL
+const headers = { Authorization: 'Bearer ' + getAccessToken() }
+const data = ref({ path: '' })
+const uploadRef = ref()
+
+/** 打开弹窗 */
+const openModal = async () => {
+  modelVisible.value = true
+  modelTitle.value = t('action.fileUpload')
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+/** 处理上传的文件发生变化 */
+const handleFileChange = (file) => {
+  data.value.path = file.name
+}
+
+/** 处理文件上传中 */
+const handleFileUploadProgress = () => {
+  formLoading.value = true // 禁止修改
+}
+
+/** 发起文件上传 */
+const submitFileForm = () => {
+  unref(uploadRef)?.submit()
+}
+
+/** 文件上传成功处理 */
+const handleFileSuccess = () => {
+  // 清理
+  modelVisible.value = false
+  formLoading.value = false
+  unref(uploadRef)?.clearFiles()
+  // 提示成功,并刷新
+  message.success(t('common.createSuccess'))
+  emit('success')
+}
+</script>

+ 154 - 0
src/views/infra/file/index.vue

@@ -0,0 +1,154 @@
+<template>
+  <!-- 搜索 -->
+  <content-wrap>
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="文件路径" prop="path">
+        <el-input
+          v-model="queryParams.path"
+          placeholder="请输入文件路径"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="文件类型" prop="type" width="80">
+        <el-input
+          v-model="queryParams.type"
+          placeholder="请输入文件类型"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button type="primary" @click="openModal">
+          <Icon icon="ep:upload" class="mr-5px" /> 上传文件
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" />
+      <el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" />
+      <el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" />
+      <el-table-column
+        label="文件大小"
+        align="center"
+        prop="size"
+        width="120"
+        :formatter="fileSizeFormatter"
+      />
+      <el-table-column label="文件类型" align="center" prop="type" width="180px" />
+      <el-table-column
+        label="上传时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['infra:config:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <file-upload-form ref="modalRef" @success="getList" />
+</template>
+<script setup lang="ts" name="Config">
+import { fileSizeFormatter } from '@/utils'
+import { dateFormatter } from '@/utils/formatTime'
+import * as FileApi from '@/api/infra/file'
+import FileUploadForm from './form.vue'
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  type: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await FileApi.getFilePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = () => {
+  modalRef.value.openModal()
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await FileApi.deleteFile(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 0 - 77
src/views/infra/fileConfig/fileConfig.data.ts

@@ -1,77 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  storage: [required],
-  config: {
-    basePath: [required],
-    host: [required],
-    port: [required],
-    username: [required],
-    password: [required],
-    mode: [required],
-    endpoint: [required],
-    bucket: [required],
-    accessKey: [required],
-    accessSecret: [required],
-    domain: [required]
-  }
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '配置编号',
-  action: true,
-  actionWidth: '400px',
-  columns: [
-    {
-      title: '配置名',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '存储器',
-      field: 'storage',
-      dictType: DICT_TYPE.INFRA_FILE_STORAGE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '主配置',
-      field: 'master',
-      dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
-      dictClass: 'boolean'
-    },
-    {
-      title: t('form.remark'),
-      field: 'remark',
-      form: {
-        component: 'Input',
-        componentProps: {
-          type: 'textarea',
-          rows: 4
-        },
-        colProps: {
-          span: 24
-        }
-      }
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 195 - 0
src/views/infra/fileConfig/form.vue

@@ -0,0 +1,195 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="配置名" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入配置名" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" placeholder="请输入备注" />
+      </el-form-item>
+      <el-form-item label="存储器" prop="storage">
+        <el-select
+          v-model="formData.storage"
+          placeholder="请选择存储器"
+          :disabled="formData.id !== undefined"
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <!-- DB -->
+      <!-- Local / FTP / SFTP -->
+      <el-form-item
+        v-if="formData.storage >= 10 && formData.storage <= 12"
+        label="基础路径"
+        prop="config.basePath"
+      >
+        <el-input v-model="formData.config.basePath" placeholder="请输入基础路径" />
+      </el-form-item>
+      <el-form-item
+        v-if="formData.storage >= 11 && formData.storage <= 12"
+        label="主机地址"
+        prop="config.host"
+      >
+        <el-input v-model="formData.config.host" placeholder="请输入主机地址" />
+      </el-form-item>
+      <el-form-item
+        v-if="formData.storage >= 11 && formData.storage <= 12"
+        label="主机端口"
+        prop="config.port"
+      >
+        <el-input-number :min="0" v-model="formData.config.port" placeholder="请输入主机端口" />
+      </el-form-item>
+      <el-form-item
+        v-if="formData.storage >= 11 && formData.storage <= 12"
+        label="用户名"
+        prop="config.username"
+      >
+        <el-input v-model="formData.config.username" placeholder="请输入密码" />
+      </el-form-item>
+      <el-form-item
+        v-if="formData.storage >= 11 && formData.storage <= 12"
+        label="密码"
+        prop="config.password"
+      >
+        <el-input v-model="formData.config.password" placeholder="请输入密码" />
+      </el-form-item>
+      <el-form-item v-if="formData.storage === 11" label="连接模式" prop="config.mode">
+        <el-radio-group v-model="formData.config.mode">
+          <el-radio key="Active" label="Active">主动模式</el-radio>
+          <el-radio key="Passive" label="Passive">主动模式</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <!-- S3 -->
+      <el-form-item v-if="formData.storage === 20" label="节点地址" prop="config.endpoint">
+        <el-input v-model="formData.config.endpoint" placeholder="请输入节点地址" />
+      </el-form-item>
+      <el-form-item v-if="formData.storage === 20" label="存储 bucket" prop="config.bucket">
+        <el-input v-model="formData.config.bucket" placeholder="请输入 bucket" />
+      </el-form-item>
+      <el-form-item v-if="formData.storage === 20" label="accessKey" prop="config.accessKey">
+        <el-input v-model="formData.config.accessKey" placeholder="请输入 accessKey" />
+      </el-form-item>
+      <el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret">
+        <el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" />
+      </el-form-item>
+      <!-- 通用 -->
+      <el-form-item v-if="formData.storage === 20" label="自定义域名">
+        <!-- 无需参数校验,所以去掉 prop -->
+        <el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
+      </el-form-item>
+      <el-form-item v-else-if="formData.storage" label="自定义域名" prop="config.domain">
+        <el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import * as FileConfigApi from '@/api/infra/fileConfig'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  storage: '',
+  remark: '',
+  config: {}
+})
+const formRules = reactive({
+  name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }],
+  storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }],
+  config: {
+    basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }],
+    host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }],
+    port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }],
+    username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
+    password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
+    mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }],
+    endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }],
+    bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }],
+    accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],
+    accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],
+    domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
+  }
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await FileConfigApi.getFileConfig(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as FileConfigApi.FileConfigVO
+    if (formType.value === 'create') {
+      await FileConfigApi.createFileConfig(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await FileConfigApi.updateFileConfig(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    storage: '',
+    remark: '',
+    config: {}
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 166 - 262
src/views/infra/fileConfig/index.vue

@@ -1,294 +1,198 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['infra:file-config:create']"
-          @click="handleCreate(formRef)"
-        />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:编辑 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['infra:file-config:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['infra:file-config:query']"
-          @click="handleDetail(row.id)"
-        />
-        <!-- 操作:主配置 -->
-        <XTextButton
-          preIcon="ep:flag"
-          title="主配置"
-          v-hasPermi="['infra:file-config:update']"
-          @click="handleMaster(row)"
-        />
-        <!-- 操作:测试 -->
-        <XTextButton preIcon="ep:share" :title="t('action.test')" @click="handleTest(row.id)" />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['infra:file-config:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <el-form
-      ref="formRef"
-      v-if="['create', 'update'].includes(actionType)"
-      :model="form"
-      :rules="rules"
-      label-width="120px"
-    >
+  <!-- 搜索 -->
+  <content-wrap>
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
       <el-form-item label="配置名" prop="name">
-        <el-input v-model="form.name" placeholder="请输入配置名" />
-      </el-form-item>
-      <el-form-item label="备注" prop="remark">
-        <el-input v-model="form.remark" placeholder="请输入备注" />
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入配置名"
+          clearable
+          @keyup.enter="handleQuery"
+        />
       </el-form-item>
       <el-form-item label="存储器" prop="storage">
-        <el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== 0">
+        <el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
           <el-option
-            v-for="(dict, index) in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
-            :key="index"
+            v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
+            :key="parseInt(dict.value)"
             :label="dict.label"
-            :value="dict.value"
+            :value="parseInt(dict.value)"
           />
         </el-select>
       </el-form-item>
-      <!-- DB -->
-      <!-- Local / FTP / SFTP -->
-      <el-form-item
-        v-if="form.storage >= 10 && form.storage <= 12"
-        label="基础路径"
-        prop="config.basePath"
-      >
-        <el-input v-model="form.config.basePath" placeholder="请输入基础路径" />
-      </el-form-item>
-      <el-form-item
-        v-if="form.storage >= 11 && form.storage <= 12"
-        label="主机地址"
-        prop="config.host"
-      >
-        <el-input v-model="form.config.host" placeholder="请输入主机地址" />
-      </el-form-item>
-      <el-form-item
-        v-if="form.storage >= 11 && form.storage <= 12"
-        label="主机端口"
-        prop="config.port"
-      >
-        <el-input-number :min="0" v-model="form.config.port" placeholder="请输入主机端口" />
-      </el-form-item>
-      <el-form-item
-        v-if="form.storage >= 11 && form.storage <= 12"
-        label="用户名"
-        prop="config.username"
-      >
-        <el-input v-model="form.config.username" placeholder="请输入密码" />
-      </el-form-item>
-      <el-form-item
-        v-if="form.storage >= 11 && form.storage <= 12"
-        label="密码"
-        prop="config.password"
-      >
-        <el-input v-model="form.config.password" placeholder="请输入密码" />
-      </el-form-item>
-      <el-form-item v-if="form.storage === 11" label="连接模式" prop="config.mode">
-        <el-radio-group v-model="form.config.mode">
-          <el-radio key="Active" label="Active">主动模式</el-radio>
-          <el-radio key="Passive" label="Passive">主动模式</el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <!-- S3 -->
-      <el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint">
-        <el-input v-model="form.config.endpoint" placeholder="请输入节点地址" />
-      </el-form-item>
-      <el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket">
-        <el-input v-model="form.config.bucket" placeholder="请输入 bucket" />
-      </el-form-item>
-      <el-form-item v-if="form.storage === 20" label="accessKey" prop="config.accessKey">
-        <el-input v-model="form.config.accessKey" placeholder="请输入 accessKey" />
-      </el-form-item>
-      <el-form-item v-if="form.storage === 20" label="accessSecret" prop="config.accessSecret">
-        <el-input v-model="form.config.accessSecret" placeholder="请输入 accessSecret" />
-      </el-form-item>
-      <!-- 通用 -->
-      <el-form-item v-if="form.storage === 20" label="自定义域名">
-        <!-- 无需参数校验,所以去掉 prop -->
-        <el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+        />
       </el-form-item>
-      <el-form-item v-else-if="form.storage" label="自定义域名" prop="config.domain">
-        <el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          @click="openModal('create')"
+          v-hasPermi="['infra:file-config:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
       </el-form-item>
     </el-form>
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm(formRef)"
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="配置名" align="center" prop="name" />
+      <el-table-column label="存储器" align="center" prop="storage">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="主配置" align="center" prop="primary">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center" width="240px">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['infra:file-config:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            :disabled="scope.row.master"
+            @click="handleMaster(scope.row.id)"
+            v-hasPermi="['infra:file-config:update']"
+          >
+            主配置
+          </el-button>
+          <el-button link type="primary" @click="handleTest(scope.row.id)"> 测试 </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['infra:config:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <file-config-form ref="modalRef" @success="getList" />
 </template>
-<script setup lang="ts" name="FileConfig">
-import type { FormInstance } from 'element-plus'
-// 业务相关的 import
+<script setup lang="ts" name="Config">
 import * as FileConfigApi from '@/api/infra/fileConfig'
-import { rules, allSchemas } from './fileConfig.data'
-import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-
-const { t } = useI18n() // 国际化
+import FileConfigForm from './form.vue'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: FileConfigApi.getFileConfigPageApi,
-  deleteApi: FileConfigApi.deleteFileConfigApi
-})
+const { t } = useI18n() // 国际化
 
-// ========== CRUD 相关 ==========
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormInstance>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-const form = ref<FileConfigApi.FileConfigVO>({
-  id: 0,
-  name: '',
-  storage: 0,
-  master: false,
-  visible: false,
-  config: {
-    basePath: '',
-    host: '',
-    port: 0,
-    username: '',
-    password: '',
-    mode: '',
-    endpoint: '',
-    bucket: '',
-    accessKey: '',
-    accessSecret: '',
-    domain: ''
-  },
-  remark: '',
-  createTime: new Date()
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  storage: undefined,
+  createTime: []
 })
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
-}
+const queryFormRef = ref() // 搜索的表单
 
-// 新增操作
-const handleCreate = (formEl: FormInstance | undefined) => {
-  setDialogTile('create')
-  formEl?.resetFields()
-  form.value = {
-    id: 0,
-    name: '',
-    storage: 0,
-    master: false,
-    visible: false,
-    config: {
-      basePath: '',
-      host: '',
-      port: 0,
-      username: '',
-      password: '',
-      mode: '',
-      endpoint: '',
-      bucket: '',
-      accessKey: '',
-      accessSecret: '',
-      domain: ''
-    },
-    remark: '',
-    createTime: new Date()
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await FileConfigApi.getFileConfigPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  // 设置数据
-  const res = await FileConfigApi.getFileConfigApi(rowId)
-  form.value = res
-  setDialogTile('update')
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  // 设置数据
-  const res = await FileConfigApi.getFileConfigApi(rowId)
-  detailData.value = res
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 主配置操作
-const handleMaster = (row: FileConfigApi.FileConfigVO) => {
-  message
-    .confirm('是否确认修改配置【 ' + row.name + ' 】为主配置?', t('common.reminder'))
-    .then(async () => {
-      await FileConfigApi.updateFileConfigMasterApi(row.id)
-      await reload()
-    })
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-const handleTest = async (rowId: number) => {
-  const res = await FileConfigApi.testFileConfigApi(rowId)
-  message.alert('测试通过,上传文件成功!访问地址:' + res)
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await FileConfigApi.deleteFileConfig(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// 提交按钮
-const submitForm = async (formEl: FormInstance | undefined) => {
-  if (!formEl) return
-  formEl.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        if (actionType.value === 'create') {
-          await FileConfigApi.createFileConfigApi(form.value)
-          message.success(t('common.createSuccess'))
-        } else {
-          await FileConfigApi.updateFileConfigApi(form.value)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        await reload()
-      }
-    }
-  })
+/** 主配置按钮操作 */
+const handleMaster = async (id) => {
+  try {
+    await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?')
+    await FileConfigApi.updateFileConfigMaster(id)
+    message.success(t('common.updateSuccess'))
+    await getList()
+  } catch {}
 }
+
+/** 测试按钮操作 */
+const handleTest = async (id) => {
+  try {
+    const response = await FileConfigApi.testFileConfig(id)
+    message.alert('测试通过,上传文件成功!访问地址:' + response)
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 0 - 52
src/views/infra/fileList/fileList.data.ts

@@ -1,52 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'seq',
-  action: true,
-  columns: [
-    {
-      title: '文件名',
-      field: 'name'
-    },
-    {
-      title: '文件路径',
-      field: 'path',
-      isSearch: true
-    },
-    {
-      title: 'URL',
-      field: 'url',
-      table: {
-        cellRender: {
-          name: 'XPreview'
-        }
-      }
-    },
-    {
-      title: '文件大小',
-      field: 'size',
-      formatter: 'formatSize'
-    },
-    {
-      title: '文件类型',
-      field: 'type',
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 0 - 173
src/views/infra/fileList/index.vue

@@ -1,173 +0,0 @@
-<template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <XButton
-          type="primary"
-          preIcon="ep:upload"
-          title="上传文件"
-          @click="uploadDialogVisible = true"
-        />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <XTextButton
-          preIcon="ep:copy-document"
-          :title="t('common.copy')"
-          @click="handleCopy(row.url)"
-        />
-        <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['infra:file:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData">
-      <template #url="{ row }">
-        <el-image
-          v-if="row.type === 'jpg' || 'png' || 'gif'"
-          style="width: 100px; height: 100px"
-          :src="row.url"
-          :key="row.url"
-          lazy
-        />
-        <span>{{ row.url }}</span>
-      </template>
-    </Descriptions>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <XButton :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
-  <XModal v-model="uploadDialogVisible" :title="uploadDialogTitle">
-    <el-upload
-      ref="uploadRef"
-      :action="updateUrl + '?updateSupport=' + updateSupport"
-      :headers="uploadHeaders"
-      :drag="true"
-      :limit="1"
-      :multiple="true"
-      :show-file-list="true"
-      :disabled="uploadDisabled"
-      :before-upload="beforeUpload"
-      :on-exceed="handleExceed"
-      :on-success="handleFileSuccess"
-      :on-error="excelUploadError"
-      :auto-upload="false"
-      accept=".jpg, .png, .gif"
-    >
-      <Icon icon="ep:upload-filled" />
-      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-      <template #tip>
-        <div class="el-upload__tip">请上传 .jpg, .png, .gif 标准格式文件</div>
-      </template>
-    </el-upload>
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        type="primary"
-        preIcon="ep:upload-filled"
-        :title="t('action.save')"
-        @click="submitFileForm()"
-      />
-      <!-- 按钮:关闭 -->
-      <XButton :title="t('dialog.close')" @click="uploadDialogVisible = false" />
-    </template>
-  </XModal>
-</template>
-<script setup lang="ts" name="FileList">
-import type { UploadInstance, UploadRawFile } from 'element-plus'
-// 业务相关的 import
-import { allSchemas } from './fileList.data'
-import * as FileApi from '@/api/infra/fileList'
-import { getAccessToken, getTenantId } from '@/utils/auth'
-import { useClipboard } from '@vueuse/core'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: FileApi.getFilePageApi,
-  deleteApi: FileApi.deleteFileApi
-})
-
-const detailData = ref() // 详情 Ref
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('') // 弹出层标题
-const uploadDialogVisible = ref(false)
-const uploadDialogTitle = ref('上传')
-const updateSupport = ref(0)
-const uploadDisabled = ref(false)
-const uploadRef = ref<UploadInstance>()
-let updateUrl = import.meta.env.VITE_UPLOAD_URL
-const uploadHeaders = ref()
-// 文件上传之前判断
-const beforeUpload = (file: UploadRawFile) => {
-  const isImg = file.type === 'image/jpeg' || 'image/gif' || 'image/png'
-  const isLt5M = file.size / 1024 / 1024 < 5
-  if (!isImg) message.error('上传文件只能是 jpeg / gif / png 格式!')
-  if (!isLt5M) message.error('上传文件大小不能超过 5MB!')
-  return isImg && isLt5M
-}
-// 处理上传的文件发生变化
-// const handleFileChange = (uploadFile: UploadFile): void => {
-//   uploadRef.value.data.path = uploadFile.name
-// }
-// 文件上传
-const submitFileForm = () => {
-  uploadHeaders.value = {
-    Authorization: 'Bearer ' + getAccessToken(),
-    'tenant-id': getTenantId()
-  }
-  uploadDisabled.value = true
-  uploadRef.value!.submit()
-}
-// 文件上传成功
-const handleFileSuccess = async (response: any): Promise<void> => {
-  if (response.code !== 0) {
-    message.error(response.msg)
-    return
-  }
-  message.success('上传成功')
-  uploadDialogVisible.value = false
-  uploadDisabled.value = false
-  await reload()
-}
-// 文件数超出提示
-const handleExceed = (): void => {
-  message.error('最多只能上传一个文件!')
-}
-// 上传错误提示
-const excelUploadError = (): void => {
-  message.error('导入数据失败,请您重新上传!')
-}
-
-// 详情操作
-const handleDetail = (row: FileApi.FileVO) => {
-  // 设置数据
-  detailData.value = row
-  dialogTitle.value = t('action.detail')
-  dialogVisible.value = true
-}
-
-// ========== 复制相关 ==========
-const handleCopy = async (text: string) => {
-  const { copy, copied, isSupported } = useClipboard({ source: text })
-  if (!isSupported) {
-    message.error(t('common.copyError'))
-  } else {
-    await copy()
-    if (unref(copied)) {
-      message.success(t('common.copySuccess'))
-    }
-  }
-}
-</script>

+ 71 - 0
src/views/system/area/form.vue

@@ -0,0 +1,71 @@
+<template>
+  <Dialog title="IP 查询" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="IP" prop="ip">
+        <el-input v-model="formData.ip" placeholder="请输入 IP 地址" />
+      </el-form-item>
+      <el-form-item label="地址" prop="result">
+        <el-input v-model="formData.result" readonly placeholder="展示查询 IP 结果" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as AreaApi from '@/api/system/area'
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
+const formData = ref({
+  ip: '',
+  result: undefined
+})
+const formRules = reactive({
+  ip: [{ required: true, message: 'IP 地址不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async () => {
+  modelVisible.value = true
+  resetForm()
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    formData.value.result = await AreaApi.getAreaByIp(formData.value.ip!.trim())
+    message.success('查询成功')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    ip: '',
+    result: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 71 - 0
src/views/system/area/index.vue

@@ -0,0 +1,71 @@
+<template>
+  <!-- 操作栏 -->
+  <content-wrap>
+    <el-button type="primary" @click="openModal()">
+      <Icon icon="ep:plus" class="mr-5px" /> IP 查询
+    </el-button>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <div style="width: 100%; height: 700px">
+      <!-- AutoResizer 自动调节大小 -->
+      <el-auto-resizer>
+        <template #default="{ height, width }">
+          <!-- Virtualized Table 虚拟化表格:高性能,解决表格在大数据量下的卡顿问题 -->
+          <el-table-v2
+            :columns="columns"
+            :data="list"
+            :width="width"
+            :height="height"
+            expand-column-key="id"
+          />
+        </template>
+      </el-auto-resizer>
+    </div>
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <area-form ref="modalRef" />
+</template>
+<script setup lang="tsx" name="Area">
+import type { Column } from 'element-plus'
+import AreaForm from './form.vue'
+import * as AreaApi from '@/api/system/area'
+
+// 表格的 column 字段
+const columns: Column[] = [
+  {
+    dataKey: 'id', // 需要渲染当前列的数据字段。例如说:{id:9527, name:'Mike'},则填 id
+    title: '编号', // 显示在单元格表头的文本
+    width: 400, // 当前列的宽度,必须设置
+    fixed: true, // 是否固定列
+    key: 'id' // 树形展开对应的 key
+  },
+  {
+    dataKey: 'name',
+    title: '地名',
+    width: 200
+  }
+]
+// 表格的数据
+const list = ref([])
+
+/**
+ * 获得数据列表
+ */
+const getList = async () => {
+  list.value = await AreaApi.getAreaTree()
+}
+
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = () => {
+  modalRef.value.openModal()
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 0 - 84
src/views/system/dept/dept.data.ts

@@ -1,84 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  sort: [required],
-  // email: [required],
-  email: [
-    { required: true, message: t('profile.rules.mail'), trigger: 'blur' },
-    {
-      type: 'email',
-      message: t('profile.rules.truemail'),
-      trigger: ['blur', 'change']
-    }
-  ],
-  phone: [
-    {
-      len: 11,
-      trigger: 'blur',
-      message: '请输入正确的手机号码'
-    }
-  ]
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  action: true,
-  columns: [
-    {
-      title: '上级部门',
-      field: 'parentId',
-      isTable: false
-    },
-    {
-      title: '部门名称',
-      field: 'name',
-      isSearch: true,
-      table: {
-        treeNode: true,
-        align: 'left'
-      }
-    },
-    {
-      title: '负责人',
-      field: 'leaderUserId',
-      table: {
-        slots: {
-          default: 'leaderUserId_default'
-        }
-      }
-    },
-    {
-      title: '联系电话',
-      field: 'phone'
-    },
-    {
-      title: '邮箱',
-      field: 'email',
-      isTable: false
-    },
-    {
-      title: '显示排序',
-      field: 'sort'
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 190 - 0
src/views/system/dept/form.vue

@@ -0,0 +1,190 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible" width="800">
+    <el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
+      <el-row>
+        <el-col :span="24" v-if="formData.parentId !== 0">
+          <el-form-item label="上级部门" prop="parentId">
+            <el-tree-select
+              v-model="formData.parentId"
+              :data="deptOptions"
+              :props="{ value: 'id', label: 'name', children: 'children' }"
+              value-key="deptId"
+              placeholder="选择上级部门"
+              check-strictly
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="部门名称" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入部门名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="显示排序" prop="sort">
+            <el-input-number v-model="formData.sort" controls-position="right" :min="0" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="负责人" prop="leaderUserId">
+            <el-select
+              v-model="formData.leaderUserId"
+              placeholder="请输入负责人"
+              clearable
+              style="width: 100%"
+            >
+              <el-option
+                v-for="item in userList"
+                :key="item.id"
+                :label="item.nickname"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="联系电话" prop="phone">
+            <el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="邮箱" prop="email">
+            <el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="状态" prop="status">
+            <el-select v-model="formData.status" placeholder="请选择状态" clearable>
+              <el-option
+                v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="parseInt(dict.value)"
+                :label="dict.label"
+                :value="parseInt(dict.value)"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import * as DeptApi from '@/api/system/dept'
+import { UserVO } from '@/api/system/user'
+import { handleTree } from '@/utils/tree'
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formRef = ref() // 表单 Ref
+const deptOptions = ref() // 树形结构
+const userList = ref() // 负责人列表选项结构
+
+const formData = ref({
+  id: undefined,
+  title: '',
+  parentId: undefined,
+  name: undefined,
+  sort: undefined,
+  leaderUserId: undefined,
+  phone: undefined,
+  email: undefined,
+  status: undefined
+})
+
+const formRules = reactive({
+  parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
+  order: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
+  email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
+  phone: [
+    { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+  ]
+})
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number, userOption?: UserVO[]) => {
+  userList.value = userOption
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DeptApi.getDeptApi(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as DeptApi.DeptVO
+    if (formType.value === 'create') {
+      await DeptApi.createDeptApi(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DeptApi.updateDeptApi(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    title: '',
+    parentId: undefined,
+    name: undefined,
+    sort: undefined,
+    leaderUserId: undefined,
+    phone: undefined,
+    email: undefined,
+    status: undefined
+  }
+  formRef.value?.resetFields()
+}
+
+// 获取下拉框[上级]的数据
+const getTree = async () => {
+  deptOptions.value = []
+  const res = await DeptApi.listSimpleDeptApi()
+  let dept: Tree = { id: 0, name: '顶级部门', children: [] }
+  dept.children = handleTree(res)
+  deptOptions.value.push(dept)
+}
+
+// ========== 初始化 ==========
+onMounted(async () => {
+  await getTree()
+})
+</script>

+ 149 - 146
src/views/system/dept/index.vue

@@ -1,172 +1,175 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable ref="xGrid" @register="registerTable" show-overflow>
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:dept:create']"
-          @click="handleCreate()"
-        />
-        <XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" />
-        <XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" />
-      </template>
-      <template #leaderUserId_default="{ row }">
-        <span>{{ userNicknameFormat(row) }}</span>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:dept:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['system:dept:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <!-- 添加或修改菜单对话框 -->
-  <XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules">
-      <template #parentId="form">
-        <el-tree-select
-          node-key="id"
-          v-model="form['parentId']"
-          :props="defaultProps"
-          :data="deptOptions"
-          :default-expanded-keys="[100]"
-          check-strictly
-        />
-      </template>
-      <template #leaderUserId="form">
-        <el-select v-model="form['leaderUserId']">
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+      <el-form-item label="部门名称" prop="title">
+        <el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable />
+      </el-form-item>
+      <el-form-item label="部门状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择" clearable>
           <el-option
-            v-for="item in userOption"
-            :key="item.id"
-            :label="item.nickname"
-            :value="item.id"
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
           />
         </el-select>
-      </template>
-    </Form>
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :loading="actionLoading"
-        @click="submitForm()"
-        :title="t('action.save')"
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          @click="openModal('create')"
+          v-hasPermi="['system:dept:create']"
+          ><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
+        >
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" plain @click="toggleExpandAll"
+          ><Icon icon="ep:sort" class="mr-5px" /> 展开/折叠</el-button
+        >
+      </el-col>
+    </el-row>
+    <!-- 列表 -->
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="deptDatas"
+      row-key="id"
+      :default-expand-all="isExpandAll"
+      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+    >
+      <el-table-column prop="name" label="部门名称" width="260" />
+      <el-table-column prop="leader" label="负责人" :formatter="userNicknameFormat" width="120" />
+      <el-table-column prop="sort" label="排序" width="200" />
+      <el-table-column prop="status" label="状态" width="100">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            icon="el-icon-edit"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:dept:update']"
+            >修改</el-button
+          >
+          <el-button
+            v-if="scope.row.parentId !== 0"
+            link
+            type="danger"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:dept:delete']"
+            >删除</el-button
+          >
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 添加或修改部门对话框 -->
+  <dept-form ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="Dept">
-import { handleTree, defaultProps } from '@/utils/tree'
-import type { FormExpose } from '@/components/Form'
-import { allSchemas, rules } from './dept.data'
+import { handleTree } from '@/utils/tree'
 import * as DeptApi from '@/api/system/dept'
-import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
-
-const { t } = useI18n() // 国际化
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import DeptForm from './form.vue'
+import { dateFormatter } from '@/utils/formatTime'
+import { getSimpleUserList, UserVO } from '@/api/system/user'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const xGrid = ref<any>() // 列表 Grid Ref
-const treeConfig = {
-  transform: true,
-  rowField: 'id',
-  parentField: 'parentId',
-  expandAll: true
-}
+const { t } = useI18n() // 国际化
+// 搜索变量
+const queryParams = reactive({
+  title: '',
+  name: undefined,
+  status: undefined,
+  pageNo: 1,
+  pageSize: 100
+})
 
-// 弹窗相关的变量
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 遮罩层
-const formRef = ref<FormExpose>() // 表单 Ref
-const deptOptions = ref() // 树形结构
+const queryFormRef = ref() // 搜索的表单
+const deptDatas = ref() // 数据变量
 const userOption = ref<UserVO[]>([])
 
+const isExpandAll = ref(true) // 是否展开,默认全部展开
+const refreshTable = ref(true) // 重新渲染表格状态
+const loading = ref(true) // 列表的加载中
+
+//获取用户列表
 const getUserList = async () => {
-  const res = await getListSimpleUsersApi()
+  const res = await getSimpleUserList()
   userOption.value = res
 }
-// 获取下拉框[上级]的数据
-const getTree = async () => {
-  deptOptions.value = []
-  const res = await DeptApi.listSimpleDeptApi()
-  let dept: Tree = { id: 0, name: '顶级部门', children: [] }
-  dept.children = handleTree(res)
-  deptOptions.value.push(dept)
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  console.log(isExpandAll.value)
+  nextTick(() => {
+    refreshTable.value = true
+  })
 }
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  treeConfig: treeConfig,
-  getListApi: DeptApi.getDeptPageApi,
-  deleteApi: DeptApi.deleteDeptApi
-})
-// ========== 新增/修改 ==========
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
 }
 
-// 新增操作
-const handleCreate = async () => {
-  setDialogTile('create')
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await DeptApi.deleteDeptApi(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await DeptApi.getDeptApi(rowId)
-  await nextTick()
-  unref(formRef)?.setValues(res)
+/** 查询部门列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const res = await DeptApi.getDeptPageApi(queryParams)
+    deptDatas.value = handleTree(res)
+  } finally {
+    loading.value = false
+  }
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.pageNo = 1
+  queryParams.name = undefined
+  queryParams.status = undefined
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 提交新增/修改的表单
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as DeptApi.DeptVO
-        if (actionType.value === 'create') {
-          await DeptApi.createDeptApi(data)
-          message.success(t('common.createSuccess'))
-        } else if (actionType.value === 'update') {
-          await DeptApi.updateDeptApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        await getTree()
-        await reload()
-      }
-    }
-  })
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id, userOption.value)
 }
 
 const userNicknameFormat = (row) => {
@@ -184,6 +187,6 @@ const userNicknameFormat = (row) => {
 // ========== 初始化 ==========
 onMounted(async () => {
   await getUserList()
-  await getTree()
+  await getList()
 })
 </script>

+ 177 - 0
src/views/system/dict/data.form.vue

@@ -0,0 +1,177 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="字典类型" prop="type">
+        <el-input
+          :disabled="typeof formData.id !== 'undefined'"
+          v-model="formData.dictType"
+          placeholder="请输入参数名称"
+        />
+      </el-form-item>
+      <el-form-item label="数据标签" prop="label">
+        <el-input v-model="formData.label" placeholder="请输入数据标签" />
+      </el-form-item>
+      <el-form-item label="数据键值" prop="value">
+        <el-input v-model="formData.value" placeholder="请输入数据键值" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" controls-position="right" :min="0" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="parseInt(dict.value)"
+            >{{ dict.label }}</el-radio
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="颜色类型" prop="colorType">
+        <el-select v-model="formData.colorType">
+          <el-option
+            v-for="item in colorTypeOptions"
+            :key="item.value"
+            :label="item.label + '(' + item.value + ')'"
+            :value="item.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="CSS Class" prop="cssClass">
+        <el-input v-model="formData.cssClass" placeholder="请输入 CSS Class" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import * as DictDataApi from '@/api/system/dict/dict.data'
+import { CommonStatusEnum } from '@/utils/constants'
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  sort: undefined,
+  label: '',
+  value: '',
+  dictType: '',
+  status: CommonStatusEnum.ENABLE,
+  colorType: '',
+  cssClass: '',
+  remark: ''
+})
+const formRules = reactive({
+  label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
+  value: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+// 数据标签回显样式
+const colorTypeOptions = readonly([
+  {
+    value: 'default',
+    label: '默认'
+  },
+  {
+    value: 'primary',
+    label: '主要'
+  },
+  {
+    value: 'success',
+    label: '成功'
+  },
+  {
+    value: 'info',
+    label: '信息'
+  },
+  {
+    value: 'warning',
+    label: '警告'
+  },
+  {
+    value: 'danger',
+    label: '危险'
+  }
+])
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DictDataApi.getDictData(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as DictDataApi.DictDataVO
+    if (formType.value === 'create') {
+      await DictDataApi.createDictData(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DictDataApi.updateDictData(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    sort: undefined,
+    label: '',
+    value: '',
+    dictType: '',
+    status: CommonStatusEnum.ENABLE,
+    colorType: '',
+    cssClass: '',
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 202 - 0
src/views/system/dict/data.vue

@@ -0,0 +1,202 @@
+<template>
+  <content-wrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="字典名称" prop="dictType">
+        <el-select v-model="queryParams.dictType" class="!w-240px">
+          <el-option v-for="item in dicts" :key="item.type" :label="item.name" :value="item.type" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="字典标签" prop="label">
+        <el-input
+          v-model="queryParams.label"
+          placeholder="请输入字典标签"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="数据状态" clearable class="!w-240px">
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:dict:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="字典编码" align="center" prop="id" />
+      <el-table-column label="字典标签" align="center" prop="label" />
+      <el-table-column label="字典键值" align="center" prop="value" />
+      <el-table-column label="字典排序" align="center" prop="sort" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="颜色类型" align="center" prop="colorType" />
+      <el-table-column label="CSS Class" align="center" prop="cssClass" />
+      <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:dict:update']"
+          >
+            修改
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:dict:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <data-form ref="modalRef" @success="getList" />
+</template>
+<script setup lang="ts" name="Data">
+import * as DictDataApi from '@/api/system/dict/dict.data'
+import * as DictTypeApi from '@/api/system/dict/dict.type'
+import { getDictOptions, DICT_TYPE } from '@/utils/dict'
+import download from '@/utils/download'
+import { dateFormatter } from '@/utils/formatTime'
+import DataForm from './data.form.vue'
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const route = useRoute() // 路由
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  label: '',
+  status: undefined,
+  dictType: route.params.dictType
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+const dicts = ref<DictTypeApi.DictTypeVO[]>() // 字典类型的列表
+
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await DictDataApi.getDictDataPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await DictDataApi.deleteDictData(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await DictDataApi.exportDictDataApi(queryParams)
+    download.excel(data, '字典数据.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 查询字典(精简)列表 */
+const getDictList = async () => {
+  dicts.value = await DictTypeApi.listSimpleDictType()
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+  // 查询字典(精简)列表
+  getDictList()
+})
+</script>

+ 0 - 104
src/views/system/dict/dict.data.ts

@@ -1,104 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-// 国际化
-const { t } = useI18n()
-// 表单校验
-export const dictDataRules = reactive({
-  label: [required],
-  value: [required],
-  sort: [required]
-})
-// crudSchemas
-export const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  action: true,
-  actionWidth: '140px',
-  searchSpan: 12,
-  columns: [
-    {
-      title: '字典类型',
-      field: 'dictType',
-      isTable: false,
-      isForm: false
-    },
-    {
-      title: '数据标签',
-      field: 'label',
-      isSearch: true
-    },
-    {
-      title: '数据键值',
-      field: 'value'
-    },
-    // {
-    //   title: '标签类型',
-    //   field: 'colorType',
-    //   form: {
-    //     component: 'Select',
-    //     componentProps: {
-    //       options: [
-    //         {
-    //           label: 'default',
-    //           value: ''
-    //         },
-    //         {
-    //           label: 'success',
-    //           value: 'success'
-    //         },
-    //         {
-    //           label: 'info',
-    //           value: 'info'
-    //         },
-    //         {
-    //           label: 'warning',
-    //           value: 'warning'
-    //         },
-    //         {
-    //           label: 'danger',
-    //           value: 'danger'
-    //         }
-    //       ]
-    //     }
-    //   },
-    //   isTable: false
-    // },
-    {
-      title: '颜色',
-      field: 'cssClass',
-      isTable: false,
-      form: {
-        component: 'ColorPicker',
-        componentProps: {
-          predefine: ['#ffffff', '#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#c71585']
-        }
-      }
-    },
-    {
-      title: '显示排序',
-      field: 'sort',
-      isTable: false
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number'
-    },
-    {
-      title: t('form.remark'),
-      field: 'remark',
-      form: {
-        component: 'Input',
-        componentProps: {
-          type: 'textarea',
-          rows: 4
-        },
-        colProps: {
-          span: 24
-        }
-      },
-      isTable: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 0 - 65
src/views/system/dict/dict.type.ts

@@ -1,65 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const dictTypeRules = reactive({
-  name: [required],
-  type: [required]
-})
-// 新增 + 修改
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  action: true,
-  actionWidth: '140px',
-  searchSpan: 12,
-  columns: [
-    {
-      title: '字典名称',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '字典类型',
-      field: 'type',
-      isSearch: true
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      table: {
-        width: 70
-      }
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      isTable: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: t('form.remark'),
-      field: 'remark',
-      isTable: false,
-      form: {
-        componentProps: {
-          type: 'textarea',
-          rows: 4
-        },
-        colProps: {
-          span: 24
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 123 - 0
src/views/system/dict/form.vue

@@ -0,0 +1,123 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="字典名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入字典名称" />
+      </el-form-item>
+      <el-form-item label="字典类型" prop="type">
+        <el-input
+          :disabled="typeof formData.id !== 'undefined'"
+          v-model="formData.type"
+          placeholder="请输入参数名称"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="parseInt(dict.value)"
+            >{{ dict.label }}</el-radio
+          >
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import * as DictTypeApi from '@/api/system/dict/dict.type'
+import { CommonStatusEnum } from '@/utils/constants'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  type: '',
+  status: CommonStatusEnum.ENABLE,
+  remark: ''
+})
+const formRules = reactive({
+  name: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
+  type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await DictTypeApi.getDictType(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as DictTypeApi.DictTypeVO
+    if (formType.value === 'create') {
+      await DictTypeApi.createDictType(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await DictTypeApi.updateDictType(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    type: '',
+    name: '',
+    status: CommonStatusEnum.ENABLE,
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 187 - 233
src/views/system/dict/index.vue

@@ -1,257 +1,211 @@
 <template>
-  <div class="flex">
-    <!-- ====== 字典分类 ====== -->
-    <el-card class="w-1/2 dict" :gutter="12" shadow="always">
-      <template #header>
-        <div class="card-header">
-          <span>字典分类</span>
-        </div>
-      </template>
-      <XTable @register="registerType" @cell-click="cellClickEvent">
-        <!-- 操作:新增类型 -->
-        <template #toolbar_buttons>
-          <XButton
-            type="primary"
-            preIcon="ep:zoom-in"
-            :title="t('action.add')"
-            v-hasPermi="['system:dict:create']"
-            @click="handleTypeCreate()"
+  <!-- 搜索工作栏 -->
+  <content-wrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="字典名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入字典名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="字典类型" prop="type">
+        <el-input
+          v-model="queryParams.type"
+          placeholder="请输入字典类型"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="字典状态" clearable class="!w-240px">
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="dict.value"
           />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']">
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:dict:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="字典编号" align="center" prop="id" />
+      <el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip />
+      <el-table-column label="字典类型" align="center" prop="type" width="300" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>
-        <template #actionbtns_default="{ row }">
-          <!-- 操作:编辑类型 -->
-          <XTextButton
-            preIcon="ep:edit"
-            :title="t('action.edit')"
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column
+        label="创建时间"
+        :formatter="dateFormatter"
+        align="center"
+        prop="createTime"
+        width="180"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
             v-hasPermi="['system:dict:update']"
-            @click="handleTypeUpdate(row.id)"
-          />
-          <!-- 操作:删除类型 -->
-          <XTextButton
-            preIcon="ep:delete"
-            :title="t('action.del')"
+          >
+            修改
+          </el-button>
+          <router-link :to="'/dict/type/data/' + scope.row.type">
+            <el-button link type="primary">数据</el-button>
+          </router-link>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
             v-hasPermi="['system:dict:delete']"
-            @click="typeDeleteData(row.id)"
-          />
+          >
+            删除
+          </el-button>
         </template>
-      </XTable>
-      <!-- @星语:分页和列表重叠在一起了 -->
-    </el-card>
-    <!-- ====== 字典数据 ====== -->
-    <el-card class="w-1/2 dict ml-3" :gutter="12" shadow="hover">
-      <template #header>
-        <div class="card-header">
-          <span>字典数据</span>
-        </div>
-      </template>
-      <!-- 列表 -->
-      <div v-if="!tableTypeSelect">
-        <span>请从左侧选择</span>
-      </div>
-      <div v-if="tableTypeSelect">
-        <!-- 列表 -->
-        <XTable @register="registerData">
-          <!-- 操作:新增数据 -->
-          <template #toolbar_buttons>
-            <XButton
-              type="primary"
-              preIcon="ep:zoom-in"
-              :title="t('action.add')"
-              v-hasPermi="['system:dict:create']"
-              @click="handleDataCreate()"
-            />
-          </template>
-          <template #actionbtns_default="{ row }">
-            <!-- 操作:修改数据 -->
-            <XTextButton
-              v-hasPermi="['system:dict:update']"
-              preIcon="ep:edit"
-              :title="t('action.edit')"
-              @click="handleDataUpdate(row.id)"
-            />
-            <!-- 操作:删除数据 -->
-            <XTextButton
-              v-hasPermi="['system:dict:delete']"
-              preIcon="ep:delete"
-              :title="t('action.del')"
-              @click="dataDeleteData(row.id)"
-            />
-          </template>
-        </XTable>
-      </div>
-    </el-card>
-    <XModal id="dictModel" v-model="dialogVisible" :title="dialogTitle">
-      <Form
-        v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
-        :schema="DictTypeSchemas.allSchemas.formSchema"
-        :rules="DictTypeSchemas.dictTypeRules"
-        ref="typeFormRef"
-      >
-        <template #type>
-          <template v-if="actionType == 'typeUpdate'">
-            <el-tag>{{ dictTypeValue }}</el-tag>
-          </template>
-          <template v-else><el-input v-model="dictTypeValue" /> </template>
-        </template>
-      </Form>
-      <Form
-        v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
-        :schema="DictDataSchemas.allSchemas.formSchema"
-        :rules="DictDataSchemas.dictDataRules"
-        ref="dataFormRef"
-      />
-      <!-- 操作按钮 -->
-      <template #footer>
-        <XButton
-          v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
-          type="primary"
-          :title="t('action.save')"
-          :loading="actionLoading"
-          @click="submitTypeForm"
-        />
-        <XButton
-          v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
-          type="primary"
-          :title="t('action.save')"
-          :loading="actionLoading"
-          @click="submitDataForm"
-        />
-        <XButton :title="t('dialog.close')" @click="dialogVisible = false" />
-      </template>
-    </XModal>
-  </div>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <dict-type-form ref="modalRef" @success="getList" />
 </template>
+
 <script setup lang="ts" name="Dict">
-import { VxeTableEvents } from 'vxe-table'
-import type { FormExpose } from '@/components/Form'
-import * as DictTypeSchemas from './dict.type'
-import * as DictDataSchemas from './dict.data'
+import { getDictOptions, DICT_TYPE } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 import * as DictTypeApi from '@/api/system/dict/dict.type'
-import * as DictDataApi from '@/api/system/dict/dict.data'
-import { DictDataVO, DictTypeVO } from '@/api/system/dict/types'
-
-const { t } = useI18n() // 国际化
+import DictTypeForm from './form.vue'
+import download from '@/utils/download'
 const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
 
-const [registerType, { reload: typeGetList, deleteData: typeDeleteData }] = useXTable({
-  allSchemas: DictTypeSchemas.allSchemas,
-  getListApi: DictTypeApi.getDictTypePageApi,
-  deleteApi: DictTypeApi.deleteDictTypeApi
-})
-
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 字典表格数据
 const queryParams = reactive({
-  dictType: null
-})
-const [registerData, { reload: dataGetList, deleteData: dataDeleteData }] = useXTable({
-  allSchemas: DictDataSchemas.allSchemas,
-  params: queryParams,
-  getListApi: DictDataApi.getDictDataPageApi,
-  deleteApi: DictDataApi.deleteDictDataApi
+  pageNo: 1,
+  pageSize: 10,
+  name: '',
+  type: '',
+  status: undefined,
+  createTime: []
 })
-// ========== 字典分类列表相关 ==========
-const dictTypeValue = ref('')
-// 字典分类修改操作
-const handleTypeCreate = () => {
-  dictTypeValue.value = ''
-  setDialogTile('typeCreate')
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询字典类型列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await DictTypeApi.getDictTypePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
-const handleTypeUpdate = async (rowId: number) => {
-  setDialogTile('typeUpdate')
-  // 设置数据
-  const res = await DictTypeApi.getDictTypeApi(rowId)
-  dictTypeValue.value = res.type
-  unref(typeFormRef)?.setValues(res)
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 字典数据修改操作
-const handleDataCreate = () => {
-  setDialogTile('dataCreate')
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
-const handleDataUpdate = async (rowId: number) => {
-  setDialogTile('dataUpdate')
-  // 设置数据
-  const res = await DictDataApi.getDictDataApi(rowId)
-  unref(dataFormRef)?.setValues(res)
+
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
-// 字典分类点击行事件
-const parentType = ref('')
-const tableTypeSelect = ref(false)
-const cellClickEvent: VxeTableEvents.CellClick = async ({ row }) => {
-  tableTypeSelect.value = true
-  queryParams.dictType = row['type']
-  await nextTick()
-  await dataGetList()
-  parentType.value = row['type']
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await DictTypeApi.deleteDictType(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
-// 弹出框
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const actionLoading = ref(false) // 遮罩层
-const typeFormRef = ref<FormExpose>() // 分类表单 Ref
-const dataFormRef = ref<FormExpose>() // 数据表单 Ref
-const actionType = ref('') // 操作按钮的类型
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await DictTypeApi.exportDictType(queryParams)
+    download.excel(data, '字典类型.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
 }
 
-// 同步dictTypeValue到form 否则导致表单验证不通过
-watch(dictTypeValue, (val) => {
-  unref(typeFormRef)?.setValues({ type: val })
+/** 初始化 **/
+onMounted(() => {
+  getList()
 })
-
-// 提交按钮
-const submitTypeForm = async () => {
-  const elForm = unref(typeFormRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid && dictTypeValue.value != '') {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(typeFormRef)?.formModel as DictTypeVO
-        if (actionType.value === 'typeCreate') {
-          data.type = dictTypeValue.value
-          await DictTypeApi.createDictTypeApi(data)
-          message.success(t('common.createSuccess'))
-        } else if (actionType.value === 'typeUpdate') {
-          await DictTypeApi.updateDictTypeApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        typeGetList()
-      }
-    }
-  })
-}
-const submitDataForm = async () => {
-  const elForm = unref(dataFormRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(dataFormRef)?.formModel as DictDataVO
-        if (actionType.value === 'dataCreate') {
-          data.dictType = parentType.value
-          await DictDataApi.createDictDataApi(data)
-          message.success(t('common.createSuccess'))
-        } else if (actionType.value === 'dataUpdate') {
-          await DictDataApi.updateDictDataApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        dataGetList()
-      }
-    }
-  })
-}
 </script>

+ 0 - 54
src/views/system/errorCode/errorCode.data.ts

@@ -1,54 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  applicationName: [required],
-  code: [required],
-  message: [required]
-})
-
-// 新增 + 修改
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '编号',
-  action: true,
-  columns: [
-    {
-      title: '错误码类型',
-      field: 'type',
-      dictType: DICT_TYPE.SYSTEM_ERROR_CODE_TYPE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '应用名',
-      field: 'applicationName',
-      isSearch: true
-    },
-    {
-      title: '错误码编码',
-      field: 'code',
-      isSearch: true
-    },
-    {
-      title: '错误码错误提示',
-      field: 'message',
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 112 - 0
src/views/system/errorCode/form.vue

@@ -0,0 +1,112 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="应用名" prop="applicationName">
+        <el-input v-model="formData.applicationName" placeholder="请输入应用名" clearable />
+      </el-form-item>
+      <el-form-item label="错误码编码" prop="code">
+        <el-input v-model="formData.code" placeholder="请输入错误码编码" clearable />
+      </el-form-item>
+      <el-form-item label="错误码提示" prop="message">
+        <el-input v-model="formData.message" placeholder="请输入错误码提示" clearable />
+      </el-form-item>
+      <el-form-item label="备注" prop="memo">
+        <el-input v-model="formData.memo" placeholder="请输入备注" clearable />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as ErrorCodeApi from '@/api/system/errorCode'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+// 表单参数
+const formData = ref({
+  id: undefined,
+  code: undefined,
+  applicationName: '',
+  message: '',
+  memo: ''
+})
+// 表单校验
+const formRules = reactive({
+  applicationName: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
+  code: [{ required: true, message: '错误码编码不能为空', trigger: 'blur' }],
+  message: [{ required: true, message: '错误码提示不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ErrorCodeApi.getErrorCodeApi(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as ErrorCodeApi.ErrorCodeVO
+    if (formType.value === 'create') {
+      await ErrorCodeApi.createErrorCodeApi(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ErrorCodeApi.updateErrorCodeApi(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 表单重置 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    applicationName: '',
+    code: undefined,
+    message: '',
+    memo: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 207 - 123
src/views/system/errorCode/index.vue

@@ -1,145 +1,229 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <!-- 操作:新增 -->
-      <template #toolbar_buttons>
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:error-code:create']"
-          @click="handleCreate()"
+  <!-- 搜索工作栏 -->
+  <content-wrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="90px"
+    >
+      <el-form-item label="错误码类型" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择错误码类型" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_ERROR_CODE_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+            class="!w-240px"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="应用名" prop="applicationName">
+        <el-input
+          v-model="queryParams.applicationName"
+          placeholder="请输入应用名"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:error-code:update']"
-          @click="handleUpdate(row.id)"
+      </el-form-item>
+      <el-form-item label="错误码编码" prop="code">
+        <el-input
+          v-model="queryParams.code"
+          placeholder="请输入错误码编码"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:error-code:query']"
-          @click="handleDetail(row.id)"
+      </el-form-item>
+      <el-form-item label="错误码提示" prop="message">
+        <el-input
+          v-model="queryParams.message"
+          placeholder="请输入错误码提示"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['system:error-code:delete']"
-          @click="deleteData(row.id)"
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
         />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <!-- 弹窗 -->
-  <XModal id="errorCodeModel" v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    />
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          @click="openModal('create')"
+          v-hasPermi="['system:error-code:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:error-code:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="类型" align="center" prop="type" width="80">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column label="应用名" align="center" prop="applicationName" width="200" />
+      <el-table-column label="错误码编码" align="center" prop="code" width="120" />
+      <el-table-column label="错误码提示" align="center" prop="message" width="300" />
+      <el-table-column label="备注" align="center" prop="memo" width="200" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:error-code:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:error-code:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <error-code-form ref="modalRef" @success="getList" />
 </template>
+
 <script setup lang="ts" name="ErrorCode">
-import type { FormExpose } from '@/components/Form'
-// 业务相关的 import
-import { rules, allSchemas } from './errorCode.data'
 import * as ErrorCodeApi from '@/api/system/errorCode'
-
-const { t } = useI18n() // 国际化
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import ErrorCodeForm from './form.vue'
+import download from '@/utils/download'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: ErrorCodeApi.getErrorCodePageApi,
-  deleteApi: ErrorCodeApi.deleteErrorCodeApi
+const { t } = useI18n() // 国际化
+
+// 遮罩层
+const loading = ref(true)
+// 导出遮罩层
+const exportLoading = ref(false)
+// 总条数
+const total = ref(0)
+// 错误码列表
+const list = ref([])
+// 查询参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  type: undefined,
+  applicationName: undefined,
+  code: undefined,
+  message: undefined,
+  createTime: []
 })
-// 弹窗相关的变量
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
+// 搜索的表单
+const queryFormRef = ref()
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  // 执行查询
+  try {
+    const data = await ErrorCodeApi.getErrorCodePageApi(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await ErrorCodeApi.getErrorCodeApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  // 设置数据
-  const res = await ErrorCodeApi.getErrorCodeApi(rowId)
-  detailData.value = res
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 提交新增/修改的表单
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as ErrorCodeApi.ErrorCodeVO
-        if (actionType.value === 'create') {
-          await ErrorCodeApi.createErrorCodeApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await ErrorCodeApi.updateErrorCodeApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    await ErrorCodeApi.deleteErrorCodeApi(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await ErrorCodeApi.excelErrorCodeApi(queryParams)
+    download.excel(data, '错误码.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 52 - 54
src/views/system/mail/account/account.data.ts

@@ -1,10 +1,10 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
+import { dateFormatter } from '@/utils/formatTime'
 
 const { t } = useI18n() // 国际化
 
 // 表单校验
 export const rules = reactive({
-  // mail: [required],
   mail: [
     { required: true, message: t('profile.rules.mail'), trigger: 'blur' },
     {
@@ -20,56 +20,54 @@ export const rules = reactive({
   sslEnable: [required]
 })
 
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id', // 默认的主键 ID
-  primaryTitle: '编号',
-  primaryType: 'id',
-  action: true,
-  actionWidth: '200', // 3 个按钮默认 200,如有删减对应增减即可
-  columns: [
-    {
-      title: '邮箱',
-      field: 'mail',
-      isSearch: true
-    },
-    {
-      title: '用户名',
-      field: 'username',
-      isSearch: true
-    },
-    {
-      title: '密码',
-      field: 'password',
-      isTable: false
-    },
-    {
-      title: 'SMTP 服务器域名',
-      field: 'host'
-    },
-    {
-      title: 'SMTP 服务器端口',
-      field: 'port',
-      form: {
-        component: 'InputNumber',
-        value: 465
-      }
-    },
-    {
-      title: '是否开启 SSL',
-      field: 'sslEnable',
-      dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
-      dictClass: 'boolean'
-    },
-    {
-      title: '创建时间',
-      field: 'createTime',
-      isForm: false,
-      formatter: 'formatDate',
-      table: {
-        width: 180
-      }
+// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    label: '邮箱',
+    field: 'mail',
+    isSearch: true
+  },
+  {
+    label: '用户名',
+    field: 'username',
+    isSearch: true
+  },
+  {
+    label: '密码',
+    field: 'password',
+    isTable: false
+  },
+  {
+    label: 'SMTP 服务器域名',
+    field: 'host'
+  },
+  {
+    label: 'SMTP 服务器端口',
+    field: 'port',
+    form: {
+      component: 'InputNumber',
+      value: 465
     }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
+  },
+  {
+    label: '是否开启 SSL',
+    field: 'sslEnable',
+    dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
+    dictClass: 'boolean',
+    form: {
+      component: 'Radio'
+    }
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    isForm: false,
+    formatter: dateFormatter
+  },
+  {
+    label: '操作',
+    field: 'action',
+    isForm: false
+  }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)

+ 66 - 0
src/views/system/mail/account/form.vue

@@ -0,0 +1,66 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="modelVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as MailAccountApi from '@/api/system/mail/account'
+import { rules, allSchemas } from './account.data'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await MailAccountApi.getMailAccount(id)
+      formRef.value.setValues(data)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.getElFormRef().validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formRef.value.formModel as MailAccountApi.MailAccountVO
+    if (formType.value === 'create') {
+      await MailAccountApi.createMailAccount(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await MailAccountApi.updateMailAccount(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+</script>

+ 66 - 133
src/views/system/mail/account/index.vue

@@ -1,151 +1,84 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
+  <!-- 搜索工作栏 -->
+  <content-wrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
+      <!-- 新增等操作按钮 -->
+      <template #actionMore>
+        <el-button
           type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
+          @click="openModal('create')"
           v-hasPermi="['system:mail-account:create']"
-          @click="handleCreate()"
-        />
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
       </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
+    </Search>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <Table
+      :columns="allSchemas.tableColumns"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          @click="openModal('update', row.id)"
           v-hasPermi="['system:mail-account:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:mail-account:query']"
-          @click="handleDetail(row.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="danger"
           v-hasPermi="['system:mail-account:delete']"
-          @click="deleteData(row.id)"
-        />
+          @click="handleDelete(row.id)"
+        >
+          删除
+        </el-button>
       </template>
-    </XTable>
-  </ContentWrap>
-  <!-- 弹窗 -->
-  <XModal id="mailAccountModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
-    <!-- 表单:添加/修改 -->
-    <Form
-      ref="formRef"
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-    />
-    <!-- 表单:详情 -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
-      />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
-    </template>
-  </XModal>
+    </Table>
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <mail-account-form ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="MailAccount">
-import { FormExpose } from '@/components/Form'
-// 业务相关的 import
-import { rules, allSchemas } from './account.data'
+import { allSchemas } from './account.data'
 import * as MailAccountApi from '@/api/system/mail/account'
+import MailAccountForm from './form.vue'
 
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: MailAccountApi.getMailAccountPageApi,
-  deleteApi: MailAccountApi.deleteMailAccountApi
+// tableObject:表格的属性对象,可获得分页大小、条数等属性
+// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
+// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
+const { tableObject, tableMethods } = useTable({
+  getListApi: MailAccountApi.getMailAccountPage, // 分页接口
+  delListApi: MailAccountApi.deleteMailAccount // 删除接口
 })
+// 获得表格的各种操作
+const { getList, setSearchParams } = tableMethods
 
-// 弹窗相关的变量
-const modelVisible = ref(false) // 是否显示弹出层
-const modelTitle = ref('edit') // 弹出层标题
-const modelLoading = ref(false) // 弹出层loading
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-
-// 设置标题
-const setDialogTile = (type: string) => {
-  modelLoading.value = true
-  modelTitle.value = t('action.' + type)
-  actionType.value = type
-  modelVisible.value = true
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
-  modelLoading.value = false
+/** 删除按钮操作 */
+const handleDelete = (id: number) => {
+  tableMethods.delList(id, false)
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await MailAccountApi.getMailAccountApi(rowId)
-  unref(formRef)?.setValues(res)
-  modelLoading.value = false
-}
-
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await MailAccountApi.getMailAccountApi(rowId)
-  detailData.value = res
-  modelLoading.value = false
-}
-
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as MailAccountApi.MailAccountVO
-        if (actionType.value === 'create') {
-          await MailAccountApi.createMailAccountApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await MailAccountApi.updateMailAccountApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        modelVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
-}
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 31 - 0
src/views/system/mail/log/detail.vue

@@ -0,0 +1,31 @@
+<template>
+  <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500">
+    <Descriptions :schema="allSchemas.detailSchema" :data="detailData">
+      <!-- 展示 HTML 内容 -->
+      <template #templateContent="{ row }">
+        <div v-html="row.templateContent"></div>
+      </template>
+    </Descriptions>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as MailLogApi from '@/api/system/mail/log'
+import { allSchemas } from './log.data'
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const detailLoading = ref(false) // 表单的加载中
+const detailData = ref() // 详情数据
+
+/** 打开弹窗 */
+const openModal = async (id: number) => {
+  modelVisible.value = true
+  // 设置数据
+  detailLoading.value = true
+  try {
+    detailData.value = await MailLogApi.getMailLog(id)
+  } finally {
+    detailLoading.value = false
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+</script>

+ 44 - 83
src/views/system/mail/log/index.vue

@@ -1,98 +1,59 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #accountId_search>
-        <el-select v-model="queryParams.accountId">
-          <el-option :key="undefined" label="全部" :value="undefined" />
-          <el-option
-            v-for="item in accountOptions"
-            :key="item.id"
-            :label="item.mail"
-            :value="item.id"
-          />
-        </el-select>
-      </template>
-      <template #toMail_default="{ row }">
-        <div>{{ row.toMail }}</div>
-        <div v-if="row.userType && row.userId">
-          <DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />{{ '(' + row.userId + ')' }}
-        </div>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
+  <!-- 搜索工作栏 -->
+  <content-wrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <Table
+      :columns="allSchemas.tableColumns"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          @click="openModal(row.id)"
           v-hasPermi="['system:mail-log:query']"
-          @click="handleDetail(row.id)"
-        />
+        >
+          详情
+        </el-button>
       </template>
-    </XTable>
-  </ContentWrap>
-  <!-- 弹窗 -->
-  <XModal id="mailLogModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
-    <!-- 表单:详情 -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <template #footer>
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
-    </template>
-  </XModal>
+    </Table>
+  </content-wrap>
+
+  <!-- 表单弹窗:详情 -->
+  <mail-log-detail ref="modalRef" />
 </template>
 <script setup lang="ts" name="MailLog">
-// 业务相关的 import
-import { DICT_TYPE } from '@/utils/dict'
 import { allSchemas } from './log.data'
 import * as MailLogApi from '@/api/system/mail/log'
-import * as MailAccountApi from '@/api/system/mail/account'
-
-const { t } = useI18n() // 国际化
+import MailLogDetail from './detail.vue'
 
-// 列表相关的变量
-const queryParams = reactive({
-  accountId: null
+// tableObject:表格的属性对象,可获得分页大小、条数等属性
+// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
+// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
+const { tableObject, tableMethods } = useTable({
+  getListApi: MailLogApi.getMailLogPage // 分页接口
 })
-const [registerTable] = useXTable({
-  allSchemas: allSchemas,
-  topActionSlots: false,
-  params: queryParams,
-  getListApi: MailLogApi.getMailLogPageApi
-})
-const accountOptions = ref<any[]>([]) // 账号下拉选项
-
-// 弹窗相关的变量
-const modelVisible = ref(false) // 是否显示弹出层
-const modelTitle = ref('edit') // 弹出层标题
-const modelLoading = ref(false) // 弹出层loading
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const detailData = ref() // 详情 Ref
-
-// 设置标题
-const setDialogTile = (type: string) => {
-  modelLoading.value = true
-  modelTitle.value = t('action.' + type)
-  actionType.value = type
-  modelVisible.value = true
-}
+// 获得表格的各种操作
+const { getList, setSearchParams } = tableMethods
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await MailLogApi.getMailLogApi(rowId)
-  detailData.value = res
-  modelLoading.value = false
+/** 详情操作 */
+const modalRef = ref()
+const openModal = (id: number) => {
+  modalRef.value.openModal(id)
 }
 
-// ========== 初始化 ==========
+/** 初始化 **/
 onMounted(() => {
-  MailAccountApi.getSimpleMailAccounts().then((data) => {
-    accountOptions.value = data
-  })
+  getList()
 })
 </script>

+ 127 - 115
src/views/system/mail/log/log.data.ts

@@ -1,121 +1,133 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
+import { dateFormatter } from '@/utils/formatTime'
+import * as MailAccountApi from '@/api/system/mail/account'
 
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryTitle: '编号',
-  primaryType: 'id',
-  action: true,
-  actionWidth: '70',
-  columns: [
-    {
-      title: '发送时间',
-      field: 'sendTime',
-      table: {
-        width: 180
-      },
-      formatter: 'formatDate',
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '接收邮箱',
-      field: 'toMail',
-      isSearch: true,
-      table: {
-        width: 180,
-        slots: {
-          default: 'toMail_default'
-        }
+// 邮箱账号的列表
+const accounts = await MailAccountApi.getSimpleMailAccountList()
+
+// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    label: '编号',
+    field: 'id'
+  },
+  {
+    label: '发送时间',
+    field: 'sendTime',
+    formatter: dateFormatter,
+    search: {
+      show: true,
+      component: 'DatePicker',
+      componentProps: {
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        type: 'daterange',
+        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
       }
     },
-    {
-      title: '用户编号',
-      field: 'userId',
-      isSearch: true,
-      isTable: false
-    },
-    {
-      title: '用户类型',
-      field: 'userType',
-      dictType: DICT_TYPE.USER_TYPE,
-      dictClass: 'number',
-      isSearch: true,
-      isTable: false
-    },
-    {
-      title: '邮件标题',
-      field: 'templateTitle'
-    },
-    {
-      title: '邮件内容',
-      field: 'templateContent',
-      isTable: false
-    },
-    {
-      title: '邮箱参数',
-      field: 'templateParams',
-      isTable: false
-    },
-    {
-      title: '发送状态',
-      field: 'sendStatus',
-      dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,
-      dictClass: 'string',
-      isSearch: true
-    },
-    {
-      title: '邮箱账号',
-      field: 'accountId',
-      isSearch: true,
-      isTable: false,
-      search: {
-        slots: {
-          default: 'accountId_search'
+    detail: {
+      dateFormat: 'YYYY-MM-DD HH:mm:ss'
+    }
+  },
+  {
+    label: '接收邮箱',
+    field: 'toMail'
+  },
+  {
+    label: '用户编号',
+    field: 'userId',
+    isSearch: true,
+    isTable: false
+  },
+  {
+    label: '用户类型',
+    field: 'userType',
+    dictType: DICT_TYPE.USER_TYPE,
+    dictClass: 'number',
+    isSearch: true,
+    isTable: false
+  },
+  {
+    label: '邮件标题',
+    field: 'templateTitle'
+  },
+  {
+    label: '邮件内容',
+    field: 'templateContent',
+    isTable: false
+  },
+  {
+    label: '邮箱参数',
+    field: 'templateParams',
+    isTable: false
+  },
+  {
+    label: '发送状态',
+    field: 'sendStatus',
+    dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,
+    dictClass: 'string',
+    isSearch: true
+  },
+  {
+    label: '邮箱账号',
+    field: 'accountId',
+    isTable: false,
+    search: {
+      show: true,
+      component: 'Select',
+      api: () => accounts,
+      componentProps: {
+        optionsAlias: {
+          labelField: 'mail',
+          valueField: 'id'
         }
       }
-    },
-    {
-      title: '发送邮箱地址',
-      field: 'fromMail',
-      table: {
-        title: '邮箱账号'
-      }
-    },
-    {
-      title: '模板编号',
-      field: 'templateId',
-      isSearch: true
-    },
-    {
-      title: '模板编码',
-      field: 'templateCode',
-      isTable: false
-    },
-    {
-      title: '模版发送人名称',
-      field: 'templateNickname',
-      isTable: false
-    },
-    {
-      title: '发送返回的消息编号',
-      field: 'sendMessageId',
-      isTable: false
-    },
-    {
-      title: '发送异常',
-      field: 'sendException',
-      isTable: false
-    },
-    {
-      title: '创建时间',
-      field: 'createTime',
-      isTable: false
     }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
+  },
+  {
+    label: '发送邮箱地址',
+    field: 'fromMail',
+    table: {
+      label: '邮箱账号'
+    }
+  },
+  {
+    label: '模板编号',
+    field: 'templateId',
+    isSearch: true
+  },
+  {
+    label: '模板编码',
+    field: 'templateCode',
+    isTable: false
+  },
+  {
+    label: '模版发送人名称',
+    field: 'templateNickname',
+    isTable: false
+  },
+  {
+    label: '发送返回的消息编号',
+    field: 'sendMessageId',
+    isTable: false
+  },
+  {
+    label: '发送异常',
+    field: 'sendException',
+    isTable: false
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    isTable: false,
+    formatter: dateFormatter,
+    detail: {
+      dateFormat: 'YYYY-MM-DD HH:mm:ss'
+    }
+  },
+  {
+    label: '操作',
+    field: 'action',
+    isDetail: false
+  }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)

+ 66 - 0
src/views/system/mail/template/form.vue

@@ -0,0 +1,66 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible" :scroll="true" :width="800" :max-height="500">
+    <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="modelVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as MailTemplateApi from '@/api/system/mail/template'
+import { rules, allSchemas } from './template.data'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await MailTemplateApi.getMailTemplate(id)
+      formRef.value.setValues(data)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.getElFormRef().validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formRef.value.formModel as MailTemplateApi.MailTemplateVO
+    if (formType.value === 'create') {
+      await MailTemplateApi.createMailTemplate(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await MailTemplateApi.updateMailTemplate(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+</script>

+ 78 - 249
src/views/system/mail/template/index.vue

@@ -1,273 +1,102 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #accountId_search>
-        <el-select v-model="queryParams.accountId">
-          <el-option :key="undefined" label="全部" :value="undefined" />
-          <el-option
-            v-for="item in accountOptions"
-            :key="item.id"
-            :label="item.mail"
-            :value="item.id"
-          />
-        </el-select>
-      </template>
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
+  <!-- 搜索工作栏 -->
+  <content-wrap>
+    <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
+      <!-- 新增等操作按钮 -->
+      <template #actionMore>
+        <el-button
           type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:mail-template:create']"
-          @click="handleCreate()"
-        />
-      </template>
-      <template #accountId_default="{ row }">
-        <span>{{ accountOptions.find((account) => account.id === row.accountId)?.mail }}</span>
+          @click="openModal('create')"
+          v-hasPermi="['system:mail-account:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
       </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:测试短信 -->
-        <XTextButton
-          preIcon="ep:cpu"
-          :title="t('action.test')"
+    </Search>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <Table
+      :columns="allSchemas.tableColumns"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+    >
+      <template #action="{ row }">
+        <el-button
+          link
+          type="primary"
+          @click="openSend(row.id)"
           v-hasPermi="['system:mail-template:send-mail']"
-          @click="handleSendMail(row)"
-        />
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
+        >
+          测试
+        </el-button>
+        <el-button
+          link
+          type="primary"
+          @click="openModal('update', row.id)"
           v-hasPermi="['system:mail-template:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:mail-template:query']"
-          @click="handleDetail(row.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
+        >
+          编辑
+        </el-button>
+        <el-button
+          link
+          type="danger"
           v-hasPermi="['system:mail-template:delete']"
-          @click="deleteData(row.id)"
-        />
+          @click="handleDelete(row.id)"
+        >
+          删除
+        </el-button>
       </template>
-    </XTable>
-  </ContentWrap>
+    </Table>
+  </content-wrap>
 
-  <!-- 添加/修改/详情的弹窗 -->
-  <XModal id="mailTemplateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
-    <!-- 表单:添加/修改 -->
-    <Form
-      ref="formRef"
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-    >
-      <template #accountId="form">
-        <el-select v-model="form.accountId">
-          <el-option
-            v-for="item in accountOptions"
-            :key="item.id"
-            :label="item.mail"
-            :value="item.id"
-          />
-        </el-select>
-      </template>
-    </Form>
-    <!-- 表单:详情 -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
-      />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
-    </template>
-  </XModal>
+  <!-- 表单弹窗:添加/修改 -->
+  <mail-template-form ref="modalRef" @success="getList" />
 
-  <!-- 测试邮件的弹窗 -->
-  <XModal id="sendTest" v-model="sendVisible" title="测试">
-    <el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top">
-      <el-form-item label="模板内容" prop="content">
-        <Editor :model-value="sendForm.content" readonly height="150px" />
-      </el-form-item>
-      <el-form-item label="收件邮箱" prop="mail">
-        <el-input v-model="sendForm.mail" placeholder="请输入收件邮箱" />
-      </el-form-item>
-      <el-form-item
-        v-for="param in sendForm.params"
-        :key="param"
-        :label="'参数 {' + param + '}'"
-        :prop="'templateParams.' + param"
-      >
-        <el-input
-          v-model="sendForm.templateParams[param]"
-          :placeholder="'请输入 ' + param + ' 参数'"
-        />
-      </el-form-item>
-    </el-form>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <XButton
-        type="primary"
-        :title="t('action.test')"
-        :loading="actionLoading"
-        @click="sendTest()"
-      />
-      <XButton :title="t('dialog.close')" @click="sendVisible = false" />
-    </template>
-  </XModal>
+  <!-- 表单弹窗:发送测试 -->
+  <mail-template-send ref="sendRef" />
 </template>
 <script setup lang="ts" name="MailTemplate">
-import { FormExpose } from '@/components/Form'
-// 业务相关的 import
-import { rules, allSchemas } from './template.data'
+import { allSchemas } from './template.data'
 import * as MailTemplateApi from '@/api/system/mail/template'
-import * as MailAccountApi from '@/api/system/mail/account'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
+import MailTemplateForm from './form.vue'
+import MailTemplateSend from './send.vue'
 
-// 列表相关的变量
-const queryParams = reactive({
-  accountId: null
+// tableObject:表格的属性对象,可获得分页大小、条数等属性
+// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
+// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
+const { tableObject, tableMethods } = useTable({
+  getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口
+  delListApi: MailTemplateApi.deleteMailTemplate // 删除接口
 })
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  params: queryParams,
-  getListApi: MailTemplateApi.getMailTemplatePageApi,
-  deleteApi: MailTemplateApi.deleteMailTemplateApi
-})
-const accountOptions = ref<any[]>([]) // 账号下拉选项
-
-// 弹窗相关的变量
-const modelVisible = ref(false) // 是否显示弹出层
-const modelTitle = ref('edit') // 弹出层标题
-const modelLoading = ref(false) // 弹出层loading
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
+// 获得表格的各种操作
+const { getList, setSearchParams } = tableMethods
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  modelLoading.value = true
-  modelTitle.value = t('action.' + type)
-  actionType.value = type
-  modelVisible.value = true
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
-  modelLoading.value = false
-}
-
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await MailTemplateApi.getMailTemplateApi(rowId)
-  unref(formRef)?.setValues(res)
-  modelLoading.value = false
-}
-
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await MailTemplateApi.getMailTemplateApi(rowId)
-  detailData.value = res
-  modelLoading.value = false
-}
-
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as MailTemplateApi.MailTemplateVO
-        if (actionType.value === 'create') {
-          await MailTemplateApi.createMailTemplateApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await MailTemplateApi.updateMailTemplateApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        modelVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
-}
-
-// ========== 测试相关 ==========
-const sendForm = ref({
-  content: '',
-  params: {},
-  mail: '',
-  templateCode: '',
-  templateParams: {}
-})
-const sendRules = ref({
-  mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
-  templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
-  templateParams: {}
-})
-const sendVisible = ref(false)
-
-const handleSendMail = (row: any) => {
-  sendForm.value.content = row.content
-  sendForm.value.params = row.params
-  sendForm.value.templateCode = row.code
-  sendForm.value.templateParams = row.params.reduce(function (obj, item) {
-    obj[item] = undefined
-    return obj
-  }, {})
-  sendRules.value.templateParams = row.params.reduce(function (obj, item) {
-    obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
-    return obj
-  }, {})
-  sendVisible.value = true
+/** 删除按钮操作 */
+const handleDelete = (id: number) => {
+  tableMethods.delList(id, false)
 }
 
-const sendTest = async () => {
-  const data: MailTemplateApi.MailSendReqVO = {
-    mail: sendForm.value.mail,
-    templateCode: sendForm.value.templateCode,
-    templateParams: sendForm.value.templateParams as unknown as Map<string, Object>
-  }
-  const res = await MailTemplateApi.sendMailApi(data)
-  if (res) {
-    message.success('提交发送成功!发送结果,见发送日志编号:' + res)
-  }
-  sendVisible.value = false
+/** 发送测试操作 */
+const sendRef = ref()
+const openSend = (id: number) => {
+  sendRef.value.openModal(id)
 }
 
-// ========== 初始化 ==========
+/** 初始化 **/
 onMounted(() => {
-  MailAccountApi.getSimpleMailAccounts().then((data) => {
-    accountOptions.value = data
-  })
+  getList()
 })
 </script>

+ 115 - 0
src/views/system/mail/template/send.vue

@@ -0,0 +1,115 @@
+<template>
+  <Dialog title="测试" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="模板内容" prop="content">
+        <Editor :model-value="formData.content" readonly height="150px" />
+      </el-form-item>
+      <el-form-item label="收件邮箱" prop="mail">
+        <el-input v-model="formData.mail" placeholder="请输入收件邮箱" />
+      </el-form-item>
+      <el-form-item
+        v-for="param in formData.params"
+        :key="param"
+        :label="'参数 {' + param + '}'"
+        :prop="'templateParams.' + param"
+      >
+        <el-input
+          v-model="formData.templateParams[param]"
+          :placeholder="'请输入 ' + param + ' 参数'"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as MailTemplateApi from '@/api/system/mail/template'
+
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  content: '',
+  params: {},
+  mail: '',
+  templateCode: '',
+  templateParams: new Map()
+})
+const formRules = reactive({
+  mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
+  templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
+  templateParams: {}
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (id: number) => {
+  modelVisible.value = true
+  resetForm()
+  // 设置数据
+  formLoading.value = true
+  try {
+    const data = await MailTemplateApi.getMailTemplate(id)
+    // 设置动态表单
+    formData.value.content = data.content
+    formData.value.params = data.params
+    formData.value.templateCode = data.code
+    formData.value.templateParams = data.params.reduce((obj, item) => {
+      obj[item] = '' // 给每个动态属性赋值,避免无法读取
+      return obj
+    }, {})
+    formRules.templateParams = data.params.reduce((obj, item) => {
+      obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' }
+      return obj
+    }, {})
+  } finally {
+    formLoading.value = false
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as MailTemplateApi.MailSendReqVO
+    const logId = await MailTemplateApi.sendMail(data)
+    if (logId) {
+      message.success('提交发送成功!发送结果,见发送日志编号:' + logId)
+    }
+    modelVisible.value = false
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    content: '',
+    params: {},
+    mail: '',
+    templateCode: '',
+    templateParams: new Map()
+  }
+  formRules.templateParams = {}
+  formRef.value?.resetFields()
+}
+</script>

+ 94 - 79
src/views/system/mail/template/template.data.ts

@@ -1,98 +1,113 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
+import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
+import { dateFormatter } from '@/utils/formatTime'
+import { TableColumn } from '@/types/table'
+import * as MailAccountApi from '@/api/system/mail/account'
+
+// 邮箱账号的列表
+const accounts = await MailAccountApi.getSimpleMailAccountList()
 
 // 表单校验
 export const rules = reactive({
   name: [required],
   code: [required],
   accountId: [required],
-  title: [required],
+  label: [required],
   content: [required],
   params: [required],
   status: [required]
 })
 
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id', // 默认的主键ID
-  primaryTitle: '编号', // 默认显示的值
-  primaryType: null,
-  action: true,
-  actionWidth: '260',
-  columns: [
-    {
-      title: '模板编码',
-      field: 'code',
-      isSearch: true
-    },
-    {
-      title: '模板名称',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '模板标题',
-      field: 'title'
-    },
-    {
-      title: '模板内容',
-      field: 'content',
-      form: {
-        component: 'Editor',
-        colProps: {
-          span: 24
-        },
-        componentProps: {
-          valueHtml: ''
-        }
+// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
+const crudSchemas = reactive<CrudSchema[]>([
+  {
+    label: '模板编码',
+    field: 'code',
+    isSearch: true
+  },
+  {
+    label: '模板名称',
+    field: 'name',
+    isSearch: true
+  },
+  {
+    label: '模板标题',
+    field: 'title'
+  },
+  {
+    label: '模板内容',
+    field: 'content',
+    form: {
+      component: 'Editor',
+      componentProps: {
+        valueHtml: '',
+        height: 200
       }
+    }
+  },
+  {
+    label: '邮箱账号',
+    field: 'accountId',
+    width: '200px',
+    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+      return accounts.find((account) => account.id === cellValue)?.mail
     },
-    {
-      title: '邮箱账号',
-      field: 'accountId',
-      isSearch: true,
-      table: {
-        width: 200,
-        slots: {
-          default: 'accountId_default'
-        }
-      },
-      search: {
-        slots: {
-          default: 'accountId_search'
+    search: {
+      show: true,
+      component: 'Select',
+      api: () => accounts,
+      componentProps: {
+        optionsAlias: {
+          labelField: 'mail',
+          valueField: 'id'
         }
       }
     },
-    {
-      title: '发送人名称',
-      field: 'nickname'
-    },
-    {
-      title: '开启状态',
-      field: 'status',
-      isSearch: true,
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number'
-    },
-    {
-      title: '备注',
-      field: 'remark',
-      isTable: false
-    },
-    {
-      title: '创建时间',
-      field: 'createTime',
-      isForm: false,
-      formatter: 'formatDate',
-      table: {
-        width: 180
-      },
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
+    form: {
+      component: 'Select',
+      api: () => accounts,
+      componentProps: {
+        optionsAlias: {
+          labelField: 'mail',
+          valueField: 'id'
         }
       }
     }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
+  },
+  {
+    label: '发送人名称',
+    field: 'nickname'
+  },
+  {
+    label: '开启状态',
+    field: 'status',
+    isSearch: true,
+    dictType: DICT_TYPE.COMMON_STATUS,
+    dictClass: 'number'
+  },
+  {
+    label: '备注',
+    field: 'remark',
+    isTable: false
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    isForm: false,
+    formatter: dateFormatter,
+    search: {
+      show: true,
+      component: 'DatePicker',
+      componentProps: {
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        type: 'daterange',
+        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
+      }
+    }
+  },
+  {
+    label: '操作',
+    field: 'action',
+    isForm: false
+  }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)

+ 18 - 4
src/views/system/notice/index.vue

@@ -1,17 +1,29 @@
 <template>
   <content-wrap>
     <!-- 搜索工作栏 -->
-    <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="公告标题" prop="title">
         <el-input
           v-model="queryParams.title"
           placeholder="请输入公告标题"
           clearable
           @keyup.enter="handleQuery"
+          class="!w-240px"
         />
       </el-form-item>
       <el-form-item label="公告状态" prop="status">
-        <el-select v-model="queryParams.status" placeholder="请选择公告状态" clearable>
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择公告状态"
+          clearable
+          class="!w-240px"
+        >
           <el-option
             v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
             :key="parseInt(dict.value)"
@@ -32,9 +44,11 @@
         </el-button>
       </el-form-item>
     </el-form>
+  </content-wrap>
 
-    <!-- 列表 -->
-    <el-table v-loading="loading" :data="list" align="center">
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
       <el-table-column label="公告编号" align="center" prop="id" />
       <el-table-column label="公告标题" align="center" prop="title" />
       <el-table-column label="公告类型" align="center" prop="type">

+ 2 - 2
src/views/system/notify/template/index.vue

@@ -119,7 +119,7 @@ import { FormExpose } from '@/components/Form'
 // 业务相关的 import
 import { rules, allSchemas } from './template.data'
 import * as NotifyTemplateApi from '@/api/system/notify/template'
-import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
+import { getSimpleUserList, UserVO } from '@/api/system/user'
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
@@ -244,7 +244,7 @@ const sendTest = async () => {
 
 // ========== 初始化 ==========
 onMounted(() => {
-  getListSimpleUsersApi().then((data) => {
+  getSimpleUserList().then((data) => {
     userOption.value = data
   })
 })

+ 148 - 53
src/views/system/oauth2/token/index.vue

@@ -1,64 +1,159 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
-        <!-- 操作:登出 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.logout')"
-          v-hasPermi="['system:oauth2-token:delete']"
-          @click="handleForceLogout(row.id)"
+  <content-wrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="90px"
+    >
+      <el-form-item label="用户编号" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <XButton :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      </el-form-item>
+      <el-form-item label="用户类型" prop="userType">
+        <el-select
+          v-model="queryParams.userType"
+          placeholder="请选择用户类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="客户端编号" prop="clientId">
+        <el-input
+          v-model="queryParams.clientId"
+          placeholder="请输入客户端编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="访问令牌" align="center" prop="accessToken" width="300" />
+      <el-table-column label="刷新令牌" align="center" prop="refreshToken" width="300" />
+      <el-table-column label="用户编号" align="center" prop="userId" />
+      <el-table-column label="用户类型" align="center" prop="userType">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="过期时间"
+        align="center"
+        prop="expiresTime"
+        :formatter="dateFormatter"
+        width="180"
+      />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="danger"
+            @click="handleForceLogout(scope.row.id)"
+            v-hasPermi="['system:oauth2-token:delete']"
+          >
+            强退
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
 </template>
-<script setup lang="ts" name="Token">
-import { allSchemas } from './token.data'
-import * as TokenApi from '@/api/system/oauth2/token'
 
-const { t } = useI18n() // 国际化
+<script setup lang="ts" name="Oauth2AccessToken">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload }] = useXTable({
-  allSchemas: allSchemas,
-  topActionSlots: false,
-  getListApi: TokenApi.getAccessTokenPageApi
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  userId: null,
+  userType: null,
+  clientId: null
 })
+const queryFormRef = ref() // 搜索的表单
 
-// ========== 详情相关 ==========
-const detailData = ref() // 详情 Ref
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref(t('action.detail')) // 弹出层标题
-// 详情
-const handleDetail = async (row: TokenApi.OAuth2TokenVO) => {
-  // 设置数据
-  detailData.value = row
-  dialogVisible.value = true
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await OAuth2AccessTokenApi.getAccessTokenPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
 
-// 强退操作
-const handleForceLogout = (rowId: number) => {
-  message
-    .confirm('是否要强制退出用户')
-    .then(async () => {
-      await TokenApi.deleteAccessTokenApi(rowId)
-      message.success(t('common.success'))
-    })
-    .finally(async () => {
-      // 刷新列表
-      await reload()
-    })
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 强制退出操作 */
+const handleForceLogout = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.confirm('是否要强制退出用户')
+    // 发起删除
+    await OAuth2AccessTokenApi.deleteAccessToken(id)
+    message.success(t('common.success'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 0 - 48
src/views/system/oauth2/token/token.data.ts

@@ -1,48 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  action: true,
-  columns: [
-    {
-      title: '用户编号',
-      field: 'userId',
-      isSearch: true
-    },
-    {
-      title: '访问令牌',
-      field: 'accessToken'
-    },
-    {
-      title: '刷新令牌',
-      field: 'refreshToken'
-    },
-    {
-      title: '客户端编号',
-      field: 'clientId',
-      isSearch: true
-    },
-    {
-      title: '用户类型',
-      field: 'userType',
-      dictType: DICT_TYPE.USER_TYPE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false
-    },
-    {
-      title: '过期时间',
-      field: 'expiresTime',
-      formatter: 'formatDate',
-      isForm: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 80 - 0
src/views/system/operatelog/detail.vue

@@ -0,0 +1,80 @@
+<template>
+  <Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
+    <el-descriptions border :column="1">
+      <el-descriptions-item label="日志主键" min-width="120">
+        {{ detailData.id }}
+      </el-descriptions-item>
+      <el-descriptions-item label="链路追踪">
+        {{ detailData.traceId }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作人编号">
+        {{ detailData.userId }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作人名字">
+        {{ detailData.userNickname }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作人 IP">
+        {{ detailData.userIp }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作人 UA">
+        {{ detailData.userAgent }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作模块">
+        {{ detailData.module }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作名">
+        {{ detailData.name }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作内容" v-if="detailData.content">
+        {{ detailData.content }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作拓展参数" v-if="detailData.exts">
+        {{ detailData.exts }}
+      </el-descriptions-item>
+      <el-descriptions-item label="请求 URL">
+        {{ detailData.requestMethod }} {{ detailData.requestUrl }}
+      </el-descriptions-item>
+      <el-descriptions-item label="Java 方法名">
+        {{ detailData.javaMethod }}
+      </el-descriptions-item>
+      <el-descriptions-item label="Java 方法参数">
+        {{ detailData.javaMethodArgs }}
+      </el-descriptions-item>
+      <el-descriptions-item label="操作时间">
+        {{ formatDate(detailData.startTime, 'YYYY-MM-DD HH:mm:ss') }}
+      </el-descriptions-item>
+      <el-descriptions-item label="执行时长">{{ detailData.duration }} ms</el-descriptions-item>
+      <el-descriptions-item label="操作结果">
+        <div v-if="detailData.resultCode === 0">正常</div>
+        <div v-else>失败({{ detailData.resultCode }})</div>
+      </el-descriptions-item>
+      <el-descriptions-item label="操作结果" v-if="detailData.resultCode === 0">
+        {{ detailData.resultData }}
+      </el-descriptions-item>
+      <el-descriptions-item label="失败提示" v-if="detailData.resultCode > 0">
+        {{ detailData.resultMsg }}
+      </el-descriptions-item>
+    </el-descriptions>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { formatDate } from '@/utils/formatTime'
+import * as OperateLogApi from '@/api/system/operatelog'
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const detailLoading = ref(false) // 表单的加载中
+const detailData = ref() // 详情数据
+
+/** 打开弹窗 */
+const openModal = async (data: OperateLogApi.OperateLogVO) => {
+  modelVisible.value = true
+  // 设置数据
+  detailLoading.value = true
+  try {
+    detailData.value = data
+  } finally {
+    detailLoading.value = false
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+</script>

+ 198 - 57
src/views/system/operatelog/index.vue

@@ -1,67 +1,208 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['system:operate-log:export']"
-          @click="exportList('操作日志.xls')"
+  <content-wrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="系统模块" prop="module">
+        <el-input
+          v-model="queryParams.module"
+          placeholder="请输入系统模块"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
         />
-      </template>
-      <template #duration="{ row }">
-        <span>{{ row.duration + 'ms' }}</span>
-      </template>
-      <template #resultCode="{ row }">
-        <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:详情 -->
-        <XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <!-- 弹窗 -->
-  <XModal id="postModel" v-model="dialogVisible" :title="t('action.detail')">
-    <!-- 对话框(详情) -->
-    <Descriptions :schema="allSchemas.detailSchema" :data="detailData">
-      <template #resultCode="{ row }">
-        <span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
-      </template>
-      <template #duration="{ row }">
-        <span>{{ row.duration + 'ms' }}</span>
-      </template>
-    </Descriptions>
-    <template #footer>
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      </el-form-item>
+      <el-form-item label="操作人员" prop="userNickname">
+        <el-input
+          v-model="queryParams.userNickname"
+          placeholder="请输入操作人员"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="操作类型" prop="type">
+        <el-select
+          v-model="queryParams.type"
+          placeholder="请选择操作类型"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="操作状态" prop="success">
+        <el-select
+          v-model="queryParams.success"
+          placeholder="请选择操作状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option :key="true" label="成功" :value="true" />
+          <el-option :key="false" label="失败" :value="false" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="操作时间" prop="startTime">
+        <el-date-picker
+          v-model="queryParams.startTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['infra:config:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="日志编号" align="center" prop="id" />
+      <el-table-column label="操作模块" align="center" prop="module" width="180" />
+      <el-table-column label="操作名" align="center" prop="name" width="180" />
+      <el-table-column label="操作类型" align="center" prop="type">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="scope.row.type" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作人" align="center" prop="userNickname" />
+      <el-table-column label="操作结果" align="center" prop="status">
+        <template #default="scope">
+          <span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="操作时间"
+        align="center"
+        prop="startTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="执行时长" align="center" prop="startTime">
+        <template #default="scope">
+          <span>{{ scope.row.duration }} ms</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal(scope.row)"
+            v-hasPermi="['infra:config:query']"
+          >
+            详情
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:详情 -->
+  <operate-log-detail ref="modalRef" />
 </template>
 <script setup lang="ts" name="OperateLog">
-// 业务相关的 import
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
 import * as OperateLogApi from '@/api/system/operatelog'
-import { allSchemas } from './operatelog.data'
+import OperateLogDetail from './detail.vue'
+const message = useMessage() // 消息弹窗
 
-const { t } = useI18n() // 国际化
-// 列表相关的变量
-const [registerTable, { exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: OperateLogApi.getOperateLogPageApi,
-  exportListApi: OperateLogApi.exportOperateLogApi
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  module: undefined,
+  userNickname: undefined,
+  type: undefined,
+  success: undefined,
+  startTime: []
 })
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await OperateLogApi.getOperateLogPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
 
-// 弹窗相关的变量
-const dialogVisible = ref(false) // 是否显示弹出层
-const actionLoading = ref(false) // 按钮 Loading
-const detailData = ref() // 详情 Ref
-// 详情
-const handleDetail = (row: OperateLogApi.OperateLogVO) => {
-  // 设置数据
-  detailData.value = row
-  dialogVisible.value = true
+/** 详情操作 */
+const modalRef = ref()
+const openModal = (data: OperateLogApi.OperateLogVO) => {
+  modalRef.value.openModal(data)
 }
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await OperateLogApi.exportOperateLog(queryParams)
+    download.excel(data, '操作日志.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 0 - 106
src/views/system/operatelog/operatelog.data.ts

@@ -1,106 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-const { t } = useI18n() // 国际化
-
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '日志编号',
-  action: true,
-  actionWidth: '80px',
-  columns: [
-    {
-      title: '操作模块',
-      field: 'module',
-      isSearch: true
-    },
-    {
-      title: '操作名',
-      field: 'name'
-    },
-    {
-      title: '操作类型',
-      field: 'type',
-      dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '请求方法名',
-      field: 'requestMethod',
-      isTable: false
-    },
-    {
-      title: '请求地址',
-      field: 'requestUrl',
-      isTable: false
-    },
-    {
-      title: '操作人员',
-      field: 'userNickname',
-      isSearch: true
-    },
-    {
-      title: '操作明细',
-      field: 'content',
-      isTable: false
-    },
-    {
-      title: '用户 IP',
-      field: 'userIp',
-      isTable: false
-    },
-    {
-      title: 'userAgent',
-      field: 'userAgent'
-    },
-    {
-      title: '操作结果',
-      field: 'resultCode',
-      table: {
-        slots: {
-          default: 'resultCode'
-        }
-      }
-    },
-    {
-      title: '操作结果',
-      field: 'success',
-      isTable: false,
-      isDetail: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: '$select',
-          props: { placeholder: t('common.selectText') },
-          options: [
-            { label: '成功', value: 'true' },
-            { label: '失败', value: 'false' }
-          ]
-        }
-      }
-    },
-    {
-      title: '操作日期',
-      field: 'startTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '执行时长',
-      field: 'duration',
-      table: {
-        slots: {
-          default: 'duration'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 122 - 0
src/views/system/post/PostForm.vue

@@ -0,0 +1,122 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible" width="800">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="岗位标题" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入岗位标题" />
+      </el-form-item>
+      <el-form-item label="岗位编码" prop="code">
+        <el-input :model-value="formData.code" placeholder="请输入岗位编码" height="150px" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="formData.status" placeholder="请选择状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="formData.remark" type="textarea" placeholder="请输备注" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as PostApi from '@/api/system/post'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  code: '',
+  sort: undefined,
+  status: CommonStatusEnum.ENABLE,
+  remark: ''
+})
+const formRules = reactive({
+  name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],
+  code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],
+  status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],
+  remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await PostApi.getPost(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as PostApi.PostVO
+    if (formType.value === 'create') {
+      await PostApi.createPost(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await PostApi.updatePost(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    code: '',
+    sort: undefined,
+    status: CommonStatusEnum.ENABLE,
+    remark: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 0 - 91
src/views/system/post/form.vue

@@ -1,91 +0,0 @@
-<template>
-  <!-- 弹窗 -->
-  <XModal :title="modelTitle" :loading="modelLoading" v-model="modelVisible">
-    <!-- 表单:添加/修改 -->
-    <Form
-      ref="formRef"
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-    />
-    <!-- 表单:详情 -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
-      />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
-    </template>
-  </XModal>
-</template>
-<script setup lang="ts">
-import type { FormExpose } from '@/components/Form'
-import * as PostApi from '@/api/system/post'
-import { rules, allSchemas } from './post.data'
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-// 弹窗相关的变量
-const modelVisible = ref(false) // 是否显示弹出层
-const modelTitle = ref('') // 弹出层标题
-const modelLoading = ref(false) // 弹出层loading
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-
-// 打开弹窗
-const openModal = async (type: string, id?: number) => {
-  modelVisible.value = true
-  modelLoading.value = true
-  modelTitle.value = t('action.' + type)
-  actionType.value = type
-  // 设置数据
-  if (id) {
-    const res = await PostApi.getPostApi(id)
-    if (type === 'update') {
-      unref(formRef)?.setValues(res)
-    } else if (type === 'detail') {
-      detailData.value = res
-    }
-  }
-  modelLoading.value = false
-}
-defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
-
-// 提交新增/修改的表单
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  const valid = await elForm.validate()
-  if (!valid) return
-  // 提交请求
-  actionLoading.value = true
-  try {
-    const data = unref(formRef)?.formModel as PostApi.PostVO
-    if (actionType.value === 'create') {
-      await PostApi.createPostApi(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await PostApi.updatePostApi(data)
-      message.success(t('common.updateSuccess'))
-    }
-    modelVisible.value = false
-    emit('success')
-  } finally {
-    actionLoading.value = false
-  }
-}
-</script>

+ 183 - 56
src/views/system/post/index.vue

@@ -1,71 +1,198 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:post:create']"
-          @click="openModal('create')"
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="岗位名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入岗位名称"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:导出 -->
-        <XButton
+      </el-form-item>
+      <el-form-item label="岗位编码" prop="code">
+        <el-input
+          v-model="queryParams.code"
+          placeholder="请输入岗位编码"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
           type="primary"
+          @click="openModal('create')"
+          v-hasPermi="['system:notice:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
           plain
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['system:post:export']"
-          @click="exportList('岗位列表.xls')"
-        />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:post:update']"
-          @click="openModal('update', row?.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:post:query']"
-          @click="openModal('detail', row?.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.delete')"
-          v-hasPermi="['system:post:delete']"
-          @click="deleteData(row?.id)"
-        />
-      </template>
-    </XTable>
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['infra:config:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="岗位编号" align="center" prop="id" />
+      <el-table-column label="岗位名称" align="center" prop="name" />
+      <el-table-column label="岗位编码" align="center" prop="code" />
+      <el-table-column label="岗位顺序" align="center" prop="sort" />
+      <el-table-column label="岗位备注" align="center" prop="remark" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:post:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:post:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
   </ContentWrap>
-  <!-- 表单弹窗:添加/修改/详情 -->
-  <PostForm ref="modalRef" @success="reload()" />
+
+  <!-- 表单弹窗:添加/修改 -->
+  <PostForm ref="formRef" @success="getList" />
 </template>
-<script setup lang="ts" name="Post">
+<script setup lang="tsx">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
 import * as PostApi from '@/api/system/post'
-import { allSchemas } from './post.data'
-import PostForm from './form.vue'
+import PostForm from './PostForm.vue'
+
+const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
-// 列表相关的变量
-const [registerTable, { reload, deleteData, exportList }] = useXTable({
-  allSchemas: allSchemas, // 列表配置
-  getListApi: PostApi.getPostPageApi, // 加载列表的 API
-  deleteApi: PostApi.deletePostApi, // 删除数据的 API
-  exportListApi: PostApi.exportPostApi // 导出数据的 API
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  code: '',
+  name: '',
+  status: undefined
 })
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
 
-// 表单相关的变量
-const modalRef = ref()
+/** 查询岗位列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await PostApi.getPostPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
 const openModal = (type: string, id?: number) => {
-  modalRef.value.openModal(type, id)
+  formRef.value.openModal(type, id)
 }
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await PostApi.deletePost(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await PostApi.exportPost(queryParams)
+    download.excel(data, '岗位列表.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 0 - 58
src/views/system/post/post.data.ts

@@ -1,58 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  code: [required],
-  sort: [required]
-})
-
-// 增删改查 CrudSchema 配置
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '岗位编号',
-  action: true,
-  columns: [
-    {
-      title: '岗位名称',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '岗位编码',
-      field: 'code',
-      isSearch: true
-    },
-    {
-      title: '岗位顺序',
-      field: 'sort',
-      form: {
-        component: 'InputNumber'
-      }
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '备注',
-      field: 'remark',
-      isTable: false
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      table: {
-        width: 180
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 130 - 0
src/views/system/sensitiveWord/form.vue

@@ -0,0 +1,130 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="敏感词" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入敏感词" />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注" prop="description">
+        <el-input v-model="formData.description" placeholder="请输入内容" />
+      </el-form-item>
+      <el-form-item label="标签" prop="tags">
+        <el-select
+          v-model="formData.tags"
+          multiple
+          filterable
+          allow-create
+          placeholder="请选择文章标签"
+          style="width: 380px"
+        >
+          <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as SensitiveWordApi from '@/api/system/sensitiveWord'
+import { CommonStatusEnum } from '@/utils/constants'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  status: CommonStatusEnum.ENABLE,
+  description: '',
+  tags: []
+})
+const formRules = reactive({
+  name: [{ required: true, message: '敏感词不能为空', trigger: 'blur' }],
+  tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const tags = ref([]) // todo @blue-syd:在 openModal 里加载下
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await SensitiveWordApi.getSensitiveWordApi(id)
+      console.log(formData.value)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO
+    if (formType.value === 'create') {
+      await SensitiveWordApi.createSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀
+      message.success(t('common.createSuccess'))
+    } else {
+      await SensitiveWordApi.updateSensitiveWordApi(data) // TODO @blue-syd:去掉 API 后缀
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    status: CommonStatusEnum.ENABLE,
+    description: '',
+    tags: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 198 - 164
src/views/system/sensitiveWord/index.vue

@@ -1,191 +1,225 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
+  <!-- 搜索 -->
+  <content-wrap>
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="敏感词" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入敏感词"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="标签" prop="tag">
+        <el-select
+          v-model="queryParams.tag"
+          placeholder="请选择标签"
+          clearable
+          @keyup.enter="handleQuery"
+        >
+          <el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
           type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
+          @click="openModal('create')"
           v-hasPermi="['system:sensitive-word:create']"
-          @click="handleCreate()"
-        />
-        <!-- 操作:导出 -->
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
           v-hasPermi="['system:sensitive-word:export']"
-          @click="exportList('敏感词数据.xls')"
-        />
-      </template>
-      <template #tags_default="{ row }">
-        <el-tag
-          :disable-transitions="true"
-          :key="index"
-          v-for="(tag, index) in row.tags"
-          :index="index"
         >
-          {{ tag }}
-        </el-tag>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:sensitive-word:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:sensitive-word:update']"
-          @click="handleDetail(row.id)"
-        />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['system:sensitive-word:delete']"
-          @click="deleteData(row.id)"
-        />
-      </template>
-    </XTable>
-  </ContentWrap>
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
 
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    >
-      <template #tags="form">
-        <el-select v-model="form['tags']" multiple placeholder="请选择">
-          <el-option v-for="item in tagsOptions" :key="item" :label="item" :value="item" />
-        </el-select>
-      </template>
-    </Form>
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    >
-      <template #tags="{ row }">
-        <el-tag
-          :disable-transitions="true"
-          :key="index"
-          v-for="(tag, index) in row.tags"
-          :index="index"
-        >
-          {{ tag }}
-        </el-tag>
-      </template>
-    </Descriptions>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="敏感词" align="center" prop="name" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="描述" align="center" prop="description" />
+      <el-table-column label="标签" align="center" prop="tags">
+        <template #default="scope">
+          <el-tag
+            :disable-transitions="true"
+            :key="index"
+            v-for="(tag, index) in scope.row.tags"
+            :index="index"
+            class="mr-5px"
+          >
+            {{ tag }}
+          </el-tag>
+          &nbsp; &nbsp;
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['infra:config:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['infra:config:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <config-form ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="SensitiveWord">
-import type { FormExpose } from '@/components/Form'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
 import * as SensitiveWordApi from '@/api/system/sensitiveWord'
-import { rules, allSchemas } from './sensitiveWord.data'
-
-const { t } = useI18n() // 国际化
+import ConfigForm from './form.vue' // TODO @blue-syd:组件名不对
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData, exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: SensitiveWordApi.getSensitiveWordPageApi,
-  deleteApi: SensitiveWordApi.deleteSensitiveWordApi,
-  exportListApi: SensitiveWordApi.exportSensitiveWordApi
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  tag: undefined,
+  status: undefined,
+  createTime: []
 })
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+const tags = ref([])
 
-// 获取标签
-const tagsOptions = ref()
-const getTags = async () => {
-  const res = await SensitiveWordApi.getSensitiveWordTagsApi()
-  tagsOptions.value = res
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
 }
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await SensitiveWordApi.getSensitiveWordApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await SensitiveWordApi.getSensitiveWordApi(rowId)
-  detailData.value = res
+// TODO @blue-syd:还少一个【测试】按钮的功能,参见 http://dashboard.yudao.iocoder.cn/system/sensitive-word
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await SensitiveWordApi.deleteSensitiveWordApi(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as SensitiveWordApi.SensitiveWordVO
-        if (actionType.value === 'create') {
-          await SensitiveWordApi.createSensitiveWordApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await SensitiveWordApi.updateSensitiveWordApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) // TODO @blue-syd:去掉 API 后缀哈
+    download.excel(data, '敏感词.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 获得 Tag 标签列表 */
+const getTags = async () => {
+  tags.value = await SensitiveWordApi.getSensitiveWordTagsApi() // TODO @blue-syd:去掉 API 后缀哈
 }
 
-// ========== 初始化 ==========
-onMounted(async () => {
-  await getTags()
+/** 初始化 **/
+onMounted(() => {
+  getTags()
+  getList()
 })
 </script>

+ 0 - 74
src/views/system/sensitiveWord/sensitiveWord.data.ts

@@ -1,74 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-const { t } = useI18n() // 国际化
-
-// 表单校验
-export const rules = reactive({
-  name: [required],
-  tags: [required]
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '敏感词编号',
-  action: true,
-  columns: [
-    {
-      title: '敏感词',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '标签',
-      field: 'tag',
-      isTable: false,
-      isForm: false,
-      isDetail: false,
-      isSearch: true
-    },
-    {
-      title: '标签',
-      field: 'tags',
-      table: {
-        slots: {
-          default: 'tags_default'
-        }
-      }
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '描述',
-      field: 'description',
-      form: {
-        component: 'Input',
-        componentProps: {
-          type: 'textarea',
-          rows: 4
-        },
-        colProps: {
-          span: 24
-        }
-      }
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 137 - 0
src/views/system/sms/smsChannel/form.vue

@@ -0,0 +1,137 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading">
+      <el-form-item label="短信签名" prop="signature">
+        <el-input v-model="form.signature" placeholder="请输入短信签名" />
+      </el-form-item>
+      <el-form-item label="渠道编码" prop="code">
+        <el-select v-model="form.code" placeholder="请选择渠道编码" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="启用状态">
+        <el-radio-group v-model="form.status">
+          <el-radio
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="parseInt(dict.value)"
+            >{{ dict.label }}</el-radio
+          >
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" placeholder="请输入备注" />
+      </el-form-item>
+      <el-form-item label="短信 API 的账号" prop="apiKey">
+        <el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
+      </el-form-item>
+      <el-form-item label="短信 API 的密钥" prop="apiSecret">
+        <el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" />
+      </el-form-item>
+      <el-form-item label="短信发送回调 URL" prop="callbackUrl">
+        <el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import * as SmsChannelApi from '@/api/system/sms/smsChannel'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const form = ref({
+  id: undefined,
+  signature: '',
+  code: '',
+  status: '',
+  remark: '',
+  apiKey: '',
+  apiSecret: '',
+  callbackUrl: ''
+})
+const rules = reactive({
+  signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }],
+  code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }],
+  apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      form.value = await SmsChannelApi.getSmsChannelApi(id)
+      console.log(form)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO
+    if (formType.value === 'create') {
+      await SmsChannelApi.createSmsChannelApi(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await SmsChannelApi.updateSmsChannelApi(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  form.value = {
+    id: undefined,
+    signature: '',
+    code: '',
+    status: '',
+    remark: '',
+    apiKey: '',
+    apiSecret: '',
+    callbackUrl: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 198 - 120
src/views/system/sms/smsChannel/index.vue

@@ -1,147 +1,225 @@
 <template>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <!-- 操作:新增 -->
-      <template #toolbar_buttons>
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:sms-channel:create']"
-          @click="handleCreate()"
-        />
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:sms-channel:update']"
-          @click="handleUpdate(row.id)"
-        />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:sms-channel:query']"
-          @click="handleDetail(row.id)"
+    <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+      <el-form-item label="短信签名" prop="signature">
+        <el-input
+          v-model="queryParams.signature"
+          placeholder="请输入短信签名"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['system:sms-channel:delete']"
-          @click="deleteData(row.id)"
+      </el-form-item>
+      <el-form-item label="启用状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="parseInt(dict.value)"
+            :label="dict.label"
+            :value="parseInt(dict.value)"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
         />
-      </template>
-    </XTable>
-  </ContentWrap>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          @click="openModal('create')"
+          v-hasPermi="['system:sms-channel:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
+        >
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:sms-channel:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出</el-button
+        >
+      </el-form-item>
+    </el-form>
 
-  <XModal id="smsChannel" v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    />
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    />
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="编号" align="center" prop="id" />
+      <el-table-column label="短信签名" align="center" prop="signature" />
+      <el-table-column label="渠道编码" align="center" prop="code">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.code" />
+        </template>
+      </el-table-column>
+      <el-table-column label="启用状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
+      <el-table-column
+        label="短信 API 的账号"
+        align="center"
+        prop="apiKey"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        label="短信 API 的密钥"
+        align="center"
+        prop="apiSecret"
+        :show-overflow-tooltip="true"
+      />
+      <el-table-column
+        label="短信发送回调 URL"
+        align="center"
+        prop="callbackUrl"
+        :show-overflow-tooltip="true"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:sms-channel:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:sms-channel:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+  <!-- 表单弹窗:添加/修改 -->
+  <SmsChannelForm ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="SmsChannel">
-import type { FormExpose } from '@/components/Form'
 // 业务相关的 import
 import * as SmsChannelApi from '@/api/system/sms/smsChannel'
-import { rules, allSchemas } from './sms.channel.data'
+//格式化时间
+import { dateFormatter } from '@/utils/formatTime'
+//字典
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+//表单弹窗:添加/修改
+import SmsChannelForm from './form.vue'
+//下载
+// import download from '@/utils/download'
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
-// 列表相关的变量
-const [registerTable, { reload, deleteData }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: SmsChannelApi.getSmsChannelPageApi,
-  deleteApi: SmsChannelApi.deleteSmsChannelApi
+// 列表的加载中
+const loading = ref(true)
+//搜索的表单
+const queryFormRef = ref()
+// 列表的总页数
+const total = ref(0)
+// 列表的数据
+const list = ref([])
+//导出的加载中
+const exportLoading = ref(false)
+//查询参数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  signature: undefined,
+  status: undefined,
+  createTime: []
 })
 
-// 弹窗相关的变量
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const actionType = ref('') // 操作按钮的类型
-const actionLoading = ref(false) // 按钮 Loading
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  // 执行查询
+  try {
+    const data = await SmsChannelApi.getSmsChannelPageApi(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 新增操作
-const handleCreate = () => {
-  setDialogTile('create')
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  // 设置数据
-  const res = await SmsChannelApi.getSmsChannelApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  setDialogTile('detail')
-  const res = await SmsChannelApi.getSmsChannelApi(rowId)
-  detailData.value = res
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    await message.info('该功能目前不支持')
+    //导出功能先不考虑
+    // const data = await SmsChannelApi.exportSmsChanelApi(queryParams)
+    // download.excel(data, '短信渠道.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO
-        if (actionType.value === 'create') {
-          await SmsChannelApi.createSmsChannelApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await SmsChannelApi.updateSmsChannelApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await SmsChannelApi.deleteSmsChannelApi(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>

+ 0 - 76
src/views/system/sms/smsChannel/sms.channel.data.ts

@@ -1,76 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
-
-const { t } = useI18n() // 国际化
-
-const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
-
-// 表单校验
-export const rules = reactive({
-  signature: [required],
-  code: [required],
-  apiKey: [required],
-  status: [required]
-})
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: 'id',
-  primaryTitle: '渠道编号',
-  action: true,
-  columns: [
-    {
-      title: '短信签名',
-      field: 'signature',
-      isSearch: true
-    },
-    {
-      title: '渠道编码',
-      field: 'code',
-      // dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
-      // dictClass: 'string',
-      isSearch: true,
-      form: {
-        component: 'Select',
-        componentProps: {
-          options: authorizedGrantOptions,
-          multiple: false,
-          filterable: true
-        }
-      }
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.COMMON_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '短信 API 的账号',
-      field: 'apiKey'
-    },
-    {
-      title: '短信 API 的密钥',
-      field: 'apiSecret'
-    },
-    {
-      title: '短信发送回调 URL',
-      field: 'callbackUrl'
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      isForm: false,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 204 - 0
src/views/system/tenant/form.vue

@@ -0,0 +1,204 @@
+<template>
+  <Dialog :title="modelTitle" v-model="modelVisible" width="50%">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-row>
+        <el-col :span="10">
+          <el-form-item label="租户名" prop="name">
+            <el-input v-model="formData.name" placeholder="请输入租户名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="10" :offset="2">
+          <el-form-item label="租户套餐" prop="packageId">
+            <el-select v-model="formData.packageId" placeholder="请选择租户套餐" clearable>
+              <el-option
+                v-for="item in packageList"
+                :key="item.id"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="10">
+          <el-form-item label="联系人" prop="contactName">
+            <el-input v-model="formData.contactName" placeholder="请输入联系人" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="10" :offset="2">
+          <el-form-item label="联系手机" prop="contactMobile">
+            <el-input v-model="formData.contactMobile" placeholder="请输入联系手机" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="10">
+          <el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
+            <el-input v-model="formData.username" placeholder="请输入用户名称" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="10" :offset="2">
+          <el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
+            <el-input
+              v-model="formData.password"
+              placeholder="请输入用户密码"
+              type="password"
+              show-password
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="10">
+          <el-form-item label="账号额度" prop="accountCount">
+            <el-input-number
+              v-model="formData.accountCount"
+              placeholder="请输入账号额度"
+              controls-position="right"
+              :min="0"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="10" :offset="2">
+          <el-form-item label="过期时间" prop="expireTime">
+            <el-date-picker
+              clearable
+              v-model="formData.expireTime"
+              type="date"
+              value-format="x"
+              placeholder="请选择过期时间"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="10">
+          <el-form-item label="绑定域名" prop="domain">
+            <el-input v-model="formData.domain" placeholder="请输入绑定域名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="10" :offset="2">
+          <el-form-item label="租户状态" prop="status">
+            <el-radio-group v-model="formData.status">
+              <el-radio
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.value"
+                >{{ dict.label }}
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+        <el-button @click="modelVisible = false">取 消</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as TenantApi from '@/api/system/tenant'
+import { CommonStatusEnum } from '@/utils/constants'
+import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage'
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const modelVisible = ref(false) // 弹窗的是否展示
+const modelTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: undefined,
+  packageId: undefined,
+  contactName: undefined,
+  contactMobile: undefined,
+  accountCount: undefined,
+  expireTime: undefined,
+  domain: undefined,
+  status: CommonStatusEnum.ENABLE
+})
+const formRules = reactive({
+  name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }],
+  packageId: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }],
+  contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
+  accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
+  expireTime: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }],
+  domain: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const packageList = ref([]) // 租户套餐
+
+/** 打开弹窗 */
+const openModal = async (type: string, id?: number) => {
+  modelVisible.value = true
+  modelTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await TenantApi.getTenantApi(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  packageList.value = await getTenantPackageListApi()
+}
+defineExpose({ openModal }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as TenantApi.TenantVO
+    if (formType.value === 'create') {
+      await TenantApi.createTenantApi(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await TenantApi.updateTenantApi(data)
+      message.success(t('common.updateSuccess'))
+    }
+    modelVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: undefined,
+    packageId: undefined,
+    contactName: undefined,
+    contactMobile: undefined,
+    accountCount: undefined,
+    expireTime: undefined,
+    domain: undefined,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 230 - 172
src/views/system/tenant/index.vue

@@ -1,197 +1,255 @@
 <template>
-  <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
-          type="primary"
-          preIcon="ep:zoom-in"
-          :title="t('action.add')"
-          v-hasPermi="['system:tenant:create']"
-          @click="handleCreate()"
-        />
-        <XButton
-          type="warning"
-          preIcon="ep:download"
-          :title="t('action.export')"
-          v-hasPermi="['system:tenant:export']"
-          @click="exportList('租户列表.xls')"
+  <!-- 搜索 -->
+  <content-wrap>
+    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="租户名" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入租户名"
+          clearable
+          @keyup.enter="handleQuery"
         />
-      </template>
-      <template #accountCount_default="{ row }">
-        <el-tag> {{ row.accountCount }} </el-tag>
-      </template>
-      <template #packageId_default="{ row }">
-        <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
-        <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作:修改 -->
-        <XTextButton
-          preIcon="ep:edit"
-          :title="t('action.edit')"
-          v-hasPermi="['system:tenant:update']"
-          @click="handleUpdate(row.id)"
+      </el-form-item>
+      <el-form-item label="联系人" prop="contactName">
+        <el-input
+          v-model="queryParams.contactName"
+          placeholder="请输入联系人"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:详情 -->
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['system:tenant:update']"
-          @click="handleDetail(row.id)"
+      </el-form-item>
+      <el-form-item label="联系手机" prop="contactMobile">
+        <el-input
+          v-model="queryParams.contactMobile"
+          placeholder="请输入联系手机"
+          clearable
+          @keyup.enter="handleQuery"
         />
-        <!-- 操作:删除 -->
-        <XTextButton
-          preIcon="ep:delete"
-          :title="t('action.del')"
-          v-hasPermi="['system:tenant:delete']"
-          @click="deleteData(row.id)"
+      </el-form-item>
+      <el-form-item label="租户状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择租户状态" clearable>
+          <el-option
+            v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
         />
-      </template>
-    </XTable>
-  </ContentWrap>
-  <XModal v-model="dialogVisible" :title="dialogTitle">
-    <!-- 对话框(添加 / 修改) -->
-    <Form
-      v-if="['create', 'update'].includes(actionType)"
-      :schema="allSchemas.formSchema"
-      :rules="rules"
-      ref="formRef"
-    />
-    <!-- 对话框(详情) -->
-    <Descriptions
-      v-if="actionType === 'detail'"
-      :schema="allSchemas.detailSchema"
-      :data="detailData"
-    >
-      <template #packageId="{ row }">
-        <el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
-        <el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
-      </template>
-    </Descriptions>
-    <!-- 操作按钮 -->
-    <template #footer>
-      <!-- 按钮:保存 -->
-      <XButton
-        v-if="['create', 'update'].includes(actionType)"
-        type="primary"
-        :title="t('action.save')"
-        :loading="actionLoading"
-        @click="submitForm()"
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />
+          重置
+        </el-button>
+        <el-button
+          type="primary"
+          @click="openModal('create')"
+          v-hasPermi="['system:tenant:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" />
+          新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['system:tenant:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" />
+          导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </content-wrap>
+
+  <!-- 列表 -->
+  <content-wrap>
+    <el-table v-loading="loading" :data="list" align="center">
+      <el-table-column label="租户编号" align="center" prop="id" />
+      <el-table-column label="租户名" align="center" prop="name" />
+      <el-table-column label="租户套餐" align="center" prop="packageId">
+        <template #default="scope">
+          <el-tag v-if="scope.row.packageId === 0" type="danger">系统租户</el-tag>
+          <template v-else v-for="item in packageList">
+            <el-tag type="success" :key="item.id" v-if="item.id === scope.row.packageId"
+              >{{ item.name }}
+            </el-tag>
+          </template>
+        </template>
+      </el-table-column>
+      <el-table-column label="联系人" align="center" prop="contactName" />
+      <el-table-column label="联系手机" align="center" prop="contactMobile" />
+      <el-table-column label="账号额度" align="center" prop="accountCount">
+        <template #default="scope">
+          <el-tag>{{ scope.row.accountCount }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="过期时间"
+        align="center"
+        prop="expireTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="绑定域名" align="center" prop="domain" width="180" />
+      <el-table-column label="租户状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
       />
-      <!-- 按钮:关闭 -->
-      <XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
-    </template>
-  </XModal>
+      <el-table-column label="操作" align="center" min-width="110" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openModal('update', scope.row.id)"
+            v-hasPermi="['system:tenant:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:tenant:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </content-wrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <tenant-form ref="modalRef" @success="getList" />
 </template>
 <script setup lang="ts" name="Tenant">
-import type { FormExpose } from '@/components/Form'
+import { DICT_TYPE, getDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
 import * as TenantApi from '@/api/system/tenant'
-import { rules, allSchemas, tenantPackageOption } from './tenant.data'
+import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage'
+import TenantForm from './form.vue'
+import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
+import DictTag from '@/components/DictTag/src/DictTag.vue'
 
-const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
-// 列表相关的变量
-const [registerTable, { reload, deleteData, exportList }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: TenantApi.getTenantPageApi,
-  deleteApi: TenantApi.deleteTenantApi,
-  exportListApi: TenantApi.exportTenantApi
-})
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const packageList = ref([]) //租户套餐列表
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  contactName: undefined,
+  contactMobile: undefined,
+  status: undefined,
+  createTime: []
+}) //查询参数对象
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
 
-const actionLoading = ref(false) // 遮罩层
-const actionType = ref('') // 操作按钮的类型
-const dialogVisible = ref(false) // 是否显示弹出层
-const dialogTitle = ref('edit') // 弹出层标题
-const formRef = ref<FormExpose>() // 表单 Ref
-const detailData = ref() // 详情 Ref
-const getPackageName = (packageId: number) => {
-  for (let item of tenantPackageOption) {
-    if (item.value === packageId) {
-      return item.label
-    }
+/** 查询参数列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await TenantApi.getTenantPageApi(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
   }
-  return '未知套餐'
 }
 
-// 设置标题
-const setDialogTile = (type: string) => {
-  dialogTitle.value = t('action.' + type)
-  actionType.value = type
-  dialogVisible.value = true
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
 }
 
-// 新增操作
-const handleCreate = async () => {
-  // 重置表单
-  setDialogTile('create')
-  await nextTick()
-  console.log(allSchemas.formSchema, 'allSchemas.formSchema')
-  if (allSchemas.formSchema[4].field !== 'username') {
-    unref(formRef)?.addSchema(
-      {
-        field: 'username',
-        label: '用户名称',
-        component: 'Input'
-      },
-      0
-    )
-    unref(formRef)?.addSchema(
-      {
-        field: 'password',
-        label: '用户密码',
-        component: 'InputPassword'
-      },
-      1
-    )
-  }
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
-// 修改操作
-const handleUpdate = async (rowId: number) => {
-  setDialogTile('update')
-  await nextTick()
-  unref(formRef)?.delSchema('username')
-  unref(formRef)?.delSchema('password')
-  // 设置数据
-  const res = await TenantApi.getTenantApi(rowId)
-  unref(formRef)?.setValues(res)
+/** 添加/修改操作 */
+const modalRef = ref()
+const openModal = (type: string, id?: number) => {
+  modalRef.value.openModal(type, id)
 }
 
-// 详情操作
-const handleDetail = async (rowId: number) => {
-  // 设置数据
-  const res = await TenantApi.getTenantApi(rowId)
-  detailData.value = res
-  setDialogTile('detail')
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await TenantApi.deleteTenantApi(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-// 提交按钮
-const submitForm = async () => {
-  const elForm = unref(formRef)?.getElFormRef()
-  if (!elForm) return
-  elForm.validate(async (valid) => {
-    if (valid) {
-      actionLoading.value = true
-      // 提交请求
-      try {
-        const data = unref(formRef)?.formModel as TenantApi.TenantVO
-        if (actionType.value === 'create') {
-          await TenantApi.createTenantApi(data)
-          message.success(t('common.createSuccess'))
-        } else {
-          await TenantApi.updateTenantApi(data)
-          message.success(t('common.updateSuccess'))
-        }
-        // 操作成功,重新加载列表
-        dialogVisible.value = false
-      } finally {
-        actionLoading.value = false
-        // 刷新列表
-        await reload()
-      }
-    }
-  })
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await TenantApi.exportTenantApi(queryParams)
+    download.excel(data, '参数配置.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/**获取租户套餐**/
+const getTenantPackageList = async () => {
+  const data = await getTenantPackageListApi()
+  packageList.value = data
 }
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+  getTenantPackageList()
+})
 </script>

+ 2 - 2
src/views/system/user/index.vue

@@ -273,7 +273,7 @@ import { rules, allSchemas } from './user.data'
 import * as UserApi from '@/api/system/user'
 import { listSimpleDeptApi } from '@/api/system/dept'
 import { listSimpleRolesApi } from '@/api/system/role'
-import { listSimplePostsApi, PostVO } from '@/api/system/post'
+import { getSimplePostList, PostVO } from '@/api/system/post'
 import {
   aassignUserRoleApi,
   listUserRolesApi,
@@ -329,7 +329,7 @@ const postOptions = ref<PostVO[]>([]) //岗位列表
 
 // 获取岗位列表
 const getPostOptions = async () => {
-  const res = await listSimplePostsApi()
+  const res = await getSimplePostList()
   postOptions.value.push(...res)
 }
 const dataFormater = (val) => {