Преглед изворни кода

Merge branch 'dev' of gitee.com:yudaocode/yudao-ui-admin-vue3 into dev

Signed-off-by: dhb52 <dhb52@126.com>
dhb52 пре 2 година
родитељ
комит
75750c1bd3
100 измењених фајлова са 1670 додато и 1106 уклоњено
  1. 3 0
      .env
  2. 1 1
      .env.dev
  3. 0 36
      src/api/infra/redis/index.ts
  4. 0 9
      src/api/infra/redis/types.ts
  5. 1 5
      src/api/login/index.ts
  6. 41 0
      src/api/login/oauth2/index.ts
  7. 0 14
      src/api/login/types.ts
  8. 56 0
      src/api/mall/product/brand.ts
  9. 0 0
      src/api/mp/user/index.ts
  10. 5 5
      src/components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue
  11. 3 2
      src/config/axios/service.ts
  12. 1 0
      src/locales/zh-CN.ts
  13. 2 0
      src/main.ts
  14. 23 0
      src/plugins/tongji/index.ts
  15. 2 2
      src/router/index.ts
  16. 18 20
      src/router/modules/remaining.ts
  17. 1 7
      src/types/auto-components.d.ts
  18. 0 1
      src/utils/dict.ts
  19. 11 8
      src/views/Login/Login.vue
  20. 8 2
      src/views/Login/components/LoginForm.vue
  21. 2 1
      src/views/Login/components/LoginFormTitle.vue
  22. 186 0
      src/views/Login/components/SSOLogin.vue
  23. 2 1
      src/views/Login/components/index.ts
  24. 2 1
      src/views/Login/components/useLogin.ts
  25. 1 1
      src/views/bpm/definition/index.vue
  26. 2 3
      src/views/bpm/form/index.vue
  27. 1 1
      src/views/bpm/group/index.vue
  28. 1 1
      src/views/bpm/model/editor/index.vue
  29. 3 3
      src/views/bpm/model/index.vue
  30. 1 1
      src/views/bpm/processInstance/create/index.vue
  31. 1 1
      src/views/bpm/processInstance/detail/index.vue
  32. 207 53
      src/views/bpm/processInstance/index.vue
  33. 0 94
      src/views/bpm/processInstance/process.data.ts
  34. 1 1
      src/views/bpm/task/done/index.vue
  35. 110 20
      src/views/bpm/task/todo/index.vue
  36. 0 58
      src/views/bpm/task/todo/todo.data.ts
  37. 1 1
      src/views/bpm/taskAssignRule/index.vue
  38. 1 1
      src/views/infra/apiAccessLog/index.vue
  39. 1 2
      src/views/infra/apiErrorLog/index.vue
  40. 1 1
      src/views/infra/build/index.vue
  41. 1 1
      src/views/infra/codegen/index.vue
  42. 1 1
      src/views/infra/config/index.vue
  43. 1 1
      src/views/infra/dataSourceConfig/index.vue
  44. 24 30
      src/views/infra/dbDoc/index.vue
  45. 18 4
      src/views/infra/druid/index.vue
  46. 45 22
      src/views/infra/file/FileForm.vue
  47. 11 6
      src/views/infra/file/index.vue
  48. 16 3
      src/views/infra/fileConfig/index.vue
  49. 1 1
      src/views/infra/job/index.vue
  50. 1 1
      src/views/infra/job/logger/index.vue
  51. 7 111
      src/views/infra/redis/index.vue
  52. 19 4
      src/views/infra/server/index.vue
  53. 18 2
      src/views/infra/skywalking/index.vue
  54. 18 4
      src/views/infra/swagger/index.vue
  55. 120 0
      src/views/mall/product/brand/BrandForm.vue
  56. 177 0
      src/views/mall/product/brand/index.vue
  57. 1 1
      src/views/mall/product/category/CategoryForm.vue
  58. 1 1
      src/views/mall/product/property/index.vue
  59. 1 1
      src/views/mall/product/property/value/index.vue
  60. 21 27
      src/views/mp/components/wx-material-select/main.vue
  61. 1 1
      src/views/mp/components/wx-msg/main.vue
  62. 17 44
      src/views/mp/components/wx-reply/main.vue
  63. 57 65
      src/views/mp/draft/index.vue
  64. 1 1
      src/views/mp/freePublish/index.vue
  65. 43 53
      src/views/mp/material/index.vue
  66. 33 39
      src/views/mp/menu/index.vue
  67. 0 0
      src/views/mp/menu/menuOptions.ts
  68. 0 293
      src/views/mp/mpuser/index.vue
  69. 99 0
      src/views/mp/user/UserForm.vue
  70. 187 0
      src/views/mp/user/index.vue
  71. 1 1
      src/views/pay/app/index.vue
  72. 1 1
      src/views/pay/merchant/index.vue
  73. 1 1
      src/views/pay/order/index.vue
  74. 1 1
      src/views/pay/refund/index.vue
  75. 1 1
      src/views/system/area/index.vue
  76. 1 1
      src/views/system/dept/index.vue
  77. 1 1
      src/views/system/dict/data/index.vue
  78. 1 1
      src/views/system/dict/index.vue
  79. 1 1
      src/views/system/errorCode/index.vue
  80. 1 1
      src/views/system/loginlog/index.vue
  81. 1 1
      src/views/system/mail/account/index.vue
  82. 1 1
      src/views/system/mail/log/index.vue
  83. 1 1
      src/views/system/mail/template/index.vue
  84. 1 1
      src/views/system/menu/index.vue
  85. 1 1
      src/views/system/notice/index.vue
  86. 1 1
      src/views/system/notify/message/index.vue
  87. 1 1
      src/views/system/notify/my/index.vue
  88. 1 1
      src/views/system/notify/template/index.vue
  89. 1 1
      src/views/system/oauth2/client/index.vue
  90. 1 1
      src/views/system/oauth2/token/index.vue
  91. 1 1
      src/views/system/operatelog/index.vue
  92. 1 2
      src/views/system/post/index.vue
  93. 1 1
      src/views/system/role/index.vue
  94. 1 1
      src/views/system/sensitiveWord/index.vue
  95. 1 1
      src/views/system/sms/channel/index.vue
  96. 1 1
      src/views/system/sms/log/index.vue
  97. 1 1
      src/views/system/sms/template/index.vue
  98. 1 2
      src/views/system/tenant/index.vue
  99. 1 1
      src/views/system/tenantPackage/index.vue
  100. 1 1
      src/views/system/user/index.vue

+ 3 - 0
.env

@@ -15,3 +15,6 @@ VITE_APP_CAPTCHA_ENABLE=true
 
 
 # 验证码的开关
 # 验证码的开关
 VITE_APP_CAPTCHA_ENABLE=true
 VITE_APP_CAPTCHA_ENABLE=true
+
+# 百度统计
+VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc

+ 1 - 1
.env.dev

@@ -16,7 +16,7 @@ VITE_API_BASEPATH=/dev-api
 VITE_API_URL=/admin-api
 VITE_API_URL=/admin-api
 
 
 # 打包路径
 # 打包路径
-VITE_BASE_PATH=/dist-dev/
+VITE_BASE_PATH=/
 
 
 # 是否删除debugger
 # 是否删除debugger
 VITE_DROP_DEBUGGER=false
 VITE_DROP_DEBUGGER=false

+ 0 - 36
src/api/infra/redis/index.ts

@@ -6,39 +6,3 @@ import request from '@/config/axios'
 export const getCache = () => {
 export const getCache = () => {
   return request.get({ url: '/infra/redis/get-monitor-info' })
   return request.get({ url: '/infra/redis/get-monitor-info' })
 }
 }
-
-// 获取模块
-export const getKeyDefineList = () => {
-  return request.get({ url: '/infra/redis/get-key-define-list' })
-}
-
-/**
- * 获取redis key列表
- */
-export const getKeyList = (keyTemplate: string) => {
-  return request.get({
-    url: '/infra/redis/get-key-list',
-    params: {
-      keyTemplate
-    }
-  })
-}
-
-// 获取缓存内容
-export const getKeyValue = (key: string) => {
-  return request.get({ url: '/infra/redis/get-key-value?key=' + key })
-}
-
-// 根据键名删除缓存
-export const deleteKey = (key: string) => {
-  return request.delete({ url: '/infra/redis/delete-key?key=' + key })
-}
-
-export const deleteKeys = (keyTemplate: string) => {
-  return request.delete({
-    url: '/infra/redis/delete-keys?',
-    params: {
-      keyTemplate
-    }
-  })
-}

+ 0 - 9
src/api/infra/redis/types.ts

@@ -174,12 +174,3 @@ export interface RedisCommandStatsVO {
   calls: number
   calls: number
   usec: number
   usec: number
 }
 }
-
-export interface RedisKeyInfo {
-  keyTemplate: string
-  keyType: string
-  valueType: string
-  timeoutType: number
-  timeout: number
-  memo: string
-}

+ 1 - 5
src/api/login/index.ts

@@ -2,15 +2,11 @@ import request from '@/config/axios'
 import { getRefreshToken } from '@/utils/auth'
 import { getRefreshToken } from '@/utils/auth'
 import type { UserLoginVO } from './types'
 import type { UserLoginVO } from './types'
 
 
-export interface CodeImgResult {
-  captchaOnOff: boolean
-  img: string
-  uuid: string
-}
 export interface SmsCodeVO {
 export interface SmsCodeVO {
   mobile: string
   mobile: string
   scene: number
   scene: number
 }
 }
+
 export interface SmsLoginVO {
 export interface SmsLoginVO {
   mobile: string
   mobile: string
   code: string
   code: string

+ 41 - 0
src/api/login/oauth2/index.ts

@@ -0,0 +1,41 @@
+import request from '@/config/axios'
+
+// 获得授权信息
+export const getAuthorize = (clientId: string) => {
+  return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
+}
+
+// 发起授权
+export const authorize = (
+  responseType: string,
+  clientId: string,
+  redirectUri: string,
+  state: string,
+  autoApprove: boolean,
+  checkedScopes: string[],
+  uncheckedScopes: string[]
+) => {
+  // 构建 scopes
+  const scopes = {}
+  for (const scope of checkedScopes) {
+    scopes[scope] = true
+  }
+  for (const scope of uncheckedScopes) {
+    scopes[scope] = false
+  }
+  // 发起请求
+  return request.post({
+    url: '/system/oauth2/authorize',
+    headers: {
+      'Content-type': 'application/x-www-form-urlencoded'
+    },
+    params: {
+      response_type: responseType,
+      client_id: clientId,
+      redirect_uri: redirectUri,
+      state: state,
+      auto_approve: autoApprove,
+      scope: JSON.stringify(scopes)
+    }
+  })
+}

+ 0 - 14
src/api/login/types.ts

@@ -26,17 +26,3 @@ export type UserVO = {
   loginIp: string
   loginIp: string
   loginDate: string
   loginDate: string
 }
 }
-
-export type UserInfoVO = {
-  permissions: []
-  roles: []
-  user: {
-    avatar: string
-    id: number
-    nickname: string
-  }
-}
-
-export type TentantNameVO = {
-  name: string
-}

+ 56 - 0
src/api/mall/product/brand.ts

@@ -0,0 +1,56 @@
+import request from '@/config/axios'
+
+/**
+ * 商品品牌
+ */
+export interface BrandVO {
+  /**
+   * 品牌编号
+   */
+  id?: number
+  /**
+   * 品牌名称
+   */
+  name: string
+  /**
+   * 品牌图片
+   */
+  picUrl: string
+  /**
+   * 品牌排序
+   */
+  sort?: number
+  /**
+   * 品牌描述
+   */
+  description?: string
+  /**
+   * 开启状态
+   */
+  status: number
+}
+
+// 创建商品品牌
+export const createBrand = (data: BrandVO) => {
+  return request.post({ url: '/product/brand/create', data })
+}
+
+// 更新商品品牌
+export const updateBrand = (data: BrandVO) => {
+  return request.put({ url: '/product/brand/update', data })
+}
+
+// 删除商品品牌
+export const deleteBrand = (id: number) => {
+  return request.delete({ url: `/product/brand/delete?id=${id}` })
+}
+
+// 获得商品品牌
+export const getBrand = (id: number) => {
+  return request.get({ url: `/product/brand/get?id=${id}` })
+}
+
+// 获得商品品牌列表
+export const getBrandParam = (params: PageParam) => {
+  return request.get({ url: '/product/brand/page', params })
+}

+ 0 - 0
src/api/mp/mpuser/index.ts → src/api/mp/user/index.ts


+ 5 - 5
src/components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue

@@ -23,7 +23,7 @@
     </el-table>
     </el-table>
 
 
     <el-dialog
     <el-dialog
-      v-model="modelVisible"
+      v-model="dialogVisible"
       :title="modelConfig.title"
       :title="modelConfig.title"
       :close-on-click-modal="false"
       :close-on-click-modal="false"
       width="400px"
       width="400px"
@@ -39,7 +39,7 @@
         </el-form-item>
         </el-form-item>
       </el-form>
       </el-form>
       <template #footer>
       <template #footer>
-        <el-button @click="modelVisible = false">取 消</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
         <el-button type="primary" @click="addNewObject">保 存</el-button>
         <el-button type="primary" @click="addNewObject">保 存</el-button>
       </template>
       </template>
     </el-dialog>
     </el-dialog>
@@ -49,7 +49,7 @@
 const message = useMessage()
 const message = useMessage()
 const signalList = ref<any[]>([])
 const signalList = ref<any[]>([])
 const messageList = ref<any[]>([])
 const messageList = ref<any[]>([])
-const modelVisible = ref(false)
+const dialogVisible = ref(false)
 const modelType = ref('')
 const modelType = ref('')
 const modelObjectForm = ref<any>({})
 const modelObjectForm = ref<any>({})
 const rootElements = ref()
 const rootElements = ref()
@@ -85,7 +85,7 @@ const initDataList = () => {
 const openModel = (type) => {
 const openModel = (type) => {
   modelType.value = type
   modelType.value = type
   modelObjectForm.value = {}
   modelObjectForm.value = {}
-  modelVisible.value = true
+  dialogVisible.value = true
 }
 }
 const addNewObject = () => {
 const addNewObject = () => {
   if (modelType.value === 'message') {
   if (modelType.value === 'message') {
@@ -101,7 +101,7 @@ const addNewObject = () => {
     const signalRef = bpmnInstances().moddle.create('bpmn:Signal', modelObjectForm.value)
     const signalRef = bpmnInstances().moddle.create('bpmn:Signal', modelObjectForm.value)
     rootElements.value.push(signalRef)
     rootElements.value.push(signalRef)
   }
   }
-  modelVisible.value = false
+  dialogVisible.value = false
   initDataList()
   initDataList()
 }
 }
 
 

+ 3 - 2
src/config/axios/service.ts

@@ -1,8 +1,8 @@
 import axios, {
 import axios, {
+  AxiosError,
   AxiosInstance,
   AxiosInstance,
   AxiosRequestHeaders,
   AxiosRequestHeaders,
   AxiosResponse,
   AxiosResponse,
-  AxiosError,
   InternalAxiosRequestConfig
   InternalAxiosRequestConfig
 } from 'axios'
 } from 'axios'
 
 
@@ -230,7 +230,8 @@ const handleAuthorized = () => {
       wsCache.clear()
       wsCache.clear()
       removeToken()
       removeToken()
       isRelogin.show = false
       isRelogin.show = false
-      window.location.href = import.meta.env.VITE_BASE_PATH
+      // 干掉token后再走一次路由让它过router.beforeEach的校验
+      window.location.href = window.location.href
     })
     })
   }
   }
   return Promise.reject(t('sys.api.timeoutMessage'))
   return Promise.reject(t('sys.api.timeoutMessage'))

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

@@ -352,6 +352,7 @@ export default {
     login: {
     login: {
       backSignIn: '返回',
       backSignIn: '返回',
       signInFormTitle: '登录',
       signInFormTitle: '登录',
+      ssoFormTitle: '三方授权',
       mobileSignInFormTitle: '手机登录',
       mobileSignInFormTitle: '手机登录',
       qrSignInFormTitle: '二维码登录',
       qrSignInFormTitle: '二维码登录',
       signUpFormTitle: '注册',
       signUpFormTitle: '注册',

+ 2 - 0
src/main.ts

@@ -52,6 +52,8 @@ import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
 import hljs from 'highlight.js' //导入代码高亮文件
 import hljs from 'highlight.js' //导入代码高亮文件
 import 'highlight.js/styles/github.css' //导入代码高亮样式  新版
 import 'highlight.js/styles/github.css' //导入代码高亮样式  新版
 
 
+import '@/plugins/tongji' // 百度统计
+
 import Logger from '@/utils/Logger'
 import Logger from '@/utils/Logger'
 
 
 // 本地开发模式 全局引入 element-plus 样式,加快第一次进入速度
 // 本地开发模式 全局引入 element-plus 样式,加快第一次进入速度

+ 23 - 0
src/plugins/tongji/index.ts

@@ -0,0 +1,23 @@
+import router from '@/router'
+
+// 用于 router push
+window._hmt = window._hmt || []
+// HM_ID
+const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE
+;(function () {
+  // 有值的时候,才开启
+  if (!HM_ID) {
+    return
+  }
+  const hm = document.createElement('script')
+  hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID
+  const s = document.getElementsByTagName('script')[0]
+  s.parentNode.insertBefore(hm, s)
+})()
+
+router.afterEach(function (to) {
+  if (!HM_ID) {
+    return
+  }
+  _hmt.push(['_trackPageview', to.fullPath])
+})

+ 2 - 2
src/router/index.ts

@@ -1,11 +1,11 @@
 import type { App } from 'vue'
 import type { App } from 'vue'
 import type { RouteRecordRaw } from 'vue-router'
 import type { RouteRecordRaw } from 'vue-router'
-import { createRouter, createWebHashHistory } from 'vue-router'
+import { createRouter, createWebHistory } from 'vue-router'
 import remainingRouter from './modules/remaining'
 import remainingRouter from './modules/remaining'
 
 
 // 创建路由实例
 // 创建路由实例
 const router = createRouter({
 const router = createRouter({
-  history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
+  history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
   strict: true,
   strict: true,
   routes: remainingRouter as RouteRecordRaw[],
   routes: remainingRouter as RouteRecordRaw[],
   scrollBehavior: () => ({ left: 0, top: 0 })
   scrollBehavior: () => ({ left: 0, top: 0 })

+ 18 - 20
src/router/modules/remaining.ts

@@ -116,7 +116,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: 'type/data/:dictType',
         path: 'type/data/:dictType',
         component: () => import('@/views/system/dict/data/index.vue'),
         component: () => import('@/views/system/dict/data/index.vue'),
-        name: 'data',
+        name: 'SystemDictData',
         meta: {
         meta: {
           title: '字典数据',
           title: '字典数据',
           noCache: true,
           noCache: true,
@@ -140,7 +140,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: 'edit',
         path: 'edit',
         component: () => import('@/views/infra/codegen/EditTable.vue'),
         component: () => import('@/views/infra/codegen/EditTable.vue'),
-        name: 'EditTable',
+        name: 'InfraCodegenEditTable',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -163,7 +163,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: 'job-log',
         path: 'job-log',
         component: () => import('@/views/infra/job/logger/index.vue'),
         component: () => import('@/views/infra/job/logger/index.vue'),
-        name: 'JobLog',
+        name: 'InfraJobLog',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
       noTagsView: true
       noTagsView: true
     }
     }
   },
   },
+  {
+    path: '/sso',
+    component: () => import('@/views/Login/Login.vue'),
+    name: 'SSOLogin',
+    meta: {
+      hidden: true,
+      title: t('router.login'),
+      noTagsView: true
+    }
+  },
   {
   {
     path: '/403',
     path: '/403',
     component: () => import('@/views/Error/403.vue'),
     component: () => import('@/views/Error/403.vue'),
@@ -226,7 +236,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: '/manager/form/edit',
         path: '/manager/form/edit',
         component: () => import('@/views/bpm/form/editor/index.vue'),
         component: () => import('@/views/bpm/form/editor/index.vue'),
-        name: 'bpmFormEditor',
+        name: 'BpmFormEditor',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -238,7 +248,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: '/manager/model/edit',
         path: '/manager/model/edit',
         component: () => import('@/views/bpm/model/editor/index.vue'),
         component: () => import('@/views/bpm/model/editor/index.vue'),
-        name: 'modelEditor',
+        name: 'BpmModelEditor',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -250,7 +260,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: '/manager/definition',
         path: '/manager/definition',
         component: () => import('@/views/bpm/definition/index.vue'),
         component: () => import('@/views/bpm/definition/index.vue'),
-        name: 'BpmProcessDefinitionList',
+        name: 'BpmProcessDefinition',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -262,7 +272,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: '/manager/task-assign-rule',
         path: '/manager/task-assign-rule',
         component: () => import('@/views/bpm/taskAssignRule/index.vue'),
         component: () => import('@/views/bpm/taskAssignRule/index.vue'),
-        name: 'BpmTaskAssignRuleList',
+        name: 'BpmTaskAssignRule',
         meta: {
         meta: {
           noCache: true,
           noCache: true,
           hidden: true,
           hidden: true,
@@ -305,18 +315,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
           title: '发起 OA 请假',
           title: '发起 OA 请假',
           activeMenu: 'bpm/oa/leave/create'
           activeMenu: 'bpm/oa/leave/create'
         }
         }
-      },
-      {
-        path: '/bpm/oa/leave/detail',
-        component: () => import('@/views/bpm/oa/leave/detail.vue'),
-        name: 'OALeaveDetail',
-        meta: {
-          noCache: true,
-          hidden: true,
-          canTo: true,
-          title: '查看 OA 请假',
-          activeMenu: 'bpm/oa/leave/detail'
-        }
       }
       }
     ]
     ]
   },
   },
@@ -331,7 +329,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       {
       {
         path: 'value/:propertyId(\\d+)',
         path: 'value/:propertyId(\\d+)',
         component: () => import('@/views/mall/product/property/value/index.vue'),
         component: () => import('@/views/mall/product/property/value/index.vue'),
-        name: 'PropertyValue',
+        name: 'ProductPropertyValue',
         meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
         meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
       }
       }
     ]
     ]

+ 1 - 7
src/types/auto-components.d.ts

@@ -25,13 +25,12 @@ declare module '@vue/runtime-core' {
     Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
     Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
     Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
     Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
     ElAlert: typeof import('element-plus/es')['ElAlert']
     ElAlert: typeof import('element-plus/es')['ElAlert']
-    ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer']
-    ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
+    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapse: typeof import('element-plus/es')['ElCollapse']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@@ -71,19 +70,14 @@ declare module '@vue/runtime-core' {
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
     ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
-    ElSpace: typeof import('element-plus/es')['ElSpace']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElSwitch: typeof import('element-plus/es')['ElSwitch']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
-    ElTableV2: typeof import('element-plus/es')['ElTableV2']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTag: typeof import('element-plus/es')['ElTag']
-    ElTimeline: typeof import('element-plus/es')['ElTimeline']
-    ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
     ElTree: typeof import('element-plus/es')['ElTree']
-    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     ElUpload: typeof import('element-plus/es')['ElUpload']
     Error: typeof import('./../components/Error/src/Error.vue')['default']
     Error: typeof import('./../components/Error/src/Error.vue')['default']
     FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']
     FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']

+ 0 - 1
src/utils/dict.ts

@@ -112,7 +112,6 @@ export enum DICT_TYPE {
 
 
   // ========== INFRA 模块 ==========
   // ========== INFRA 模块 ==========
   INFRA_BOOLEAN_STRING = 'infra_boolean_string',
   INFRA_BOOLEAN_STRING = 'infra_boolean_string',
-  INFRA_REDIS_TIMEOUT_TYPE = 'infra_redis_timeout_type',
   INFRA_JOB_STATUS = 'infra_job_status',
   INFRA_JOB_STATUS = 'infra_job_status',
   INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
   INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
   INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
   INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',

+ 11 - 8
src/views/Login/Login.vue

@@ -9,19 +9,19 @@
       >
       >
         <!-- 左上角的 logo + 系统标题 -->
         <!-- 左上角的 logo + 系统标题 -->
         <div class="flex items-center relative text-white">
         <div class="flex items-center relative text-white">
-          <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
+          <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
           <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
           <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
         </div>
         </div>
         <!-- 左边的背景图 + 欢迎语 -->
         <!-- 左边的背景图 + 欢迎语 -->
         <div class="flex justify-center items-center h-[calc(100%-60px)]">
         <div class="flex justify-center items-center h-[calc(100%-60px)]">
           <TransitionGroup
           <TransitionGroup
             appear
             appear
-            tag="div"
             enter-active-class="animate__animated animate__bounceInLeft"
             enter-active-class="animate__animated animate__bounceInLeft"
+            tag="div"
           >
           >
-            <img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
-            <div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
-            <div class="mt-5 font-normal text-white text-14px" key="3">
+            <img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
+            <div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
+            <div key="3" class="mt-5 font-normal text-white text-14px">
               {{ t('login.message') }}
               {{ t('login.message') }}
             </div>
             </div>
           </TransitionGroup>
           </TransitionGroup>
@@ -31,7 +31,7 @@
         <!-- 右上角的主题、语言选择 -->
         <!-- 右上角的主题、语言选择 -->
         <div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
         <div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
           <div class="flex items-center @2xl:hidden @xl:hidden">
           <div class="flex items-center @2xl:hidden @xl:hidden">
-            <img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
+            <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
             <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
             <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
           </div>
           </div>
           <div class="flex justify-end items-center space-x-10px">
           <div class="flex justify-end items-center space-x-10px">
@@ -52,20 +52,23 @@
             <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
             <QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
             <!-- 注册 -->
             <!-- 注册 -->
             <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
             <RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
+            <!-- 三方登录 -->
+            <SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
           </div>
           </div>
         </Transition>
         </Transition>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
-<script setup lang="ts">
+<script lang="ts" setup>
 import { underlineToHump } from '@/utils'
 import { underlineToHump } from '@/utils'
 
 
 import { useDesign } from '@/hooks/web/useDesign'
 import { useDesign } from '@/hooks/web/useDesign'
 import { useAppStore } from '@/store/modules/app'
 import { useAppStore } from '@/store/modules/app'
 import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
 import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
 import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
 import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
-import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components'
+
+import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
 
 
 const { t } = useI18n()
 const { t } = useI18n()
 const appStore = useAppStore()
 const appStore = useAppStore()

+ 8 - 2
src/views/Login/components/LoginForm.vue

@@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon'
 import * as authUtil from '@/utils/auth'
 import * as authUtil from '@/utils/auth'
 import { usePermissionStore } from '@/store/modules/permission'
 import { usePermissionStore } from '@/store/modules/permission'
 import * as LoginApi from '@/api/login'
 import * as LoginApi from '@/api/login'
-import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
+import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
 
 
 const { t } = useI18n()
 const { t } = useI18n()
 const message = useMessage()
 const message = useMessage()
@@ -240,7 +240,12 @@ const handleLogin = async (params) => {
     if (!redirect.value) {
     if (!redirect.value) {
       redirect.value = '/'
       redirect.value = '/'
     }
     }
-    push({ path: redirect.value || permissionStore.addRouters[0].path })
+    // 判断是否为SSO登录
+    if (redirect.value.indexOf('sso') !== -1) {
+      window.location.href = window.location.href.replace('/login?redirect=', '')
+    } else {
+      push({ path: redirect.value || permissionStore.addRouters[0].path })
+    }
   } catch {
   } catch {
     loginLoading.value = false
     loginLoading.value = false
   } finally {
   } finally {
@@ -291,6 +296,7 @@ onMounted(() => {
     color: var(--el-color-primary) !important;
     color: var(--el-color-primary) !important;
   }
   }
 }
 }
+
 .login-code {
 .login-code {
   width: 100%;
   width: 100%;
   height: 38px;
   height: 38px;

+ 2 - 1
src/views/Login/components/LoginFormTitle.vue

@@ -16,7 +16,8 @@ const getFormTitle = computed(() => {
     [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
     [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
     [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
     [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
     [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
     [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
-    [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle')
+    [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
+    [LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')
   }
   }
   return titleObj[unref(getLoginState)]
   return titleObj[unref(getLoginState)]
 })
 })

+ 186 - 0
src/views/Login/components/SSOLogin.vue

@@ -0,0 +1,186 @@
+<template>
+  <div v-show="ssoVisible" class="form-cont">
+    <!-- 应用名 -->
+    <LoginFormTitle style="width: 100%" />
+    <el-tabs class="form" style="float: none" value="uname">
+      <el-tab-pane :label="client.name" name="uname" />
+    </el-tabs>
+    <div>
+      <el-form :model="formData" class="login-form">
+        <!-- 授权范围的选择 -->
+        此第三方应用请求获得以下权限:
+        <el-form-item prop="scopes">
+          <el-checkbox-group v-model="formData.scopes">
+            <el-checkbox
+              v-for="scope in queryParams.scopes"
+              :key="scope"
+              :label="scope"
+              style="display: block; margin-bottom: -10px"
+            >
+              {{ formatScope(scope) }}
+            </el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <!-- 下方的登录按钮 -->
+        <el-form-item class="w-1/1">
+          <el-button
+            :loading="formLoading"
+            class="w-6/10"
+            type="primary"
+            @click.prevent="handleAuthorize(true)"
+          >
+            <span v-if="!formLoading">同意授权</span>
+            <span v-else>授 权 中...</span>
+          </el-button>
+          <el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+</template>
+<script lang="ts" name="SSOLogin" setup>
+import LoginFormTitle from './LoginFormTitle.vue'
+import * as OAuth2Api from '@/api/login/oauth2'
+import { LoginStateEnum, useLoginState } from './useLogin'
+import type { RouteLocationNormalizedLoaded } from 'vue-router'
+const route = useRoute() // 路由
+const { currentRoute } = useRouter() // 路由
+const { getLoginState, setLoginState } = useLoginState()
+
+const client = ref({
+  // 客户端信息
+  name: '',
+  logo: ''
+})
+const queryParams = reactive({
+  // URL 上的 client_id、scope 等参数
+  responseType: '',
+  clientId: '',
+  redirectUri: '',
+  state: '',
+  scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
+})
+const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
+const formData = reactive({
+  scopes: [] // 已选中的 scope 数组
+})
+const formLoading = ref(false) // 表单是否提交中
+
+/** 初始化授权信息 */
+const init = async () => {
+  // 防止在没有登录的情况下循环弹窗
+  if (typeof route.query.client_id === 'undefined') return
+  // 解析参数
+  // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
+  // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
+  queryParams.responseType = route.query.response_type as string
+  queryParams.clientId = route.query.client_id as string
+  queryParams.redirectUri = route.query.redirect_uri as string
+  queryParams.state = route.query.state as string
+  if (route.query.scope) {
+    queryParams.scopes = (route.query.scope as string).split(' ')
+  }
+
+  // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
+  if (queryParams.scopes.length > 0) {
+    const data = await doAuthorize(true, queryParams.scopes, [])
+    if (data) {
+      location.href = data
+      return
+    }
+  }
+
+  // 获取授权页的基本信息
+  const data = await OAuth2Api.getAuthorize(queryParams.clientId)
+  client.value = data.client
+  // 解析 scope
+  let scopes
+  // 1.1 如果 params.scope 非空,则过滤下返回的 scopes
+  if (queryParams.scopes.length > 0) {
+    scopes = []
+    for (const scope of data.scopes) {
+      if (queryParams.scopes.indexOf(scope.key) >= 0) {
+        scopes.push(scope)
+      }
+    }
+    // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
+  } else {
+    scopes = data.scopes
+    for (const scope of scopes) {
+      queryParams.scopes.push(scope.key)
+    }
+  }
+  // 生成已选中的 checkedScopes
+  for (const scope of scopes) {
+    if (scope.value) {
+      formData.scopes.push(scope.key)
+    }
+  }
+}
+
+/** 处理授权的提交 */
+const handleAuthorize = async (approved) => {
+  // 计算 checkedScopes + uncheckedScopes
+  let checkedScopes
+  let uncheckedScopes
+  if (approved) {
+    // 同意授权,按照用户的选择
+    checkedScopes = formData.scopes
+    uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
+  } else {
+    // 拒绝,则都是取消
+    checkedScopes = []
+    uncheckedScopes = queryParams.scopes
+  }
+  // 提交授权的请求
+  formLoading.value = true
+  try {
+    const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
+    if (!data) {
+      return
+    }
+    location.href = data
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 调用授权 API 接口 */
+const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
+  return OAuth2Api.authorize(
+    queryParams.responseType,
+    queryParams.clientId,
+    queryParams.redirectUri,
+    queryParams.state,
+    autoApprove,
+    checkedScopes,
+    uncheckedScopes
+  )
+}
+
+/** 格式化 scope 文本 */
+const formatScope = (scope) => {
+  // 格式化 scope 授权范围,方便用户理解。
+  // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
+  switch (scope) {
+    case 'user.read':
+      return '访问你的个人信息'
+    case 'user.write':
+      return '修改你的个人信息'
+    default:
+      return scope
+  }
+}
+
+/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
+watch(
+  () => currentRoute.value,
+  (route: RouteLocationNormalizedLoaded) => {
+    if (route.name === 'SSOLogin') {
+      setLoginState(LoginStateEnum.SSO)
+      init()
+    }
+  },
+  { immediate: true }
+)
+</script>

+ 2 - 1
src/views/Login/components/index.ts

@@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue'
 import LoginFormTitle from './LoginFormTitle.vue'
 import LoginFormTitle from './LoginFormTitle.vue'
 import RegisterForm from './RegisterForm.vue'
 import RegisterForm from './RegisterForm.vue'
 import QrCodeForm from './QrCodeForm.vue'
 import QrCodeForm from './QrCodeForm.vue'
+import SSOLoginVue from './SSOLogin.vue'
 
 
-export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm }
+export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }

+ 2 - 1
src/views/Login/components/useLogin.ts

@@ -5,7 +5,8 @@ export enum LoginStateEnum {
   REGISTER,
   REGISTER,
   RESET_PASSWORD,
   RESET_PASSWORD,
   MOBILE,
   MOBILE,
-  QR_CODE
+  QR_CODE,
+  SSO
 }
 }
 
 
 const currentState = ref(LoginStateEnum.LOGIN)
 const currentState = ref(LoginStateEnum.LOGIN)

+ 1 - 1
src/views/bpm/definition/index.vue

@@ -93,7 +93,7 @@
   </Dialog>
   </Dialog>
 </template>
 </template>
 
 
-<script setup lang="ts" name="Form">
+<script setup lang="ts" name="BpmProcessDefinition">
 import { DICT_TYPE } from '@/utils/dict'
 import { DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as DefinitionApi from '@/api/bpm/definition'

+ 2 - 3
src/views/bpm/form/index.vue

@@ -83,12 +83,11 @@
   </Dialog>
   </Dialog>
 </template>
 </template>
 
 
-<script setup lang="ts" name="Form">
+<script setup lang="ts" name="BpmForm">
 import { DICT_TYPE } from '@/utils/dict'
 import { DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as FormApi from '@/api/bpm/form'
 import * as FormApi from '@/api/bpm/form'
 import { setConfAndFields2 } from '@/utils/formCreate'
 import { setConfAndFields2 } from '@/utils/formCreate'
-
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 const { push } = useRouter() // 路由
 const { push } = useRouter() // 路由
@@ -130,7 +129,7 @@ const resetQuery = () => {
 /** 添加/修改操作 */
 /** 添加/修改操作 */
 const openForm = (id?: number) => {
 const openForm = (id?: number) => {
   push({
   push({
-    name: 'bpmFormEditor',
+    name: 'BpmFormEditor',
     query: {
     query: {
       id
       id
     }
     }

+ 1 - 1
src/views/bpm/group/index.vue

@@ -111,7 +111,7 @@
   <UserGroupForm ref="formRef" @success="getList" />
   <UserGroupForm ref="formRef" @success="getList" />
 </template>
 </template>
 
 
-<script setup lang="ts" name="UserGroup">
+<script setup lang="ts" name="BpmUserGroup">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as UserGroupApi from '@/api/bpm/userGroup'
 import * as UserGroupApi from '@/api/bpm/userGroup'

+ 1 - 1
src/views/bpm/model/editor/index.vue

@@ -24,7 +24,7 @@
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
 
 
-<script setup lang="ts">
+<script setup lang="ts" name="BpmModelEditor">
 // 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
 // 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
 import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
 import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
 // 自定义左侧菜单(修改 默认任务 为 用户任务)
 // 自定义左侧菜单(修改 默认任务 为 用户任务)

+ 3 - 3
src/views/bpm/model/index.vue

@@ -224,7 +224,7 @@
   </Dialog>
   </Dialog>
 </template>
 </template>
 
 
-<script setup lang="ts" name="Form">
+<script setup lang="ts" name="BpmModel">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter, formatDate } from '@/utils/formatTime'
 import { dateFormatter, formatDate } from '@/utils/formatTime'
 import * as ModelApi from '@/api/bpm/model'
 import * as ModelApi from '@/api/bpm/model'
@@ -319,7 +319,7 @@ const handleChangeState = async (row) => {
 /** 设计流程 */
 /** 设计流程 */
 const handleDesign = (row) => {
 const handleDesign = (row) => {
   push({
   push({
-    name: 'modelEditor',
+    name: 'BpmModelEditor',
     query: {
     query: {
       modelId: row.id
       modelId: row.id
     }
     }
@@ -352,7 +352,7 @@ const handleAssignRule = (row) => {
 /** 跳转到指定流程定义列表 */
 /** 跳转到指定流程定义列表 */
 const handleDefinitionList = (row) => {
 const handleDefinitionList = (row) => {
   push({
   push({
-    name: 'BpmProcessDefinitionList',
+    name: 'BpmProcessDefinition',
     query: {
     query: {
       key: row.key
       key: row.key
     }
     }

+ 1 - 1
src/views/bpm/processInstance/create/index.vue

@@ -46,7 +46,7 @@
     <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
     <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts">
+<script setup lang="ts" name="BpmProcessInstanceCreate">
 import { DICT_TYPE } from '@/utils/dict'
 import { DICT_TYPE } from '@/utils/dict'
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'

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

@@ -96,7 +96,7 @@
     <TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
     <TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts">
+<script setup lang="ts" name="BpmProcessInstanceDetail">
 import { useUserStore } from '@/store/modules/user'
 import { useUserStore } from '@/store/modules/user'
 import { setConfAndFields2 } from '@/utils/formCreate'
 import { setConfAndFields2 } from '@/utils/formCreate'
 import type { ApiAttrs } from '@form-create/element-ui/types/config'
 import type { ApiAttrs } from '@form-create/element-ui/types/config'

+ 207 - 53
src/views/bpm/processInstance/index.vue

@@ -1,64 +1,211 @@
 <template>
 <template>
   <ContentWrap>
   <ContentWrap>
-    <!-- 列表 -->
-    <XTable @register="registerTable">
-      <template #toolbar_buttons>
-        <!-- 操作:新增 -->
-        <XButton
+    <!-- 搜索工作栏 -->
+    <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="processDefinitionId">
+        <el-input
+          v-model="queryParams.processDefinitionId"
+          placeholder="请输入流程定义的编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="流程分类" prop="category">
+        <el-select
+          v-model="queryParams.category"
+          placeholder="请选择流程分类"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </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.BPM_PROCESS_INSTANCE_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="结果" prop="result">
+        <el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
+            :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"
+        />
+      </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"
           type="primary"
-          preIcon="ep:zoom-in"
-          title="发起流程"
+          plain
           v-hasPermi="['bpm:process-instance:query']"
           v-hasPermi="['bpm:process-instance:query']"
           @click="handleCreate"
           @click="handleCreate"
-        />
-      </template>
-      <!-- 流程分类 -->
-      <template #category_default="{ row }">
-        <DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
-      </template>
-      <!-- 当前审批任务 -->
-      <template #tasks_default="{ row }">
-        <el-button v-for="task in row.tasks" :key="task.id" link>
-          <span>{{ task.name }}</span>
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 发起流程
         </el-button>
         </el-button>
-      </template>
-      <!-- 操作 -->
-      <template #actionbtns_default="{ row }">
-        <XTextButton
-          preIcon="ep:view"
-          :title="t('action.detail')"
-          v-hasPermi="['bpm:process-instance:cancel']"
-          @click="handleDetail(row)"
-        />
-        <XTextButton
-          preIcon="ep:delete"
-          title="取消"
-          v-if="row.result === 1"
-          v-hasPermi="['bpm:process-instance:query']"
-          @click="handleCancel(row)"
-        />
-      </template>
-    </XTable>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="流程编号" align="center" prop="id" width="300px" />
+      <el-table-column label="流程名称" align="center" prop="name" />
+      <el-table-column label="流程分类" align="center" prop="category">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
+        </template>
+      </el-table-column>
+      <el-table-column label="当前审批任务" align="center" prop="tasks">
+        <template #default="scope">
+          <el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
+            <span>{{ task.name }}</span>
+          </el-button>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="结果" prop="result">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="提交时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column
+        label="结束时间"
+        align="center"
+        prop="endTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            v-hasPermi="['bpm:process-instance:cancel']"
+            @click="handleDetail(scope.row)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="primary"
+            v-if="scope.row.result === 1"
+            v-hasPermi="['bpm:process-instance:query']"
+            @click="handleCancel(scope.row)"
+          >
+            取消
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts">
-// 全局相关的 import
+<script setup lang="ts" name="BpmProcessInstance">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 import { ElMessageBox } from 'element-plus'
 import { ElMessageBox } from 'element-plus'
-import { DICT_TYPE } from '@/utils/dict'
-
-// 业务相关的 import
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { allSchemas } from './process.data'
-
 const router = useRouter() // 路由
 const router = useRouter() // 路由
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 
 
-// ========== 列表相关 ==========
-const [registerTable, { reload }] = useXTable({
-  allSchemas: allSchemas,
-  getListApi: ProcessInstanceApi.getMyProcessInstancePage
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: '',
+  processDefinitionId: undefined,
+  category: undefined,
+  status: undefined,
+  result: undefined,
+  createTime: []
 })
 })
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ProcessInstanceApi.getMyProcessInstancePage(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 handleCreate = () => {
 const handleCreate = () => {
@@ -67,7 +214,7 @@ const handleCreate = () => {
   })
   })
 }
 }
 
 
-// 列表操作
+/** 查看详情 */
 const handleDetail = (row) => {
 const handleDetail = (row) => {
   router.push({
   router.push({
     name: 'BpmProcessInstanceDetail',
     name: 'BpmProcessInstanceDetail',
@@ -78,16 +225,23 @@ const handleDetail = (row) => {
 }
 }
 
 
 /** 取消按钮操作 */
 /** 取消按钮操作 */
-const handleCancel = (row) => {
-  ElMessageBox.prompt('请输入取消原因', '取消流程', {
+const handleCancel = async (row) => {
+  // 二次确认
+  const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
     confirmButtonText: t('common.ok'),
     confirmButtonText: t('common.ok'),
     cancelButtonText: t('common.cancel'),
     cancelButtonText: t('common.cancel'),
     inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
     inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
     inputErrorMessage: '取消原因不能为空'
     inputErrorMessage: '取消原因不能为空'
-  }).then(async ({ value }) => {
-    await ProcessInstanceApi.cancelProcessInstance(row.id, value)
-    message.success('取消成功')
-    reload()
   })
   })
+  // 发起取消
+  await ProcessInstanceApi.cancelProcessInstance(row.id, value)
+  message.success('取消成功')
+  // 刷新列表
+  await getList()
 }
 }
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>
 </script>

+ 0 - 94
src/views/bpm/processInstance/process.data.ts

@@ -1,94 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-const { t } = useI18n() // 国际化
-
-// CrudSchema
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  primaryTitle: '编号',
-  action: true,
-  actionWidth: '200px',
-  columns: [
-    {
-      title: '编号',
-      field: 'id',
-      table: {
-        width: 320
-      }
-    },
-    {
-      title: '流程名',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '所属流程',
-      field: 'processDefinitionId',
-      isSearch: true,
-      isTable: false
-    },
-    {
-      title: '流程分类',
-      field: 'category',
-      dictType: DICT_TYPE.BPM_MODEL_CATEGORY,
-      dictClass: 'number',
-      isSearch: true,
-      table: {
-        slots: {
-          default: 'category_default'
-        }
-      }
-    },
-    {
-      title: '当前审批任务',
-      field: 'tasks',
-      table: {
-        width: 140,
-        slots: {
-          default: 'tasks_default'
-        }
-      }
-    },
-    {
-      title: t('common.status'),
-      field: 'status',
-      dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '结果',
-      field: 'result',
-      dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
-      dictClass: 'number',
-      isSearch: true
-    },
-    {
-      title: '提交时间',
-      field: 'createTime',
-      formatter: 'formatDate',
-      table: {
-        width: 180
-      },
-      isForm: false,
-      isSearch: true,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '结束时间',
-      field: 'endTime',
-      formatter: 'formatDate',
-      table: {
-        width: 180
-      },
-      isForm: false
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

+ 1 - 1
src/views/bpm/task/done/index.vue

@@ -74,7 +74,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <TaskDetail ref="detailRef" @success="getList" />
   <TaskDetail ref="detailRef" @success="getList" />
 </template>
 </template>
-<script setup lang="tsx">
+<script setup lang="tsx" name="BpmTodoTask">
 import { DICT_TYPE } from '@/utils/dict'
 import { DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as TaskApi from '@/api/bpm/task'
 import * as TaskApi from '@/api/bpm/task'

+ 110 - 20
src/views/bpm/task/todo/index.vue

@@ -1,32 +1,117 @@
 <template>
 <template>
   <ContentWrap>
   <ContentWrap>
-    <XTable @register="registerTable">
-      <template #suspensionState_default="{ row }">
-        <el-tag type="success" v-if="row.suspensionState === 1">激活</el-tag>
-        <el-tag type="warning" v-if="row.suspensionState === 2">挂起</el-tag>
-      </template>
-      <template #actionbtns_default="{ row }">
-        <!-- 操作: 审批进度 -->
-        <XTextButton preIcon="ep:edit-pen" title="审批进度" @click="handleAudit(row)" />
-      </template>
-    </XTable>
+    <!-- 搜索工作栏 -->
+    <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="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"
+        />
+      </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>
   </ContentWrap>
   </ContentWrap>
-</template>
 
 
-<script setup lang="ts">
-// 业务相关的 import
-import { allSchemas } from './todo.data'
-import * as TaskApi from '@/api/bpm/task'
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="任务编号" align="center" prop="id" width="300px" />
+      <el-table-column label="任务名称" align="center" prop="name" />
+      <el-table-column label="所属流程" align="center" prop="processInstance.name" />
+      <el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="任务状态" prop="suspensionState">
+        <template #default="scope">
+          <el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
+          <el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button link type="primary" @click="handleAudit(scope.row)">审批进度</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+</template>
 
 
+<script setup lang="tsx" name="BpmDoneTask">
+import { dateFormatter } from '@/utils/formatTime'
 const { push } = useRouter() // 路由
 const { push } = useRouter() // 路由
+import * as TaskApi from '@/api/bpm/task'
 
 
-const [registerTable] = useXTable({
-  allSchemas: allSchemas,
-  topActionSlots: false,
-  getListApi: TaskApi.getTodoTaskPage
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: '',
+  createTime: []
 })
 })
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询任务列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await TaskApi.getTodoTaskPage(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 handleAudit = (row) => {
 const handleAudit = (row) => {
   push({
   push({
     name: 'BpmProcessInstanceDetail',
     name: 'BpmProcessInstanceDetail',
@@ -35,4 +120,9 @@ const handleAudit = (row) => {
     }
     }
   })
   })
 }
 }
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
 </script>
 </script>

+ 0 - 58
src/views/bpm/task/todo/todo.data.ts

@@ -1,58 +0,0 @@
-import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
-
-const { t } = useI18n() // 国际化
-
-// crudSchemas
-const crudSchemas = reactive<VxeCrudSchema>({
-  primaryKey: 'id',
-  primaryType: null,
-  action: true,
-  searchSpan: 8,
-  columns: [
-    {
-      title: '任务编号',
-      field: 'id',
-      table: {
-        width: 320
-      }
-    },
-    {
-      title: '任务名称',
-      field: 'name',
-      isSearch: true
-    },
-    {
-      title: '所属流程',
-      field: 'processInstance.name'
-    },
-    {
-      title: '流程发起人',
-      field: 'processInstance.startUserNickname'
-    },
-    {
-      title: t('common.createTime'),
-      field: 'createTime',
-      formatter: 'formatDate',
-      table: {
-        width: 180
-      },
-      isSearch: true,
-      search: {
-        show: true,
-        itemRender: {
-          name: 'XDataTimePicker'
-        }
-      }
-    },
-    {
-      title: '任务状态',
-      field: 'suspensionState',
-      table: {
-        slots: {
-          default: 'suspensionState_default'
-        }
-      }
-    }
-  ]
-})
-export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

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

@@ -32,7 +32,7 @@
   <!-- 添加/修改弹窗 -->
   <!-- 添加/修改弹窗 -->
   <TaskAssignRuleForm ref="formRef" @success="getList" />
   <TaskAssignRuleForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="TaskAssignRule">
+<script setup lang="ts" name="BpmTaskAssignRule">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
 import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
 import * as RoleApi from '@/api/system/role'
 import * as RoleApi from '@/api/system/role'

+ 1 - 1
src/views/infra/apiAccessLog/index.vue

@@ -139,7 +139,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <ApiAccessLogDetail ref="detailRef" />
   <ApiAccessLogDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="ApiAccessLog">
+<script setup lang="ts" name="InfraApiAccessLog">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import download from '@/utils/download'
 import download from '@/utils/download'
 import { formatDate } from '@/utils/formatTime'
 import { formatDate } from '@/utils/formatTime'

+ 1 - 2
src/views/infra/apiErrorLog/index.vue

@@ -158,14 +158,13 @@
   <ApiErrorLogDetail ref="detailRef" />
   <ApiErrorLogDetail ref="detailRef" />
 </template>
 </template>
 
 
-<script setup lang="ts" name="ApiErrorLog">
+<script setup lang="ts" name="InfraApiErrorLog">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'
 import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
 import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
 import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
 import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
 import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
 import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
-
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 
 
 const loading = ref(true) // 列表的加载中
 const loading = ref(true) // 列表的加载中

+ 1 - 1
src/views/infra/build/index.vue

@@ -31,7 +31,7 @@
     </div>
     </div>
   </Dialog>
   </Dialog>
 </template>
 </template>
-<script setup lang="ts" name="Build">
+<script setup lang="ts" name="InfraBuild">
 import formCreate from '@form-create/element-ui'
 import formCreate from '@form-create/element-ui'
 import { useClipboard } from '@vueuse/core'
 import { useClipboard } from '@vueuse/core'
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化

+ 1 - 1
src/views/infra/codegen/index.vue

@@ -142,7 +142,7 @@
   <!-- 弹窗:预览代码 -->
   <!-- 弹窗:预览代码 -->
   <PreviewCode ref="previewRef" />
   <PreviewCode ref="previewRef" />
 </template>
 </template>
-<script setup lang="ts" name="Codegen">
+<script setup lang="ts" name="InfraCodegen">
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'
 import * as CodegenApi from '@/api/infra/codegen'
 import * as CodegenApi from '@/api/infra/codegen'

+ 1 - 1
src/views/infra/config/index.vue

@@ -137,7 +137,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <ConfigForm ref="formRef" @success="getList" />
   <ConfigForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Config">
+<script setup lang="ts" name="InfraConfig">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/infra/dataSourceConfig/index.vue

@@ -57,7 +57,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <DataSourceConfigForm ref="formRef" @success="getList" />
   <DataSourceConfigForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="DataSourceConfig">
+<script setup lang="ts" name="InfraDataSourceConfig">
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
 import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
 import DataSourceConfigForm from './DataSourceConfigForm.vue'
 import DataSourceConfigForm from './DataSourceConfigForm.vue'

+ 24 - 30
src/views/infra/dbDoc/index.vue

@@ -2,46 +2,38 @@
   <doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" />
   <doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" />
 
 
   <ContentWrap title="数据库文档">
   <ContentWrap title="数据库文档">
-    <!-- 操作工具栏 -->
     <div class="mb-10px">
     <div class="mb-10px">
-      <XButton
-        type="primary"
-        preIcon="ep:download"
-        :title="t('action.export') + ' HTML'"
-        @click="handleExport('HTML')"
-      />
-      <XButton
-        type="primary"
-        preIcon="ep:download"
-        :title="t('action.export') + ' Word'"
-        @click="handleExport('Word')"
-      />
-      <XButton
-        type="primary"
-        preIcon="ep:download"
-        :title="t('action.export') + ' Markdown'"
-        @click="handleExport('Markdown')"
-      />
+      <el-button type="primary" plain @click="handleExport('HTML')">
+        <Icon icon="ep:download" /> 导出 HTML
+      </el-button>
+      <el-button type="primary" plain @click="handleExport('Word')">
+        <Icon icon="ep:download" /> 导出 Word
+      </el-button>
+      <el-button type="primary" plain @click="handleExport('Markdown')">
+        <Icon icon="ep:download" /> 导出 Markdown
+      </el-button>
     </div>
     </div>
-    <IFrame v-if="!loding" v-loading="loding" :src="src" />
+    <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts" name="DbDoc">
+<script setup lang="ts" name="InfraDBDoc">
 import download from '@/utils/download'
 import download from '@/utils/download'
-
 import * as DbDocApi from '@/api/infra/dbDoc'
 import * as DbDocApi from '@/api/infra/dbDoc'
 
 
-const { t } = useI18n() // 国际化
-const src = ref('')
-const loding = ref(true)
+const loading = ref(true) // 是否加载中
+const src = ref('') // HTML 的地址
+
 /** 页面加载 */
 /** 页面加载 */
 const init = async () => {
 const init = async () => {
-  const res = await DbDocApi.exportHtml()
-  let blob = new Blob([res], { type: 'text/html' })
-  let blobUrl = window.URL.createObjectURL(blob)
-  src.value = blobUrl
-  loding.value = false
+  try {
+    const data = await DbDocApi.exportHtml()
+    const blob = new Blob([data], { type: 'text/html' })
+    src.value = window.URL.createObjectURL(blob)
+  } finally {
+    loading.value = false
+  }
 }
 }
+
 /** 处理导出  */
 /** 处理导出  */
 const handleExport = async (type: string) => {
 const handleExport = async (type: string) => {
   if (type === 'HTML') {
   if (type === 'HTML') {
@@ -57,6 +49,8 @@ const handleExport = async (type: string) => {
     download.markdown(res, '数据库文档.md')
     download.markdown(res, '数据库文档.md')
   }
   }
 }
 }
+
+/** 初始化 */
 onMounted(async () => {
 onMounted(async () => {
   await init()
   await init()
 })
 })

+ 18 - 4
src/views/infra/druid/index.vue

@@ -3,10 +3,24 @@
   <doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
   <doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
 
 
   <ContentWrap>
   <ContentWrap>
-    <IFrame :src="src" />
+    <IFrame v-if="!loading" :src="url" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts" name="Druid">
-const BASE_URL = import.meta.env.VITE_BASE_URL
-const src = ref(BASE_URL + '/druid/index.html')
+<script setup lang="ts" name="InfraDruid">
+import * as ConfigApi from '@/api/infra/config'
+
+const loading = ref(true) // 是否加载中
+const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
+
+/** 初始化 */
+onMounted(async () => {
+  try {
+    const data = await ConfigApi.getConfigKey('url.druid')
+    if (data && data.length > 0) {
+      url.value = data
+    }
+  } finally {
+    loading.value = false
+  }
+})
 </script>
 </script>

+ 45 - 22
src/views/infra/file/FileForm.vue

@@ -2,17 +2,19 @@
   <Dialog title="上传文件" v-model="dialogVisible">
   <Dialog title="上传文件" v-model="dialogVisible">
     <el-upload
     <el-upload
       ref="uploadRef"
       ref="uploadRef"
-      :limit="1"
-      accept=".jpg, .png, .gif"
-      :auto-upload="false"
-      drag
-      :headers="headers"
       :action="url"
       :action="url"
       :data="data"
       :data="data"
-      :disabled="formLoading"
+      :headers="uploadHeaders"
+      v-model:file-list="fileList"
+      drag
+      accept=".jpg, .png, .gif"
+      :limit="1"
+      :on-success="submitFormSuccess"
+      :on-exceed="handleExceed"
+      :on-error="submitFormError"
       :on-change="handleFileChange"
       :on-change="handleFileChange"
-      :on-progress="handleFileUploadProgress"
-      :on-success="handleFileSuccess"
+      :auto-upload="false"
+      :disabled="formLoading"
     >
     >
       <i class="el-icon-upload"></i>
       <i class="el-icon-upload"></i>
       <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div>
       <div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div>
@@ -29,44 +31,47 @@
   </Dialog>
   </Dialog>
 </template>
 </template>
 <script setup lang="ts">
 <script setup lang="ts">
-import { Dialog } from '@/components/Dialog'
-import { getAccessToken } from '@/utils/auth'
+import { getAccessToken, getTenantId } from '@/utils/auth'
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 
 
 const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogVisible = ref(false) // 弹窗的是否展示
-const dialogTitle = ref('') // 弹窗的标题
-const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formLoading = ref(false) // 表单的加载中
 const url = import.meta.env.VITE_UPLOAD_URL
 const url = import.meta.env.VITE_UPLOAD_URL
-const headers = { Authorization: 'Bearer ' + getAccessToken() }
+const uploadHeaders = ref() // 上传 Header 头
+const fileList = ref([]) // 文件列表
 const data = ref({ path: '' })
 const data = ref({ path: '' })
 const uploadRef = ref()
 const uploadRef = ref()
 
 
 /** 打开弹窗 */
 /** 打开弹窗 */
 const open = async () => {
 const open = async () => {
   dialogVisible.value = true
   dialogVisible.value = true
+  resetForm()
 }
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
 
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 /** 处理上传的文件发生变化 */
 /** 处理上传的文件发生变化 */
 const handleFileChange = (file) => {
 const handleFileChange = (file) => {
   data.value.path = file.name
   data.value.path = file.name
 }
 }
 
 
-/** 处理文件上传中 */
-const handleFileUploadProgress = () => {
-  formLoading.value = true // 禁止修改
-}
-
-/** 发起文件上传 */
+/** 提交表单 */
 const submitFileForm = () => {
 const submitFileForm = () => {
+  if (fileList.value.length == 0) {
+    message.error('请上传文件')
+    return
+  }
+  // 提交请求
+  uploadHeaders.value = {
+    Authorization: 'Bearer ' + getAccessToken(),
+    'tenant-id': getTenantId()
+  }
   unref(uploadRef)?.submit()
   unref(uploadRef)?.submit()
 }
 }
 
 
 /** 文件上传成功处理 */
 /** 文件上传成功处理 */
-const handleFileSuccess = () => {
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitFormSuccess = () => {
   // 清理
   // 清理
   dialogVisible.value = false
   dialogVisible.value = false
   formLoading.value = false
   formLoading.value = false
@@ -75,4 +80,22 @@ const handleFileSuccess = () => {
   message.success(t('common.createSuccess'))
   message.success(t('common.createSuccess'))
   emit('success')
   emit('success')
 }
 }
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
 </script>
 </script>

+ 11 - 6
src/views/infra/file/index.vue

@@ -1,9 +1,14 @@
 <template>
 <template>
-  <doc-alert title="上传下载" url="https://doc.iocoder.cn/file/"/>
-
+  <doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
   <!-- 搜索 -->
   <!-- 搜索 -->
   <ContentWrap>
   <ContentWrap>
-    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="文件路径" prop="path">
       <el-form-item label="文件路径" prop="path">
         <el-input
         <el-input
           v-model="queryParams.path"
           v-model="queryParams.path"
@@ -33,7 +38,7 @@
       <el-form-item>
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
         <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 @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-        <el-button type="primary" @click="openForm">
+        <el-button type="primary" plain @click="openForm">
           <Icon icon="ep:upload" class="mr-5px" /> 上传文件
           <Icon icon="ep:upload" class="mr-5px" /> 上传文件
         </el-button>
         </el-button>
       </el-form-item>
       </el-form-item>
@@ -86,11 +91,11 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <FileForm ref="formRef" @success="getList" />
   <FileForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Config">
+<script setup lang="ts" name="InfraFile">
 import { fileSizeFormatter } from '@/utils'
 import { fileSizeFormatter } from '@/utils'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as FileApi from '@/api/infra/file'
 import * as FileApi from '@/api/infra/file'
-import FileUploadForm from './FileForm.vue'
+import FileForm from './FileForm.vue'
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 
 

+ 16 - 3
src/views/infra/fileConfig/index.vue

@@ -3,17 +3,29 @@
 
 
   <!-- 搜索 -->
   <!-- 搜索 -->
   <ContentWrap>
   <ContentWrap>
-    <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="配置名" prop="name">
       <el-form-item label="配置名" prop="name">
         <el-input
         <el-input
           v-model="queryParams.name"
           v-model="queryParams.name"
           placeholder="请输入配置名"
           placeholder="请输入配置名"
           clearable
           clearable
           @keyup.enter="handleQuery"
           @keyup.enter="handleQuery"
+          class="!w-240px"
         />
         />
       </el-form-item>
       </el-form-item>
       <el-form-item label="存储器" prop="storage">
       <el-form-item label="存储器" prop="storage">
-        <el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
+        <el-select
+          v-model="queryParams.storage"
+          placeholder="请选择存储器"
+          clearable
+          class="!w-240px"
+        >
           <el-option
           <el-option
             v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
             v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
             :key="dict.value"
             :key="dict.value"
@@ -30,6 +42,7 @@
           start-placeholder="开始日期"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
           end-placeholder="结束日期"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
           :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
         />
         />
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
@@ -113,7 +126,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <FileConfigForm ref="formRef" @success="getList" />
   <FileConfigForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Config">
+<script setup lang="ts" name="InfraFileConfig">
 import * as FileConfigApi from '@/api/infra/fileConfig'
 import * as FileConfigApi from '@/api/infra/fileConfig'
 import FileConfigForm from './FileConfigForm.vue'
 import FileConfigForm from './FileConfigForm.vue'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'

+ 1 - 1
src/views/infra/job/index.vue

@@ -147,7 +147,7 @@
   <!-- 表单弹窗:查看 -->
   <!-- 表单弹窗:查看 -->
   <JobDetail ref="detailRef" />
   <JobDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="Job">
+<script setup lang="ts" name="InfraJob">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { checkPermi } from '@/utils/permission'
 import { checkPermi } from '@/utils/permission'
 import JobForm from './JobForm.vue'
 import JobForm from './JobForm.vue'

+ 1 - 1
src/views/infra/job/logger/index.vue

@@ -121,7 +121,7 @@
   <!-- 表单弹窗:查看 -->
   <!-- 表单弹窗:查看 -->
   <JobLogDetail ref="detailRef" />
   <JobLogDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="JobLog">
+<script setup lang="ts" name="InfraJobLog">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { formatDate } from '@/utils/formatTime'
 import { formatDate } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 7 - 111
src/views/infra/redis/index.vue

@@ -4,6 +4,7 @@
 
 
   <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
   <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
     <el-row>
     <el-row>
+      <!-- 基本信息 -->
       <el-col :span="24" class="card-box" shadow="hover">
       <el-col :span="24" class="card-box" shadow="hover">
         <el-card>
         <el-card>
           <el-descriptions title="基本信息" :column="6" border>
           <el-descriptions title="基本信息" :column="6" border>
@@ -47,106 +48,33 @@
           </el-descriptions>
           </el-descriptions>
         </el-card>
         </el-card>
       </el-col>
       </el-col>
+      <!-- 命令统计 -->
       <el-col :span="12" class="mt-3">
       <el-col :span="12" class="mt-3">
         <el-card :gutter="12" shadow="hover">
         <el-card :gutter="12" shadow="hover">
           <div ref="commandStatsRef" class="h-88"></div>
           <div ref="commandStatsRef" class="h-88"></div>
         </el-card>
         </el-card>
       </el-col>
       </el-col>
+      <!-- 内存使用量统计 -->
       <el-col :span="12" class="mt-3">
       <el-col :span="12" class="mt-3">
         <el-card class="ml-3" :gutter="12" shadow="hover">
         <el-card class="ml-3" :gutter="12" shadow="hover">
           <div ref="usedmemory" class="h-88"></div>
           <div ref="usedmemory" class="h-88"></div>
         </el-card>
         </el-card>
       </el-col>
       </el-col>
     </el-row>
     </el-row>
-    <el-row class="mt-3">
-      <el-col :span="24" class="card-box" shadow="hover">
-        <el-card>
-          <el-table
-            v-loading="keyListLoad"
-            :data="keyList"
-            row-key="id"
-            @row-click="openKeyTemplate"
-          >
-            <el-table-column prop="keyTemplate" label="Key 模板" width="200" />
-            <el-table-column prop="keyType" label="Key 类型" width="100" />
-            <el-table-column prop="valueType" label="Value 类型" />
-            <el-table-column prop="timeoutType" label="超时时间" width="200">
-              <template #default="{ row }">
-                <DictTag :type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE" :value="row?.timeoutType" />
-                <span v-if="row?.timeout > 0">({{ row?.timeout / 1000 }} 秒)</span>
-              </template>
-            </el-table-column>
-            <el-table-column prop="memo" label="备注" />
-          </el-table>
-        </el-card>
-      </el-col>
-    </el-row>
   </el-scrollbar>
   </el-scrollbar>
-  <XModal v-model="dialogVisible" :title="keyTemplate + ' 模板'">
-    <el-row>
-      <el-col :span="14" class="mt-3">
-        <el-card shadow="always">
-          <template #header>
-            <div class="card-header">
-              <span>键名列表</span>
-            </div>
-          </template>
-          <el-table :data="cacheKeys" style="width: 100%" @row-click="handleKeyValue">
-            <el-table-column label="缓存键名" align="center" :show-overflow-tooltip="true">
-              <template #default="{ row }">
-                {{ row }}
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" align="right" width="60">
-              <template #default="{ row }">
-                <XTextButton preIcon="ep:delete" @click="handleDeleteKey(row)" />
-              </template>
-            </el-table-column>
-          </el-table>
-        </el-card>
-      </el-col>
-      <el-col :span="10" class="mt-3">
-        <el-card shadow="always">
-          <template #header>
-            <div class="card-header">
-              <span>缓存内容</span>
-              <XTextButton
-                preIcon="ep:refresh"
-                title="清理全部"
-                @click="handleDeleteKeys(keyTemplate)"
-                class="float-right p-1"
-              />
-            </div>
-          </template>
-          <el-descriptions :column="1">
-            <el-descriptions-item label="缓存键名:">{{ cacheForm.key }}</el-descriptions-item>
-            <el-descriptions-item label="缓存内容:">{{ cacheForm.value }}</el-descriptions-item>
-          </el-descriptions>
-        </el-card>
-      </el-col>
-    </el-row>
-  </XModal>
 </template>
 </template>
-<script setup lang="ts" name="Redis">
+<script setup lang="ts" name="InfraRedis">
 import * as echarts from 'echarts'
 import * as echarts from 'echarts'
-import { DICT_TYPE } from '@/utils/dict'
-
 import * as RedisApi from '@/api/infra/redis'
 import * as RedisApi from '@/api/infra/redis'
-import { RedisKeyInfo, RedisMonitorInfoVO } from '@/api/infra/redis/types'
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
+import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
 
 
 const cache = ref<RedisMonitorInfoVO>()
 const cache = ref<RedisMonitorInfoVO>()
-const keyListLoad = ref(true)
-const keyList = ref<RedisKeyInfo[]>([])
+
 // 基本信息
 // 基本信息
 const readRedisInfo = async () => {
 const readRedisInfo = async () => {
   const data = await RedisApi.getCache()
   const data = await RedisApi.getCache()
   cache.value = data
   cache.value = data
   loadEchartOptions(data.commandStats)
   loadEchartOptions(data.commandStats)
-  const redisKeysInfo = await RedisApi.getKeyDefineList()
-  keyList.value = redisKeysInfo
-  keyListLoad.value = false //加载完成
 }
 }
 // 图表
 // 图表
 const commandStatsRef = ref<HTMLElement>()
 const commandStatsRef = ref<HTMLElement>()
@@ -241,40 +169,8 @@ const loadEchartOptions = (stats) => {
     ]
     ]
   })
   })
 }
 }
-const dialogVisible = ref(false)
-const keyTemplate = ref('')
-const cacheKeys = ref()
-const cacheForm = ref<{
-  key: string
-  value: string
-}>({
-  key: '',
-  value: ''
-})
-const openKeyTemplate = async (row: RedisKeyInfo) => {
-  keyTemplate.value = row.keyTemplate
-  cacheKeys.value = await RedisApi.getKeyList(row.keyTemplate)
-  dialogVisible.value = true
-}
-const handleDeleteKey = async (row) => {
-  RedisApi.deleteKey(row)
-  message.success(t('common.delSuccess'))
-}
-const handleDeleteKeys = async (row) => {
-  RedisApi.deleteKeys(row)
-  message.success(t('common.delSuccess'))
-}
-const handleKeyValue = async (row) => {
-  const res = await RedisApi.getKeyValue(row)
-  cacheForm.value = res
-}
+
 onBeforeMount(() => {
 onBeforeMount(() => {
   readRedisInfo()
   readRedisInfo()
 })
 })
 </script>
 </script>
-<style scoped>
-.redis {
-  height: 600px;
-  max-height: 860px;
-}
-</style>

+ 19 - 4
src/views/infra/server/index.vue

@@ -1,10 +1,25 @@
 <template>
 <template>
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
+
   <ContentWrap>
   <ContentWrap>
-    <IFrame :src="src" />
+    <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts" name="AdminServer">
-const BASE_URL = import.meta.env.VITE_BASE_URL
-const src = ref(BASE_URL + '/admin/applications')
+<script setup lang="ts" name="InfraAdminServer">
+import * as ConfigApi from '@/api/infra/config'
+
+const loading = ref(true) // 是否加载中
+const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
+
+/** 初始化 */
+onMounted(async () => {
+  try {
+    const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
+    if (data && data.length > 0) {
+      src.value = data
+    }
+  } finally {
+    loading.value = false
+  }
+})
 </script>
 </script>

+ 18 - 2
src/views/infra/skywalking/index.vue

@@ -1,9 +1,25 @@
 <template>
 <template>
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
   <doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
+
   <ContentWrap>
   <ContentWrap>
-    <IFrame :src="src" />
+    <IFrame v-if="!loading" v-loading="loading" :src="src" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts" name="Skywalking">
+<script setup lang="ts" name="InfraSkyWalking">
+import * as ConfigApi from '@/api/infra/config'
+
+const loading = ref(true) // 是否加载中
 const src = ref('http://skywalking.shop.iocoder.cn')
 const src = ref('http://skywalking.shop.iocoder.cn')
+
+/** 初始化 */
+onMounted(async () => {
+  try {
+    const data = await ConfigApi.getConfigKey('url.skywalking')
+    if (data && data.length > 0) {
+      src.value = data
+    }
+  } finally {
+    loading.value = false
+  }
+})
 </script>
 </script>

+ 18 - 4
src/views/infra/swagger/index.vue

@@ -5,8 +5,22 @@
     <IFrame :src="src" />
     <IFrame :src="src" />
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
-<script setup lang="ts" name="Swagger">
-const BASE_URL = import.meta.env.VITE_BASE_URL
-// const src = ref(BASE_URL + '/doc.html')
-const src = ref(BASE_URL + '/swagger-ui')
+<script setup lang="ts" name="InfraSwagger">
+import * as ConfigApi from '@/api/infra/config'
+
+const loading = ref(true) // 是否加载中
+const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
+// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
+
+/** 初始化 */
+onMounted(async () => {
+  try {
+    const data = await ConfigApi.getConfigKey('url.swagger')
+    if (data && data.length > 0) {
+      src.value = data
+    }
+  } finally {
+    loading.value = false
+  }
+})
 </script>
 </script>

+ 120 - 0
src/views/mall/product/brand/BrandForm.vue

@@ -0,0 +1,120 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <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="picUrl">
+        <UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" />
+      </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 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="品牌描述">
+        <el-input v-model="formData.description" type="textarea" placeholder="请输入品牌描述" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts" name="ProductBrandForm">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { CommonStatusEnum } from '@/utils/constants'
+import * as ProductBrandApi from '@/api/mall/product/brand'
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  name: '',
+  picUrl: '',
+  status: CommonStatusEnum.ENABLE,
+  description: ''
+})
+const formRules = reactive({
+  name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
+  picUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '品牌排序不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ProductBrandApi.getBrand(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+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 ProductBrandApi.BrandVO
+    if (formType.value === 'create') {
+      await ProductBrandApi.createBrand(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ProductBrandApi.updateBrand(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    picUrl: '',
+    status: CommonStatusEnum.ENABLE,
+    description: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 177 - 0
src/views/mall/product/brand/index.vue

@@ -0,0 +1,177 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <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="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"
+        />
+      </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"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['product:brand:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
+      <el-table-column label="品牌名称" prop="name" sortable />
+      <el-table-column label="品牌图片" align="center" prop="picUrl">
+        <template #default="scope">
+          <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="品牌图片" class="h-100px" />
+        </template>
+      </el-table-column>
+      <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="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['product:brand:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['product:brand: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>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <BrandForm ref="formRef" @success="getList" />
+</template>
+<script setup lang="ts" name="ProductBrand">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as ProductBrandApi from '@/api/mall/product/brand'
+import BrandForm from './BrandForm.vue'
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref<any[]>([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  status: undefined,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ProductBrandApi.getBrandParam(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ProductBrandApi.deleteBrand(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 1 - 1
src/views/mall/product/category/CategoryForm.vue

@@ -50,7 +50,7 @@
     </template>
     </template>
   </Dialog>
   </Dialog>
 </template>
 </template>
-<script setup lang="ts">
+<script setup lang="ts" name="ProductCategory">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { CommonStatusEnum } from '@/utils/constants'
 import { CommonStatusEnum } from '@/utils/constants'
 import { handleTree } from '@/utils/tree'
 import { handleTree } from '@/utils/tree'

+ 1 - 1
src/views/mall/product/property/index.vue

@@ -92,7 +92,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <PropertyForm ref="formRef" @success="getList" />
   <PropertyForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Config">
+<script setup lang="ts" name="ProductProperty">
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as PropertyApi from '@/api/mall/product/property'
 import * as PropertyApi from '@/api/mall/product/property'
 import PropertyForm from './PropertyForm.vue'
 import PropertyForm from './PropertyForm.vue'

+ 1 - 1
src/views/mall/product/property/value/index.vue

@@ -88,7 +88,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <ValueForm ref="formRef" @success="getList" />
   <ValueForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Config">
+<script setup lang="ts" name="ProductPropertyValue">
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as PropertyApi from '@/api/mall/product/property'
 import * as PropertyApi from '@/api/mall/product/property'
 import ValueForm from './ValueForm.vue'
 import ValueForm from './ValueForm.vue'

+ 21 - 27
src/views/mp/components/wx-material-select/main.vue

@@ -13,15 +13,14 @@
           <img class="material-img" :src="item.url" />
           <img class="material-img" :src="item.url" />
           <p class="item-name">{{ item.name }}</p>
           <p class="item-name">{{ item.name }}</p>
           <el-row class="ope-row">
           <el-row class="ope-row">
-            <el-button type="success" @click="selectMaterialFun(item)"
-              >选择 <Icon icon="ep:circle-check" />
+            <el-button type="success" @click="selectMaterialFun(item)">
+              选择 <Icon icon="ep:circle-check" />
             </el-button>
             </el-button>
           </el-row>
           </el-row>
         </div>
         </div>
       </div>
       </div>
       <!-- 分页组件 -->
       <!-- 分页组件 -->
-      <pagination
-        v-show="total > 0"
+      <Pagination
         :total="total"
         :total="total"
         v-model:page="queryParams.pageNo"
         v-model:page="queryParams.pageNo"
         v-model:limit="queryParams.pageSize"
         v-model:limit="queryParams.pageSize"
@@ -39,18 +38,16 @@
             <WxVoicePlayer :url="scope.row.url" />
             <WxVoicePlayer :url="scope.row.url" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="上传时间" align="center" prop="createTime" width="180">
-          <template #default="scope">
-            <span>{{ formatDate(scope.row.createTime) }}</span>
-          </template>
-        </el-table-column>
         <el-table-column
         <el-table-column
-          label="操作"
+          label="上传时间"
           align="center"
           align="center"
-          fixed="right"
-          class-name="small-padding fixed-width"
-        >
+          prop="createTime"
+          width="180"
+          :formatter="dateFormatter"
+        />
+        <el-table-column label="操作" align="center" fixed="right">
           <template #default="scope">
           <template #default="scope">
+
             <el-button type="primary" link @click="selectMaterialFun(scope.row)"
             <el-button type="primary" link @click="selectMaterialFun(scope.row)"
               >选择<Icon icon="ep:plus" />
               >选择<Icon icon="ep:plus" />
             </el-button>
             </el-button>
@@ -58,8 +55,7 @@
         </el-table-column>
         </el-table-column>
       </el-table>
       </el-table>
       <!-- 分页组件 -->
       <!-- 分页组件 -->
-      <pagination
-        v-show="total > 0"
+      <Pagination
         :total="total"
         :total="total"
         v-model:page="queryParams.pageNo"
         v-model:page="queryParams.pageNo"
         v-model:limit="queryParams.pageSize"
         v-model:limit="queryParams.pageSize"
@@ -79,11 +75,13 @@
             <WxVideoPlayer :url="scope.row.url" />
             <WxVideoPlayer :url="scope.row.url" />
           </template>
           </template>
         </el-table-column>
         </el-table-column>
-        <el-table-column label="上传时间" align="center" prop="createTime" width="180">
-          <template #default="scope">
-            <span>{{ formatDate(scope.row.createTime) }}</span>
-          </template>
-        </el-table-column>
+        <el-table-column
+          label="上传时间"
+          align="center"
+          prop="createTime"
+          width="180"
+          :formatter="dateFormatter"
+        />
         <el-table-column
         <el-table-column
           label="操作"
           label="操作"
           align="center"
           align="center"
@@ -98,8 +96,7 @@
         </el-table-column>
         </el-table-column>
       </el-table>
       </el-table>
       <!-- 分页组件 -->
       <!-- 分页组件 -->
-      <pagination
-        v-show="total > 0"
+      <Pagination
         :total="total"
         :total="total"
         v-model:page="queryParams.pageNo"
         v-model:page="queryParams.pageNo"
         v-model:limit="queryParams.pageSize"
         v-model:limit="queryParams.pageSize"
@@ -121,8 +118,7 @@
         </div>
         </div>
       </div>
       </div>
       <!-- 分页组件 -->
       <!-- 分页组件 -->
-      <pagination
-        v-show="total > 0"
+      <Pagination
         :total="total"
         :total="total"
         v-model:page="queryParams.pageNo"
         v-model:page="queryParams.pageNo"
         v-model:limit="queryParams.pageSize"
         v-model:limit="queryParams.pageSize"
@@ -139,7 +135,7 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 import { getMaterialPage } from '@/api/mp/material'
 import { getMaterialPage } from '@/api/mp/material'
 import { getFreePublishPage } from '@/api/mp/freePublish'
 import { getFreePublishPage } from '@/api/mp/freePublish'
 import { getDraftPage } from '@/api/mp/draft'
 import { getDraftPage } from '@/api/mp/draft'
-import { dateFormatter, formatDate } from '@/utils/formatTime'
+import { dateFormatter } from '@/utils/formatTime'
 import { defineComponent, PropType } from 'vue'
 import { defineComponent, PropType } from 'vue'
 
 
 export default defineComponent({
 export default defineComponent({
@@ -244,7 +240,6 @@ export default defineComponent({
       getMaterialPageFun,
       getMaterialPageFun,
       getPage,
       getPage,
       formatDate,
       formatDate,
-      newsTypeRef,
       queryParams,
       queryParams,
       objDataRef,
       objDataRef,
       list,
       list,
@@ -254,7 +249,6 @@ export default defineComponent({
   }
   }
 })
 })
 </script>
 </script>
-
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 /*瀑布流样式*/
 /*瀑布流样式*/
 .waterfall {
 .waterfall {

+ 1 - 1
src/views/mp/components/wx-msg/main.vue

@@ -139,7 +139,7 @@ import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxLocation from '@/views/mp/components/wx-location/main.vue'
 import WxLocation from '@/views/mp/components/wx-location/main.vue'
 import WxMusic from '@/views/mp/components/wx-music/main.vue'
 import WxMusic from '@/views/mp/components/wx-music/main.vue'
-import { getUser } from '@/api/mp/mpuser'
+import { getUser } from '@/api/mp/user'
 import { defineComponent } from 'vue'
 import { defineComponent } from 'vue'
 
 
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗

+ 17 - 44
src/views/mp/components/wx-reply/main.vue

@@ -12,10 +12,7 @@
     <!-- 类型 1:文本 -->
     <!-- 类型 1:文本 -->
     <el-tab-pane name="text">
     <el-tab-pane name="text">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:document" />
-          文本
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
       </template>
       </template>
       <el-input
       <el-input
         type="textarea"
         type="textarea"
@@ -28,10 +25,7 @@
     <!-- 类型 2:图片 -->
     <!-- 类型 2:图片 -->
     <el-tab-pane name="image">
     <el-tab-pane name="image">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:picture" class="mr-5px" />
-          图片
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
       </template>
       </template>
       <!-- 情况一:已经选择好素材、或者上传好图片 -->
       <!-- 情况一:已经选择好素材、或者上传好图片 -->
       <div class="select-item" v-if="objDataRef.url">
       <div class="select-item" v-if="objDataRef.url">
@@ -39,7 +33,7 @@
         <p class="item-name" v-if="objDataRef.name">{{ objDataRef.name }}</p>
         <p class="item-name" v-if="objDataRef.name">{{ objDataRef.name }}</p>
         <el-row class="ope-row" justify="center">
         <el-row class="ope-row" justify="center">
           <el-button type="danger" circle @click="deleteObj">
           <el-button type="danger" circle @click="deleteObj">
-            <icon icon="ep:delete" />
+            <Icon icon="ep:delete" />
           </el-button>
           </el-button>
         </el-row>
         </el-row>
       </div>
       </div>
@@ -48,8 +42,7 @@
         <!-- 选择素材 -->
         <!-- 选择素材 -->
         <el-col :span="12" class="col-select">
         <el-col :span="12" class="col-select">
           <el-button type="success" @click="openMaterial">
           <el-button type="success" @click="openMaterial">
-            素材库选择
-            <icon icon="ep:circle-check" />
+            素材库选择 <Icon icon="ep:circle-check" />
           </el-button>
           </el-button>
           <el-dialog title="选择图片" v-model="dialogImageVisible" width="90%" append-to-body>
           <el-dialog title="选择图片" v-model="dialogImageVisible" width="90%" append-to-body>
             <WxMaterialSelect :obj-data="objDataRef" @select-material="selectMaterial" />
             <WxMaterialSelect :obj-data="objDataRef" @select-material="selectMaterial" />
@@ -70,10 +63,8 @@
             <el-button type="primary">上传图片</el-button>
             <el-button type="primary">上传图片</el-button>
             <template #tip>
             <template #tip>
               <span>
               <span>
-                <div class="el-upload__tip"
-                  >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div
-                ></span
-              >
+                <div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
+              </span>
             </template>
             </template>
           </el-upload>
           </el-upload>
         </el-col>
         </el-col>
@@ -82,12 +73,8 @@
     <!-- 类型 3:语音 -->
     <!-- 类型 3:语音 -->
     <el-tab-pane name="voice">
     <el-tab-pane name="voice">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:phone" />
-          语音
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
       </template>
       </template>
-
       <div class="select-item2" v-if="objDataRef.url">
       <div class="select-item2" v-if="objDataRef.url">
         <p class="item-name">{{ objDataRef.name }}</p>
         <p class="item-name">{{ objDataRef.name }}</p>
         <div class="item-infos">
         <div class="item-infos">
@@ -121,8 +108,8 @@
           >
           >
             <el-button type="primary">点击上传</el-button>
             <el-button type="primary">点击上传</el-button>
             <template #tip>
             <template #tip>
-              <div class="el-upload__tip"
-                >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
+              <div class="el-upload__tip">
+                格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
               </div>
               </div>
             </template>
             </template>
           </el-upload>
           </el-upload>
@@ -132,10 +119,7 @@
     <!-- 类型 4:视频 -->
     <!-- 类型 4:视频 -->
     <el-tab-pane name="video">
     <el-tab-pane name="video">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:share" />
-          视频
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
       </template>
       </template>
       <el-row>
       <el-row>
         <el-input
         <el-input
@@ -158,8 +142,7 @@
             <!-- 选择素材 -->
             <!-- 选择素材 -->
             <el-col :span="12">
             <el-col :span="12">
               <el-button type="success" @click="openMaterial">
               <el-button type="success" @click="openMaterial">
-                素材库选择
-                <icon icon="ep:circle-check" />
+                素材库选择 <Icon icon="ep:circle-check" />
               </el-button>
               </el-button>
               <el-dialog title="选择视频" v-model="dialogVideoVisible" width="90%" append-to-body>
               <el-dialog title="选择视频" v-model="dialogVideoVisible" width="90%" append-to-body>
                 <WxMaterialSelect :objData="objDataRef" @select-material="selectMaterial" />
                 <WxMaterialSelect :objData="objDataRef" @select-material="selectMaterial" />
@@ -177,10 +160,7 @@
                 :before-upload="beforeVideoUpload"
                 :before-upload="beforeVideoUpload"
                 :on-success="handleUploadSuccess"
                 :on-success="handleUploadSuccess"
               >
               >
-                <el-button type="primary"
-                  >新建视频
-                  <icon icon="ep:upload" />
-                </el-button>
+                <el-button type="primary">新建视频 <Icon icon="ep:upload" /></el-button>
               </el-upload>
               </el-upload>
             </el-col>
             </el-col>
           </el-row>
           </el-row>
@@ -190,17 +170,14 @@
     <!-- 类型 5:图文 -->
     <!-- 类型 5:图文 -->
     <el-tab-pane name="news">
     <el-tab-pane name="news">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:reading" />
-          图文
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
       </template>
       </template>
       <el-row>
       <el-row>
         <div class="select-item" v-if="objDataRef.articles?.length > 0">
         <div class="select-item" v-if="objDataRef.articles?.length > 0">
           <WxNews :articles="objDataRef.articles" />
           <WxNews :articles="objDataRef.articles" />
           <el-col class="ope-row">
           <el-col class="ope-row">
             <el-button type="danger" circle @click="deleteObj">
             <el-button type="danger" circle @click="deleteObj">
-              <icon icon="ep:delete" />
+              <Icon icon="ep:delete" />
             </el-button>
             </el-button>
           </el-col>
           </el-col>
         </div>
         </div>
@@ -208,8 +185,8 @@
         <el-col :span="24" v-if="!objDataRef.content">
         <el-col :span="24" v-if="!objDataRef.content">
           <el-row style="text-align: center" align="middle">
           <el-row style="text-align: center" align="middle">
             <el-col :span="24">
             <el-col :span="24">
-              <el-button type="success" @click="openMaterial"
-                >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
+              <el-button type="success" @click="openMaterial">
+                {{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
                 <icon icon="ep:circle-check" />
                 <icon icon="ep:circle-check" />
               </el-button>
               </el-button>
             </el-col>
             </el-col>
@@ -227,10 +204,7 @@
     <!-- 类型 6:音乐 -->
     <!-- 类型 6:音乐 -->
     <el-tab-pane name="music">
     <el-tab-pane name="music">
       <template #label>
       <template #label>
-        <el-row align="middle">
-          <icon icon="ep:service" />
-          音乐
-        </el-row>
+        <el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
       </template>
       </template>
       <el-row align="middle" justify="center">
       <el-row align="middle" justify="center">
         <el-col :span="6">
         <el-col :span="6">
@@ -295,7 +269,6 @@
     </el-tab-pane>
     </el-tab-pane>
   </el-tabs>
   </el-tabs>
 </template>
 </template>
-
 <script lang="ts" name="WxReplySelect">
 <script lang="ts" name="WxReplySelect">
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'

+ 57 - 65
src/views/mp/draft/index.vue

@@ -1,40 +1,37 @@
 <template>
 <template>
-  <div class="app-container">
-    <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
-
-    <!-- 搜索工作栏 -->
-    <el-form :model="queryParams" ref="queryFormRef" size="small" :inline="true" label-width="68px">
+  <doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
+
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="公众号" prop="accountId">
       <el-form-item label="公众号" prop="accountId">
         <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
         <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
           <el-option
           <el-option
             v-for="item in accountList"
             v-for="item in accountList"
-            :key="parseInt(item.id)"
+            :key="item.id"
             :label="item.name"
             :label="item.name"
-            :value="parseInt(item.id)"
+            :value="item.id"
           />
           />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+        <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+        <el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
+          <Icon icon="ep:plus" />新增
+        </el-button>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
+  </ContentWrap>
 
 
-    <!-- 操作工具栏 -->
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          size="small"
-          @click="handleAdd"
-          v-hasPermi="['mp:draft:create']"
-          ><Icon icon="ep:plus" />新增
-        </el-button>
-      </el-col>
-    </el-row>
-
-    <!-- 列表 -->
+  <!-- 列表 -->
+  <ContentWrap>
     <div class="waterfall" v-loading="loading">
     <div class="waterfall" v-loading="loading">
       <template v-for="item in list" :key="item.articleId">
       <template v-for="item in list" :key="item.articleId">
         <div class="waterfall-item" v-if="item.content && item.content.newsItem">
         <div class="waterfall-item" v-if="item.content && item.content.newsItem">
@@ -46,35 +43,40 @@
               circle
               circle
               @click="handlePublish(item)"
               @click="handlePublish(item)"
               v-hasPermi="['mp:free-publish:submit']"
               v-hasPermi="['mp:free-publish:submit']"
-              ><Icon icon="fa:upload"
-            /></el-button>
+            >
+              <Icon icon="fa:upload" />
+            </el-button>
             <el-button
             <el-button
               type="primary"
               type="primary"
               circle
               circle
               @click="handleUpdate(item)"
               @click="handleUpdate(item)"
               v-hasPermi="['mp:draft:update']"
               v-hasPermi="['mp:draft:update']"
-              ><Icon icon="ep:edit"
-            /></el-button>
+            >
+              <Icon icon="ep:edit" />
+            </el-button>
             <el-button
             <el-button
               type="danger"
               type="danger"
               circle
               circle
               @click="handleDelete(item)"
               @click="handleDelete(item)"
               v-hasPermi="['mp:draft:delete']"
               v-hasPermi="['mp:draft:delete']"
-              ><Icon icon="ep:delete"
-            /></el-button>
+            >
+              <Icon icon="ep:delete" />
+            </el-button>
           </el-row>
           </el-row>
         </div>
         </div>
       </template>
       </template>
     </div>
     </div>
     <!-- 分页记录 -->
     <!-- 分页记录 -->
-    <pagination
-      v-show="total > 0"
+    <Pagination
       :total="total"
       :total="total"
       v-model:page="queryParams.pageNo"
       v-model:page="queryParams.pageNo"
       v-model:limit="queryParams.pageSize"
       v-model:limit="queryParams.pageSize"
       @pagination="getList"
       @pagination="getList"
     />
     />
+  </ContentWrap>
 
 
+  <!-- TODO @Dhb52:迁移成独立路由 -->
+  <div class="app-container">
     <!-- 添加或修改草稿对话框 -->
     <!-- 添加或修改草稿对话框 -->
     <Teleport to="body">
     <Teleport to="body">
       <el-dialog
       <el-dialog
@@ -245,49 +247,39 @@
           </el-row>
           </el-row>
         </div>
         </div>
         <template #footer>
         <template #footer>
-          <div class="dialog-footer">
-            <el-button @click="dialogNewsVisible = false">取 消</el-button>
-            <el-button type="primary" @click="submitForm">提 交</el-button>
-          </div>
+          <el-button @click="dialogNewsVisible = false">取 消</el-button>
+          <el-button type="primary" @click="submitForm">提 交</el-button>
         </template>
         </template>
       </el-dialog>
       </el-dialog>
     </Teleport>
     </Teleport>
   </div>
   </div>
 </template>
 </template>
-
 <script setup name="MpDraft">
 <script setup name="MpDraft">
-import { ref, onMounted, reactive, nextTick } from 'vue'
 import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
 import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 import { getAccessToken } from '@/utils/auth'
 import { getAccessToken } from '@/utils/auth'
-import { createDraft, deleteDraft, getDraftPage, updateDraft } from '@/api/mp/draft'
-import { getSimpleAccountList } from '@/api/mp/account'
-import { submitFreePublish } from '@/api/mp/freePublish'
+import * as MpAccountApi from '@/api/mp/account'
+import * as MpDraftApi from '@/api/mp/draft'
+import * as MpFreePublishApi from '@/api/mp/freePublish'
+const message = useMessage() // 消息
 // 可以用改本地数据模拟,避免API调用超限
 // 可以用改本地数据模拟,避免API调用超限
 // import drafts from './mock'
 // import drafts from './mock'
 
 
-const BASE_URL = import.meta.env.VITE_BASE_URL
-
-const message = useMessage()
-
-const materialSelectRef = ref()
-const queryFormRef = ref()
-
-// 遮罩层
-const loading = ref(false)
-// 显示搜索条件
-// 总条数
-const total = ref(0)
-// 数据列表
-const list = ref([])
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
 const queryParams = reactive({
 const queryParams = reactive({
   pageNo: 1,
   pageNo: 1,
   pageSize: 10,
   pageSize: 10,
   accountId: undefined
   accountId: undefined
 })
 })
+const queryFormRef = ref() // 搜索的表单
+const accountList = ref([]) // 公众号账号列表
 
 
 // ========== 文件上传 ==========
 // ========== 文件上传 ==========
+const materialSelectRef = ref()
+const BASE_URL = import.meta.env.VITE_BASE_URL
 const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
 const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
 const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
 const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
 const fileList = ref([])
 const fileList = ref([])
@@ -305,11 +297,10 @@ const dialogImageVisible = ref(false)
 const operateMaterial = ref('add')
 const operateMaterial = ref('add')
 const articlesMediaId = ref('')
 const articlesMediaId = ref('')
 const hackResetEditor = ref(false)
 const hackResetEditor = ref(false)
-// 公众号账号列表
-const accountList = ref([])
 
 
+/** 初始化 **/
 onMounted(async () => {
 onMounted(async () => {
-  accountList.value = await getSimpleAccountList()
+  accountList.value = await MpAccountApi.getSimpleAccountList()
   // 选中第一个
   // 选中第一个
   if (accountList.value.length > 0) {
   if (accountList.value.length > 0) {
     // @ts-ignore
     // @ts-ignore
@@ -335,7 +326,7 @@ const getList = async () => {
 
 
   loading.value = true
   loading.value = true
   try {
   try {
-    const drafts = await getDraftPage(queryParams)
+    const drafts = await MpDraftApi.getDraftPage(queryParams)
     drafts.list.forEach((item) => {
     drafts.list.forEach((item) => {
       const newsItem = item.content.newsItem
       const newsItem = item.content.newsItem
       // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面
       // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面
@@ -393,9 +384,10 @@ const handleUpdate = (item) => {
 
 
 /** 提交按钮 */
 /** 提交按钮 */
 const submitForm = () => {
 const submitForm = () => {
+  // TODO @Dhb52: 参考别的模块写法,改成 await 方式
   addMaterialLoading.value = true
   addMaterialLoading.value = true
   if (operateMaterial.value === 'add') {
   if (operateMaterial.value === 'add') {
-    createDraft(queryParams.accountId, articlesAdd.value)
+    MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
       .then(() => {
       .then(() => {
         message.notifySuccess('新增成功')
         message.notifySuccess('新增成功')
         dialogNewsVisible.value = false
         dialogNewsVisible.value = false
@@ -405,7 +397,7 @@ const submitForm = () => {
         addMaterialLoading.value = false
         addMaterialLoading.value = false
       })
       })
   } else {
   } else {
-    updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
+    MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
       .then(() => {
       .then(() => {
         message.notifySuccess('更新成功')
         message.notifySuccess('更新成功')
         dialogNewsVisible.value = false
         dialogNewsVisible.value = false
@@ -559,24 +551,24 @@ const handlePublish = async (item) => {
     '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。'
     '你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。'
   try {
   try {
     await message.confirm(content)
     await message.confirm(content)
-    await submitFreePublish(accountId, mediaId)
-    getList()
+    await MpFreePublishApi.submitFreePublish(accountId, mediaId)
     message.notifySuccess('发布成功')
     message.notifySuccess('发布成功')
+    await getList()
   } catch {}
   } catch {}
 }
 }
 
 
+/** 删除按钮操作 */
 const handleDelete = async (item) => {
 const handleDelete = async (item) => {
   const accountId = queryParams.accountId
   const accountId = queryParams.accountId
   const mediaId = item.mediaId
   const mediaId = item.mediaId
   try {
   try {
     await message.confirm('此操作将永久删除该草稿, 是否继续?')
     await message.confirm('此操作将永久删除该草稿, 是否继续?')
-    await deleteDraft(accountId, mediaId)
-    getList()
+    await MpDraftApi.deleteDraft(accountId, mediaId)
     message.notifySuccess('删除成功')
     message.notifySuccess('删除成功')
+    await getList()
   } catch {}
   } catch {}
 }
 }
 </script>
 </script>
-
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .pagination {
 .pagination {
   float: right;
   float: right;

+ 1 - 1
src/views/mp/freePublish/index.vue

@@ -59,7 +59,7 @@
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
 
 
-<script setup lang="ts" name="freePublish">
+<script setup lang="ts" name="MpFreePublish">
 import * as FreePublishApi from '@/api/mp/freePublish'
 import * as FreePublishApi from '@/api/mp/freePublish'
 import * as MpAccountApi from '@/api/mp/account'
 import * as MpAccountApi from '@/api/mp/account'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'

+ 43 - 53
src/views/mp/material/index.vue

@@ -1,32 +1,32 @@
 <template>
 <template>
-  <div class="app-container">
-    <doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
-
-    <!-- 搜索工作栏 -->
+  <doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
     <el-form
     <el-form
+      class="-mb-15px"
       :model="queryParams"
       :model="queryParams"
       ref="queryFormRef"
       ref="queryFormRef"
-      size="small"
       :inline="true"
       :inline="true"
-      v-show="showSearch"
       label-width="68px"
       label-width="68px"
     >
     >
       <el-form-item label="公众号" prop="accountId">
       <el-form-item label="公众号" prop="accountId">
-        <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
+        <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
           <el-option
           <el-option
-            v-for="item in accounts"
-            :key="parseInt(item.id)"
+            v-for="item in accountList"
+            :key="item.id"
             :label="item.name"
             :label="item.name"
-            :value="parseInt(item.id)"
+            :value="item.id"
           />
           />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
-        <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+        <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
+  </ContentWrap>
 
 
+  <ContentWrap>
     <el-tabs v-model="type" @tab-change="handleTabChange">
     <el-tabs v-model="type" @tab-change="handleTabChange">
       <!-- tab 1:图片  -->
       <!-- tab 1:图片  -->
       <el-tab-pane name="image">
       <el-tab-pane name="image">
@@ -44,11 +44,11 @@
             :before-upload="beforeImageUpload"
             :before-upload="beforeImageUpload"
             :on-success="handleUploadSuccess"
             :on-success="handleUploadSuccess"
           >
           >
-            <el-button size="small" type="primary">点击上传</el-button>
+            <el-button type="primary" plain>点击上传</el-button>
             <template #tip>
             <template #tip>
-              <span class="el-upload__tip" style="margin-left: 5px"
-                >支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</span
-              >
+              <span class="el-upload__tip" style="margin-left: 5px">
+                支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M
+              </span>
             </template>
             </template>
           </el-upload>
           </el-upload>
         </div>
         </div>
@@ -64,14 +64,14 @@
                 circle
                 circle
                 @click="handleDelete(item)"
                 @click="handleDelete(item)"
                 v-hasPermi="['mp:material:delete']"
                 v-hasPermi="['mp:material:delete']"
-                ><Icon icon="ep:delete"
-              /></el-button>
+              >
+                <Icon icon="ep:delete" />
+              </el-button>
             </el-row>
             </el-row>
           </div>
           </div>
         </div>
         </div>
         <!-- 分页组件 -->
         <!-- 分页组件 -->
-        <pagination
-          v-show="total > 0"
+        <Pagination
           :total="total"
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
           v-model:limit="queryParams.pageSize"
@@ -95,11 +95,11 @@
             :on-success="handleUploadSuccess"
             :on-success="handleUploadSuccess"
             :before-upload="beforeVoiceUpload"
             :before-upload="beforeVoiceUpload"
           >
           >
-            <el-button size="small" type="primary">点击上传</el-button>
+            <el-button type="primary" plain>点击上传</el-button>
             <template #tip>
             <template #tip>
-              <span class="el-upload__tip" style="margin-left: 5px"
-                >格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s</span
-              >
+              <span class="el-upload__tip" style="margin-left: 5px">
+                格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
+              </span>
             </template>
             </template>
           </el-upload>
           </el-upload>
         </div>
         </div>
@@ -118,24 +118,23 @@
           </el-table-column>
           </el-table-column>
           <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
             <template #default="scope">
             <template #default="scope">
-              <el-button type="primary" link size="small" plain @click="handleDownload(scope.row)"
-                ><Icon icon="ep:download" />下载</el-button
-              >
+              <el-button type="primary" link plain @click="handleDownload(scope.row)">
+                <Icon icon="ep:download" />下载
+              </el-button>
               <el-button
               <el-button
                 type="primary"
                 type="primary"
                 link
                 link
-                size="small"
                 plain
                 plain
                 @click="handleDelete(scope.row)"
                 @click="handleDelete(scope.row)"
                 v-hasPermi="['mp:material:delete']"
                 v-hasPermi="['mp:material:delete']"
-                ><Icon icon="ep:delete" />删除</el-button
               >
               >
+                <Icon icon="ep:delete" />删除
+              </el-button>
             </template>
             </template>
           </el-table-column>
           </el-table-column>
         </el-table>
         </el-table>
         <!-- 分页组件 -->
         <!-- 分页组件 -->
-        <pagination
-          v-show="total > 0"
+        <Pagination
           :total="total"
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
           v-model:limit="queryParams.pageSize"
@@ -149,7 +148,7 @@
           <span><Icon icon="ep:video-play" /> 视频</span>
           <span><Icon icon="ep:video-play" /> 视频</span>
         </template>
         </template>
         <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
         <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
-          <el-button size="small" type="primary" @click="handleAddVideo">新建视频</el-button>
+          <el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
         </div>
         </div>
         <!-- 新建视频的弹窗 -->
         <!-- 新建视频的弹窗 -->
         <el-dialog
         <el-dialog
@@ -220,14 +219,9 @@
               <span>{{ formatDate(scope.row.createTime) }}</span>
               <span>{{ formatDate(scope.row.createTime) }}</span>
             </template>
             </template>
           </el-table-column>
           </el-table-column>
-          <el-table-column
-            label="操作"
-            align="center"
-            fixed="right"
-            class-name="small-padding fixed-width"
-          >
+          <el-table-column label="操作" align="center" fixed="right">
             <template #default="scope">
             <template #default="scope">
-              <el-button type="primary" link size="small" plain @click="handleDownload(scope.row)"
+              <el-button type="primary" link plain @click="handleDownload(scope.row)"
                 ><Icon icon="ep:download" />下载</el-button
                 ><Icon icon="ep:download" />下载</el-button
               >
               >
               <el-button
               <el-button
@@ -237,14 +231,14 @@
                 plain
                 plain
                 @click="handleDelete(scope.row)"
                 @click="handleDelete(scope.row)"
                 v-hasPermi="['mp:material:delete']"
                 v-hasPermi="['mp:material:delete']"
-                ><Icon icon="ep:delete" />删除</el-button
               >
               >
+                <Icon icon="ep:delete" />删除
+              </el-button>
             </template>
             </template>
           </el-table-column>
           </el-table-column>
         </el-table>
         </el-table>
         <!-- 分页组件 -->
         <!-- 分页组件 -->
-        <pagination
-          v-show="total > 0"
+        <Pagination
           :total="total"
           :total="total"
           v-model:page="queryParams.pageNo"
           v-model:page="queryParams.pageNo"
           v-model:limit="queryParams.pageSize"
           v-model:limit="queryParams.pageSize"
@@ -252,11 +246,9 @@
         />
         />
       </el-tab-pane>
       </el-tab-pane>
     </el-tabs>
     </el-tabs>
-  </div>
+  </ContentWrap>
 </template>
 </template>
-
-<script setup>
-import { ref } from 'vue'
+<script setup name="MpMaterial">
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
 import { getSimpleAccountList } from '@/api/mp/account'
 import { getSimpleAccountList } from '@/api/mp/account'
@@ -275,8 +267,6 @@ const uploadVideoRef = ref()
 const type = ref('image')
 const type = ref('image')
 // 遮罩层
 // 遮罩层
 const loading = ref(false)
 const loading = ref(false)
-// 显示搜索条件
-const showSearch = ref(true)
 // 总条数
 // 总条数
 const total = ref(0)
 const total = ref(0)
 // 数据列表
 // 数据列表
@@ -308,14 +298,14 @@ const uploadRules = reactive({
 })
 })
 
 
 // 公众号账号列表
 // 公众号账号列表
-const accounts = ref([])
+const accountList = ref([])
 
 
 onMounted(() => {
 onMounted(() => {
   getSimpleAccountList().then((data) => {
   getSimpleAccountList().then((data) => {
-    accounts.value = data
+    accountList.value = data
     // 默认选中第一个
     // 默认选中第一个
-    if (accounts.value.length > 0) {
-      setAccountId(accounts.value[0].id)
+    if (accountList.value.length > 0) {
+      setAccountId(accountList.value[0].id)
     }
     }
     // 加载数据
     // 加载数据
     getList()
     getList()
@@ -365,8 +355,8 @@ const handleQuery = () => {
 const resetQuery = () => {
 const resetQuery = () => {
   queryFormRef.value?.resetFields()
   queryFormRef.value?.resetFields()
   // 默认选中第一个
   // 默认选中第一个
-  if (accounts.value.length > 0) {
-    setAccountId(accounts.value[0].id)
+  if (accountList.value.length > 0) {
+    setAccountId(accountList.value[0].id)
   }
   }
   handleQuery()
   handleQuery()
 }
 }

+ 33 - 39
src/views/mp/menu/index.vue

@@ -1,25 +1,32 @@
 <template>
 <template>
-  <div class="app-container">
-    <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
-
-    <!-- 搜索工作栏 -->
-    <el-form ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+  <doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
       <el-form-item label="公众号" prop="accountId">
       <el-form-item label="公众号" prop="accountId">
-        <el-select v-model="accountId" placeholder="请选择公众号">
+        <el-select v-model="accountId" placeholder="请选择公众号" class="!w-240px">
           <el-option
           <el-option
             v-for="item in accountList"
             v-for="item in accountList"
-            :key="parseInt(item.id)"
+            :key="item.id"
             :label="item.name"
             :label="item.name"
-            :value="parseInt(item.id)"
+            :value="item.id"
           />
           />
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
-        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+        <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
+  </ContentWrap>
 
 
+  <!-- 列表 -->
+  <ContentWrap>
     <div class="public-account-management clearfix" v-loading="loading">
     <div class="public-account-management clearfix" v-loading="loading">
       <!--左边配置菜单-->
       <!--左边配置菜单-->
       <div class="left">
       <div class="left">
@@ -63,7 +70,6 @@
           <el-button
           <el-button
             class="save_btn"
             class="save_btn"
             type="success"
             type="success"
-            size="small"
             @click="handleSave"
             @click="handleSave"
             v-hasPermi="['mp:menu:save']"
             v-hasPermi="['mp:menu:save']"
             >保存并发布菜单</el-button
             >保存并发布菜单</el-button
@@ -71,7 +77,6 @@
           <el-button
           <el-button
             class="save_btn"
             class="save_btn"
             type="danger"
             type="danger"
-            size="small"
             @click="handleDelete"
             @click="handleDelete"
             v-hasPermi="['mp:menu:delete']"
             v-hasPermi="['mp:menu:delete']"
             >清空菜单</el-button
             >清空菜单</el-button
@@ -82,9 +87,9 @@
       <div v-if="showRightFlag" class="right">
       <div v-if="showRightFlag" class="right">
         <div class="configure_page">
         <div class="configure_page">
           <div class="delete_btn">
           <div class="delete_btn">
-            <el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)"
-              >删除当前菜单<Icon icon="ep:delete"
-            /></el-button>
+            <el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)">
+              删除当前菜单<Icon icon="ep:delete" />
+            </el-button>
           </div>
           </div>
           <div>
           <div>
             <span>菜单名称:</span>
             <span>菜单名称:</span>
@@ -161,9 +166,9 @@
                 <div class="select-item" v-if="tempObj && tempObj.replyArticles">
                 <div class="select-item" v-if="tempObj && tempObj.replyArticles">
                   <WxNews :articles="tempObj.replyArticles" />
                   <WxNews :articles="tempObj.replyArticles" />
                   <el-row class="ope-row" justify="center" align="middle">
                   <el-row class="ope-row" justify="center" align="middle">
-                    <el-button type="danger" circle @click="deleteMaterial"
-                      ><icon icon="ep:delete"
-                    /></el-button>
+                    <el-button type="danger" circle @click="deleteMaterial">
+                      <icon icon="ep:delete" />
+                    </el-button>
                   </el-row>
                   </el-row>
                 </div>
                 </div>
                 <div v-else>
                 <div v-else>
@@ -197,33 +202,25 @@
         <p>请选择菜单配置</p>
         <p>请选择菜单配置</p>
       </div>
       </div>
     </div>
     </div>
-  </div>
+  </ContentWrap>
 </template>
 </template>
-
-<script setup>
-import { ref, nextTick } from 'vue'
+<script setup name="MpMenu">
+import { handleTree } from '@/utils/tree'
 import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
 import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxNews from '@/views/mp/components/wx-news/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
 import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu'
 import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu'
-import { getSimpleAccountList } from '@/api/mp/account'
-import { handleTree } from '@/utils/tree'
+import * as MpAccountApi from '@/api/mp/account'
 import menuOptions from './menuOptions'
 import menuOptions from './menuOptions'
-
-const message = useMessage()
+const message = useMessage() // 消息
 
 
 // ======================== 列表查询 ========================
 // ======================== 列表查询 ========================
-// 遮罩层
-const loading = ref(true)
-// 显示搜索条件
-const showSearch = ref(true)
-// 公众号Id
-const accountId = ref(undefined)
-// 公众号名
-const name = ref('')
+const loading = ref(true) // 遮罩层
+const accountId = ref(undefined) // 公众号Id
+const name = ref('') // 公众号名
 const menuList = ref({ children: [] })
 const menuList = ref({ children: [] })
+const accountList = ref([]) // 公众号账号列表
 
 
-// const menuList = ref(menuListData)
 // ======================== 菜单操作 ========================
 // ======================== 菜单操作 ========================
 const isActive = ref(-1) // 一级菜单点中样式
 const isActive = ref(-1) // 一级菜单点中样式
 const isSubMenuActive = ref(-1) // 一级菜单点中样式
 const isSubMenuActive = ref(-1) // 一级菜单点中样式
@@ -241,11 +238,8 @@ const tempSelfObj = ref({
 })
 })
 const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
 const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
 
 
-// 公众号账号列表
-const accountList = ref([])
-
 onMounted(async () => {
 onMounted(async () => {
-  accountList.value = await getSimpleAccountList()
+  accountList.value = await MpAccountApi.getSimpleAccountList()
   // 选中第一个
   // 选中第一个
   if (accountList.value.length > 0) {
   if (accountList.value.length > 0) {
     // @ts-ignore
     // @ts-ignore

+ 0 - 0
src/views/mp/menu/menuOptions.js → src/views/mp/menu/menuOptions.ts


+ 0 - 293
src/views/mp/mpuser/index.vue

@@ -1,293 +0,0 @@
-<template>
-  <div class="app-container">
-    <doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
-
-    <!-- 搜索工作栏 -->
-    <el-form
-      :model="queryParams"
-      ref="queryFormRef"
-      size="small"
-      :inline="true"
-      v-show="showSearch"
-      label-width="68px"
-    >
-      <el-form-item label="公众号" prop="accountId">
-        <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
-          <el-option
-            v-for="item in accounts"
-            :key="parseInt(item.id)"
-            :label="item.name"
-            :value="parseInt(item.id)"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="用户标识" prop="openid">
-        <el-input
-          v-model="queryParams.openid"
-          placeholder="请输入用户标识"
-          clearable
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="昵称" prop="nickname">
-        <el-input
-          v-model="queryParams.nickname"
-          placeholder="请输入昵称"
-          clearable
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
-        <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
-      </el-form-item>
-    </el-form>
-
-    <!-- 操作工具栏 -->
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="info"
-          plain
-          icon="el-icon-refresh"
-          size="small"
-          @click="handleSync"
-          v-hasPermi="['mp:user:sync']"
-          >同步
-        </el-button>
-      </el-col>
-      <!-- <right-toolbar :showSearch="showSearch" @query-table="getList" /> -->
-    </el-row>
-
-    <!-- 列表 -->
-    <ContentWrap>
-      <el-table v-loading="loading" :data="list">
-        <el-table-column label="编号" align="center" prop="id" />
-        <el-table-column label="用户标识" align="center" prop="openid" width="260" />
-        <el-table-column label="昵称" align="center" prop="nickname" />
-        <el-table-column label="备注" align="center" prop="remark" />
-        <el-table-column label="标签" align="center" prop="tagIds" width="200">
-          <template #default="scope">
-            <span v-for="(tagId, index) in scope.row.tagIds" :key="index">
-              <el-tag>{{ tags.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
-            </span>
-          </template>
-        </el-table-column>
-        <el-table-column label="订阅状态" align="center" prop="subscribeStatus">
-          <template #default="scope">
-            <el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
-            <el-tag v-else type="danger">未订阅</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column label="订阅时间" align="center" prop="subscribeTime" width="180">
-          <template #default="scope">
-            <span>{{ formatDate(scope.row.subscribeTime) }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" align="center">
-          <template #default="scope">
-            <div class="flex justify-center items-center">
-              <el-button
-                type="primary"
-                link
-                @click="handleUpdate(scope.row)"
-                v-hasPermi="['mp:user:update']"
-              >
-                <Icon icon="ep:edit" />修改
-              </el-button>
-            </div>
-          </template>
-        </el-table-column>
-      </el-table>
-    </ContentWrap>
-
-    <!-- 分页组件 -->
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      v-model:page="queryParams.pageNo"
-      v-model:limit="queryParams.pageSize"
-      @pagination="getList"
-    />
-
-    <!-- 对话框(添加 / 修改) -->
-    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
-      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="昵称" prop="nickname">
-          <el-input v-model="form.nickname" placeholder="请输入昵称" />
-        </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <el-input v-model="form.remark" placeholder="请输入备注" />
-        </el-form-item>
-        <el-form-item label="标签" prop="tagIds">
-          <el-select v-model="form.tagIds" multiple clearable placeholder="请选择标签">
-            <el-option
-              v-for="item in tags"
-              :key="parseInt(item.tagId)"
-              :label="item.name"
-              :value="parseInt(item.tagId)"
-            />
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script lang="ts" setup name="MpUser">
-import { ref, reactive } from 'vue'
-import { updateUser, getUser, getUserPage, syncUser } from '@/api/mp/mpuser'
-import { getSimpleAccountList } from '@/api/mp/account'
-import { getSimpleTagList } from '@/api/mp/tag'
-import { formatDate } from '@/utils/formatTime'
-
-const message = useMessage()
-
-const formRef = ref()
-const queryFormRef = ref()
-
-// 遮罩层
-const loading = ref(true)
-// 显示搜索条件
-const showSearch = ref(true)
-// 总条数
-const total = ref(0)
-// 微信公众号粉丝列表
-const list = ref([])
-// 弹出层标题
-const title = ref('')
-// 是否显示弹出层
-const open = ref(false)
-// 查询参数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  accountId: null,
-  openid: null,
-  nickname: null
-})
-// 表单参数
-const form = ref({})
-// 表单校验
-const rules = ref({})
-
-// 公众号账号列表
-const accounts = ref([])
-// 公众号标签列表
-const tags = ref([])
-
-onMounted(() => {
-  getSimpleAccountList().then((data) => {
-    accounts.value = data
-    // 默认选中第一个
-    if (accounts.value.length > 0) {
-      queryParams.accountId = accounts.value[0].id
-    }
-    // 加载数据
-    getList()
-  })
-
-  // 加载标签
-  getSimpleTagList().then((data) => {
-    tags.value = data
-  })
-})
-
-/** 查询列表 */
-const getList = () => {
-  // 如果没有选中公众号账号,则进行提示。
-  if (!queryParams.accountId) {
-    message.error('未选中公众号,无法查询用户')
-    return false
-  }
-
-  loading.value = true
-  // 处理查询参数
-  let params = { ...queryParams }
-  // 执行查询
-  getUserPage(params)
-    .then((data) => {
-      list.value = data.list
-      total.value = data.total
-    })
-    .finally(() => {
-      loading.value = false
-    })
-}
-
-/** 取消按钮 */
-const cancel = () => {
-  open.value = false
-  reset()
-}
-
-/** 表单重置 */
-const reset = () => {
-  form.value = {
-    id: undefined,
-    nickname: undefined,
-    remark: undefined,
-    tagIds: []
-  }
-  formRef.value?.resetFields()
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  // 默认选中第一个
-  if (accounts.value.length > 0) {
-    queryParams.accountId = accounts.value[0].id
-  }
-  handleQuery()
-}
-
-/** 修改按钮操作 */
-const handleUpdate = (row) => {
-  reset()
-  getUser(row.id).then((data) => {
-    form.value = data
-    open.value = true
-    title.value = '修改公众号粉丝'
-  })
-}
-
-/** 提交按钮 */
-const submitForm = () => {
-  formRef.value.validate((valid) => {
-    if (!valid) {
-      return
-    }
-    // 修改的提交
-    if (form.value.id != null) {
-      updateUser(form.value).then(() => {
-        message.success('修改成功')
-        open.value = false
-        getList()
-      })
-    }
-  })
-}
-
-/** 同步标签 */
-const handleSync = async () => {
-  const accountId = queryParams.accountId
-  try {
-    await message.confirm('是否确认同步粉丝?')
-    await syncUser(accountId)
-    message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
-  } catch {}
-}
-</script>

+ 99 - 0
src/views/mp/user/UserForm.vue

@@ -0,0 +1,99 @@
+<template>
+  <Dialog title="修改" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="昵称" prop="nickname">
+        <el-input v-model="formData.nickname" 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="tagIds">
+        <el-select v-model="formData.tagIds" multiple clearable placeholder="请选择标签">
+          <el-option
+            v-for="item in tagList"
+            :key="item.tagId"
+            :label="item.name"
+            :value="item.tagId"
+          />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import * as MpTagApi from '@/api/mp/tag'
+import * as MpUserApi from '@/api/mp/user'
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const formData = ref({
+  id: undefined,
+  nickname: undefined,
+  remark: undefined,
+  tagIds: []
+})
+const formRules = reactive({}) // 表单的校验
+const formRef = ref() // 表单 Ref
+const tagList = ref([]) // 公众号标签列表
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  dialogVisible.value = true
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await MpUserApi.getUser(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 加载标签
+  tagList.value = await MpTagApi.getSimpleTagList()
+}
+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 {
+    await MpUserApi.updateUser(formData.value)
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    nickname: undefined,
+    remark: undefined,
+    tagIds: []
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 187 - 0
src/views/mp/user/index.vue

@@ -0,0 +1,187 @@
+<template>
+  <doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
+
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="公众号" prop="accountId">
+        <el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
+          <el-option
+            v-for="item in accountList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="用户标识" prop="openid">
+        <el-input
+          v-model="queryParams.openid"
+          placeholder="请输入用户标识"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="昵称" prop="nickname">
+        <el-input
+          v-model="queryParams.nickname"
+          placeholder="请输入昵称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
+        <el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
+          <Icon icon="ep:refresh" 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="openid" width="260" />
+      <el-table-column label="昵称" align="center" prop="nickname" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="标签" align="center" prop="tagIds" width="200">
+        <template #default="scope">
+          <span v-for="(tagId, index) in scope.row.tagIds" :key="index">
+            <el-tag>{{ tagList.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="订阅状态" align="center" prop="subscribeStatus">
+        <template #default="scope">
+          <el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
+          <el-tag v-else type="danger">未订阅</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="订阅时间"
+        align="center"
+        prop="subscribeTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            type="primary"
+            link
+            @click="openForm(scope.row.id)"
+            v-hasPermi="['mp:user:update']"
+          >
+            修改
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:修改 -->
+  <UserForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup name="MpUser">
+import { dateFormatter } from '@/utils/formatTime'
+import * as MpAccountApi from '@/api/mp/account'
+import * as MpUserApi from '@/api/mp/user'
+import * as MpTagApi from '@/api/mp/tag'
+import UserForm from './UserForm.vue'
+const message = useMessage() // 消息
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  accountId: null,
+  openid: null,
+  nickname: null
+})
+const queryFormRef = ref() // 搜索的表单
+const accountList = ref([]) // 公众号账号列表
+const tagList = ref([]) // 公众号标签列表
+
+/** 查询列表 */
+const getList = async () => {
+  // 如果没有选中公众号账号,则进行提示。
+  if (!queryParams.accountId) {
+    message.error('未选中公众号,无法查询用户')
+    return false
+  }
+  try {
+    loading.value = true
+    const data = await MpUserApi.getUserPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  // 默认选中第一个
+  if (accountList.value.length > 0) {
+    queryParams.accountId = accountList.value[0].id
+  }
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id: number) => {
+  formRef.value.open(id)
+}
+
+/** 同步标签 */
+const handleSync = async () => {
+  const accountId = queryParams.accountId
+  try {
+    await message.confirm('是否确认同步粉丝?')
+    await MpUserApi.syncUser(accountId)
+    message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
+    await getList()
+  } catch {}
+}
+
+/** 初始化 */
+onMounted(async () => {
+  // 加载标签
+  tagList.value = await MpTagApi.getSimpleTagList()
+
+  // 加载账号
+  accountList.value = await MpAccountApi.getSimpleAccountList()
+  if (accountList.value.length > 0) {
+    queryParams.accountId = accountList.value[0].id
+  }
+  await getList()
+})
+</script>

+ 1 - 1
src/views/pay/app/index.vue

@@ -75,7 +75,7 @@
     </template>
     </template>
   </XModal>
   </XModal>
 </template>
 </template>
-<script setup lang="ts" name="App">
+<script setup lang="ts" name="PayApp">
 import type { FormExpose } from '@/components/Form'
 import type { FormExpose } from '@/components/Form'
 import { rules, allSchemas } from './app.data'
 import { rules, allSchemas } from './app.data'
 import * as AppApi from '@/api/pay/app'
 import * as AppApi from '@/api/pay/app'

+ 1 - 1
src/views/pay/merchant/index.vue

@@ -137,7 +137,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <MerchantForm ref="formRef" @success="getList" />
   <MerchantForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Merchant">
+<script setup lang="ts" name="PayMerchant">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { CommonStatusEnum } from '@/utils/constants'
 import { CommonStatusEnum } from '@/utils/constants'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'

+ 1 - 1
src/views/pay/order/index.vue

@@ -41,7 +41,7 @@
     </template>
     </template>
   </XModal>
   </XModal>
 </template>
 </template>
-<script setup lang="ts" name="Order">
+<script setup lang="ts" name="PayOrder">
 import { allSchemas } from './order.data'
 import { allSchemas } from './order.data'
 import * as OrderApi from '@/api/pay/order'
 import * as OrderApi from '@/api/pay/order'
 
 

+ 1 - 1
src/views/pay/refund/index.vue

@@ -33,7 +33,7 @@
     </template>
     </template>
   </XModal>
   </XModal>
 </template>
 </template>
-<script setup lang="ts" name="Refund">
+<script setup lang="ts" name="PayRefund">
 import { allSchemas } from './refund.data'
 import { allSchemas } from './refund.data'
 import * as RefundApi from '@/api/pay/refund'
 import * as RefundApi from '@/api/pay/refund'
 
 

+ 1 - 1
src/views/system/area/index.vue

@@ -30,7 +30,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <AreaForm ref="formRef" />
   <AreaForm ref="formRef" />
 </template>
 </template>
-<script setup lang="tsx" name="Area">
+<script setup lang="tsx" name="SystemArea">
 import type { Column } from 'element-plus'
 import type { Column } from 'element-plus'
 import AreaForm from './AreaForm.vue'
 import AreaForm from './AreaForm.vue'
 import * as AreaApi from '@/api/system/area'
 import * as AreaApi from '@/api/system/area'

+ 1 - 1
src/views/system/dept/index.vue

@@ -103,7 +103,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <DeptForm ref="formRef" @success="getList" />
   <DeptForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Dept">
+<script setup lang="ts" name="SystemDept">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import { handleTree } from '@/utils/tree'
 import { handleTree } from '@/utils/tree'

+ 1 - 1
src/views/system/dict/data/index.vue

@@ -115,7 +115,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <DictDataForm ref="formRef" @success="getList" />
   <DictDataForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="DictData">
+<script setup lang="ts" name="SystemDictData">
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/dict/index.vue

@@ -132,7 +132,7 @@
   <DictTypeForm ref="formRef" @success="getList" />
   <DictTypeForm ref="formRef" @success="getList" />
 </template>
 </template>
 
 
-<script setup lang="ts" name="DictType">
+<script setup lang="ts" name="SystemDictType">
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as DictTypeApi from '@/api/system/dict/dict.type'
 import * as DictTypeApi from '@/api/system/dict/dict.type'

+ 1 - 1
src/views/system/errorCode/index.vue

@@ -137,7 +137,7 @@
   <ErrorCodeForm ref="formRef" @success="getList" />
   <ErrorCodeForm ref="formRef" @success="getList" />
 </template>
 </template>
 
 
-<script setup lang="ts" name="ErrorCode">
+<script setup lang="ts" name="SystemErrorCode">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/loginlog/index.vue

@@ -104,7 +104,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <LoginLogDetail ref="detailRef" />
   <LoginLogDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="LoginLog">
+<script setup lang="ts" name="SystemLoginLog">
 import { DICT_TYPE } from '@/utils/dict'
 import { DICT_TYPE } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/mail/account/index.vue

@@ -64,7 +64,7 @@
   <!-- 详情弹窗 -->
   <!-- 详情弹窗 -->
   <MailAccountDetail ref="detailRef" />
   <MailAccountDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="MailAccount">
+<script setup lang="ts" name="SystemMailAccount">
 import { allSchemas } from './account.data'
 import { allSchemas } from './account.data'
 import * as MailAccountApi from '@/api/system/mail/account'
 import * as MailAccountApi from '@/api/system/mail/account'
 import MailAccountForm from './MailAccountForm.vue'
 import MailAccountForm from './MailAccountForm.vue'

+ 1 - 1
src/views/system/mail/log/index.vue

@@ -34,7 +34,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <mail-log-detail ref="detailRef" />
   <mail-log-detail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="MailLog">
+<script setup lang="ts" name="SystemMailLog">
 import { allSchemas } from './log.data'
 import { allSchemas } from './log.data'
 import * as MailLogApi from '@/api/system/mail/log'
 import * as MailLogApi from '@/api/system/mail/log'
 import MailLogDetail from './MailLogDetail.vue'
 import MailLogDetail from './MailLogDetail.vue'

+ 1 - 1
src/views/system/mail/template/index.vue

@@ -65,7 +65,7 @@
   <!-- 表单弹窗:发送测试 -->
   <!-- 表单弹窗:发送测试 -->
   <MailTemplateSendForm ref="sendFormRef" />
   <MailTemplateSendForm ref="sendFormRef" />
 </template>
 </template>
-<script setup lang="ts" name="MailTemplate">
+<script setup lang="ts" name="SystemMailTemplate">
 import { allSchemas } from './template.data'
 import { allSchemas } from './template.data'
 import * as MailTemplateApi from '@/api/system/mail/template'
 import * as MailTemplateApi from '@/api/system/mail/template'
 import MailTemplateForm from './MailTemplateForm.vue'
 import MailTemplateForm from './MailTemplateForm.vue'

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

@@ -111,7 +111,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <MenuForm ref="formRef" @success="getList" />
   <MenuForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Menu">
+<script setup lang="ts" name="SystemMenu">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { handleTree } from '@/utils/tree'
 import { handleTree } from '@/utils/tree'
 import * as MenuApi from '@/api/system/menu'
 import * as MenuApi from '@/api/system/menu'

+ 1 - 1
src/views/system/notice/index.vue

@@ -102,7 +102,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <NoticeForm ref="formRef" @success="getList" />
   <NoticeForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="tsx">
+<script setup lang="tsx" name="SystemNotice">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as NoticeApi from '@/api/system/notice'
 import * as NoticeApi from '@/api/system/notice'

+ 1 - 1
src/views/system/notify/message/index.vue

@@ -153,7 +153,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <NotifyMessageDetail ref="detailRef" />
   <NotifyMessageDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="NotifyMessage">
+<script setup lang="ts" name="SystemNotifyMessage">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as NotifyMessageApi from '@/api/system/notify/message'
 import * as NotifyMessageApi from '@/api/system/notify/message'

+ 1 - 1
src/views/system/notify/my/index.vue

@@ -115,7 +115,7 @@
   <MyNotifyMessageDetail ref="detailRef" />
   <MyNotifyMessageDetail ref="detailRef" />
 </template>
 </template>
 
 
-<script setup lang="ts" name="MyNotifyMessage">
+<script setup lang="ts" name="SystemMyNotify">
 import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as NotifyMessageApi from '@/api/system/notify/message'
 import * as NotifyMessageApi from '@/api/system/notify/message'

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

@@ -114,7 +114,7 @@
     </template>
     </template>
   </XModal>
   </XModal>
 </template>
 </template>
-<script setup lang="ts" name="NotifyTemplate">
+<script setup lang="ts" name="SystemNotifyTemplate">
 import { FormExpose } from '@/components/Form'
 import { FormExpose } from '@/components/Form'
 // 业务相关的 import
 // 业务相关的 import
 import { rules, allSchemas } from './template.data'
 import { rules, allSchemas } from './template.data'

+ 1 - 1
src/views/system/oauth2/client/index.vue

@@ -119,7 +119,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <ClientForm ref="formRef" @success="getList" />
   <ClientForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts">
+<script setup lang="ts" name="SystemOAuth2Client">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as ClientApi from '@/api/system/oauth2/client'
 import * as ClientApi from '@/api/system/oauth2/client'

+ 1 - 1
src/views/system/oauth2/token/index.vue

@@ -98,7 +98,7 @@
   </ContentWrap>
   </ContentWrap>
 </template>
 </template>
 
 
-<script setup lang="ts" name="Oauth2AccessToken">
+<script setup lang="ts" name="SystemTokenClient">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'
 import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'

+ 1 - 1
src/views/system/operatelog/index.vue

@@ -135,7 +135,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <OperateLogDetail ref="detailRef" />
   <OperateLogDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="OperateLog">
+<script setup lang="ts" name="SystemOperateLog">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 2
src/views/system/post/index.vue

@@ -111,13 +111,12 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <PostForm ref="formRef" @success="getList" />
   <PostForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="tsx">
+<script setup lang="tsx" name="SystemPost">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'
 import * as PostApi from '@/api/system/post'
 import * as PostApi from '@/api/system/post'
 import PostForm from './PostForm.vue'
 import PostForm from './PostForm.vue'
-
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 
 

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

@@ -152,7 +152,7 @@
   <!-- 表单弹窗:数据权限 -->
   <!-- 表单弹窗:数据权限 -->
   <RoleDataPermissionForm ref="dataPermissionFormRef" />
   <RoleDataPermissionForm ref="dataPermissionFormRef" />
 </template>
 </template>
-<script setup lang="tsx">
+<script setup lang="tsx" name="SystemRole">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/sensitiveWord/index.vue

@@ -143,7 +143,7 @@
   <!-- 表单弹窗:测试敏感词 -->
   <!-- 表单弹窗:测试敏感词 -->
   <SensitiveWordTestForm ref="testFormRef" />
   <SensitiveWordTestForm ref="testFormRef" />
 </template>
 </template>
-<script setup lang="ts" name="SensitiveWord">
+<script setup lang="ts" name="SystemSensitiveWordhao">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/sms/channel/index.vue

@@ -129,7 +129,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <SmsChannelForm ref="formRef" @success="getList" />
   <SmsChannelForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="SmsChannel">
+<script setup lang="ts" name="SystemSmsChannel">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as SmsChannelApi from '@/api/system/sms/smsChannel'
 import * as SmsChannelApi from '@/api/system/sms/smsChannel'

+ 1 - 1
src/views/system/sms/log/index.vue

@@ -184,7 +184,7 @@
   <!-- 表单弹窗:详情 -->
   <!-- 表单弹窗:详情 -->
   <SmsLogDetail ref="detailRef" />
   <SmsLogDetail ref="detailRef" />
 </template>
 </template>
-<script setup lang="ts" name="smsLog">
+<script setup lang="ts" name="SystemSmsLog">
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
 import { dateFormatter, formatDate } from '@/utils/formatTime'
 import { dateFormatter, formatDate } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'

+ 1 - 1
src/views/system/sms/template/index.vue

@@ -211,7 +211,7 @@
   <!-- 表单弹窗:测试发送 -->
   <!-- 表单弹窗:测试发送 -->
   <SmsTemplateSendForm ref="sendFormRef" />
   <SmsTemplateSendForm ref="sendFormRef" />
 </template>
 </template>
-<script setup lang="ts" name="SmsTemplate">
+<script setup lang="ts" name="SystemSmsTemplate">
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
 import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'

+ 1 - 2
src/views/system/tenant/index.vue

@@ -171,14 +171,13 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <TenantForm ref="formRef" @success="getList" />
   <TenantForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="Tenant">
+<script setup lang="ts" name="SystemTenant">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import download from '@/utils/download'
 import download from '@/utils/download'
 import * as TenantApi from '@/api/system/tenant'
 import * as TenantApi from '@/api/system/tenant'
 import * as TenantPackageApi from '@/api/system/tenantPackage'
 import * as TenantPackageApi from '@/api/system/tenantPackage'
 import TenantForm from './TenantForm.vue'
 import TenantForm from './TenantForm.vue'
-
 const message = useMessage() // 消息弹窗
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const { t } = useI18n() // 国际化
 
 

+ 1 - 1
src/views/system/tenantPackage/index.vue

@@ -106,7 +106,7 @@
   <!-- 表单弹窗:添加/修改 -->
   <!-- 表单弹窗:添加/修改 -->
   <TenantPackageForm ref="formRef" @success="getList" />
   <TenantPackageForm ref="formRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="TenantPackage">
+<script setup lang="ts" name="SystemTenantPackage">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'
 import * as TenantPackageApi from '@/api/system/tenantPackage'
 import * as TenantPackageApi from '@/api/system/tenantPackage'

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

@@ -198,7 +198,7 @@
   <!-- 分配角色 -->
   <!-- 分配角色 -->
   <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
   <UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
 </template>
 </template>
-<script setup lang="ts" name="User">
+<script setup lang="ts" name="SystemUser">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { checkPermi } from '@/utils/permission'
 import { checkPermi } from '@/utils/permission'
 import { dateFormatter } from '@/utils/formatTime'
 import { dateFormatter } from '@/utils/formatTime'