From 39248bc48bd1c2b66e18337dadd70d50b2bfaae6 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期五, 13 九月 2024 16:31:12 +0800
Subject: [PATCH] 框架及页面bug修改

---
 src/api/system/menu/index.ts                                                    |   31 +
 src/views/infra/storage/index_rec.vue                                           |  161 ++++++
 src/utils/routerHelper.ts                                                       |    1 
 src/plugins/unocss/index.ts                                                     |    1 
 src/layout/components/Logo/src/Logo.vue                                         |   14 
 src/components/bpmnProcessDesigner/package/utils.ts                             |    2 
 src/api/system/app/index.ts                                                     |    7 
 src/api/system/appgroup/index.ts                                                |   41 +
 src/api/login/index.ts                                                          |    5 
 src/router/modules/remaining.ts                                                 |   17 
 src/views/system/appmenu/AppMenuForm.vue                                        |  256 ++++++++++
 src/views/bpm/processInstance/index.vue                                         |    1 
 src/views/system/appmenu/index.vue                                              |  203 ++++++++
 src/utils/constants.ts                                                          |    9 
 src/views/bpm/task/copy/index.vue                                               |    2 
 src/views/system/appgroup/AppGroupForm.vue                                      |  130 +++++
 src/api/infra/storage/types.ts                                                  |    6 
 src/api/infra/storage/index.ts                                                  |   15 
 src/permission.ts                                                               |    3 
 src/api/system/oauth2/token.ts                                                  |   19 
 /dev/null                                                                       |  212 --------
 src/views/Login/components/LoginForm.vue                                        |  104 ---
 src/views/infra/server/index.vue                                                |    3 
 src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue |    2 
 src/utils/dict.ts                                                               |    2 
 src/config/axios/index.ts                                                       |    2 
 src/views/system/appgroup/index.vue                                             |  176 +++++++
 src/api/system/appmenu/index.ts                                                 |   49 +
 src/api/system/tenant/index.ts                                                  |    5 
 src/components/FormCreate/src/components/useApiSelect.tsx                       |    2 
 30 files changed, 1,155 insertions(+), 326 deletions(-)

diff --git a/src/api/infra/demo/demo01/index.ts b/src/api/infra/demo/demo01/index.ts
deleted file mode 100644
index e34a05d..0000000
--- a/src/api/infra/demo/demo01/index.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import request from '@/config/axios'
-
-export interface Demo01ContactVO {
-  id: number
-  name: string
-  sex: number
-  birthday: Date
-  description: string
-  avatar: string
-}
-
-// 查询示例联系人分页
-export const getDemo01ContactPage = async (params) => {
-  return await request.get({ url: `/infra/demo01-contact/page`, params })
-}
-
-// 查询示例联系人详情
-export const getDemo01Contact = async (id: number) => {
-  return await request.get({ url: `/infra/demo01-contact/get?id=` + id })
-}
-
-// 新增示例联系人
-export const createDemo01Contact = async (data: Demo01ContactVO) => {
-  return await request.post({ url: `/infra/demo01-contact/create`, data })
-}
-
-// 修改示例联系人
-export const updateDemo01Contact = async (data: Demo01ContactVO) => {
-  return await request.put({ url: `/infra/demo01-contact/update`, data })
-}
-
-// 删除示例联系人
-export const deleteDemo01Contact = async (id: number) => {
-  return await request.delete({ url: `/infra/demo01-contact/delete?id=` + id })
-}
-
-// 导出示例联系人 Excel
-export const exportDemo01Contact = async (params) => {
-  return await request.download({ url: `/infra/demo01-contact/export-excel`, params })
-}
diff --git a/src/api/infra/demo/demo02/index.ts b/src/api/infra/demo/demo02/index.ts
deleted file mode 100644
index 736a123..0000000
--- a/src/api/infra/demo/demo02/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import request from '@/config/axios'
-
-export interface Demo02CategoryVO {
-  id: number
-  name: string
-  parentId: number
-}
-
-// 查询示例分类列表
-export const getDemo02CategoryList = async () => {
-  return await request.get({ url: `/infra/demo02-category/list` })
-}
-
-// 查询示例分类详情
-export const getDemo02Category = async (id: number) => {
-  return await request.get({ url: `/infra/demo02-category/get?id=` + id })
-}
-
-// 新增示例分类
-export const createDemo02Category = async (data: Demo02CategoryVO) => {
-  return await request.post({ url: `/infra/demo02-category/create`, data })
-}
-
-// 修改示例分类
-export const updateDemo02Category = async (data: Demo02CategoryVO) => {
-  return await request.put({ url: `/infra/demo02-category/update`, data })
-}
-
-// 删除示例分类
-export const deleteDemo02Category = async (id: number) => {
-  return await request.delete({ url: `/infra/demo02-category/delete?id=` + id })
-}
-
-// 导出示例分类 Excel
-export const exportDemo02Category = async (params) => {
-  return await request.download({ url: `/infra/demo02-category/export-excel`, params })
-}
diff --git a/src/api/infra/demo/demo03/erp/index.ts b/src/api/infra/demo/demo03/erp/index.ts
deleted file mode 100644
index a2ab539..0000000
--- a/src/api/infra/demo/demo03/erp/index.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import request from '@/config/axios'
-
-export interface Demo03StudentVO {
-  id: number
-  name: string
-  sex: number
-  birthday: Date
-  description: string
-}
-
-// 查询学生分页
-export const getDemo03StudentPage = async (params) => {
-  return await request.get({ url: `/infra/demo03-student/page`, params })
-}
-
-// 查询学生详情
-export const getDemo03Student = async (id: number) => {
-  return await request.get({ url: `/infra/demo03-student/get?id=` + id })
-}
-
-// 新增学生
-export const createDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.post({ url: `/infra/demo03-student/create`, data })
-}
-
-// 修改学生
-export const updateDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.put({ url: `/infra/demo03-student/update`, data })
-}
-
-// 删除学生
-export const deleteDemo03Student = async (id: number) => {
-  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
-}
-
-// 导出学生 Excel
-export const exportDemo03Student = async (params) => {
-  return await request.download({ url: `/infra/demo03-student/export-excel`, params })
-}
-
-// ==================== 子表(学生课程) ====================
-
-// 获得学生课程分页
-export const getDemo03CoursePage = async (params) => {
-  return await request.get({ url: `/infra/demo03-student/demo03-course/page`, params })
-}
-// 新增学生课程
-export const createDemo03Course = async (data) => {
-  return await request.post({ url: `/infra/demo03-student/demo03-course/create`, data })
-}
-
-// 修改学生课程
-export const updateDemo03Course = async (data) => {
-  return await request.put({ url: `/infra/demo03-student/demo03-course/update`, data })
-}
-
-// 删除学生课程
-export const deleteDemo03Course = async (id: number) => {
-  return await request.delete({ url: `/infra/demo03-student/demo03-course/delete?id=` + id })
-}
-
-// 获得学生课程
-export const getDemo03Course = async (id: number) => {
-  return await request.get({ url: `/infra/demo03-student/demo03-course/get?id=` + id })
-}
-
-// ==================== 子表(学生班级) ====================
-
-// 获得学生班级分页
-export const getDemo03GradePage = async (params) => {
-  return await request.get({ url: `/infra/demo03-student/demo03-grade/page`, params })
-}
-// 新增学生班级
-export const createDemo03Grade = async (data) => {
-  return await request.post({ url: `/infra/demo03-student/demo03-grade/create`, data })
-}
-
-// 修改学生班级
-export const updateDemo03Grade = async (data) => {
-  return await request.put({ url: `/infra/demo03-student/demo03-grade/update`, data })
-}
-
-// 删除学生班级
-export const deleteDemo03Grade = async (id: number) => {
-  return await request.delete({ url: `/infra/demo03-student/demo03-grade/delete?id=` + id })
-}
-
-// 获得学生班级
-export const getDemo03Grade = async (id: number) => {
-  return await request.get({ url: `/infra/demo03-student/demo03-grade/get?id=` + id })
-}
diff --git a/src/api/infra/demo/demo03/inner/index.ts b/src/api/infra/demo/demo03/inner/index.ts
deleted file mode 100644
index e366307..0000000
--- a/src/api/infra/demo/demo03/inner/index.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import request from '@/config/axios'
-
-export interface Demo03StudentVO {
-  id: number
-  name: string
-  sex: number
-  birthday: Date
-  description: string
-}
-
-// 查询学生分页
-export const getDemo03StudentPage = async (params) => {
-  return await request.get({ url: `/infra/demo03-student/page`, params })
-}
-
-// 查询学生详情
-export const getDemo03Student = async (id: number) => {
-  return await request.get({ url: `/infra/demo03-student/get?id=` + id })
-}
-
-// 新增学生
-export const createDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.post({ url: `/infra/demo03-student/create`, data })
-}
-
-// 修改学生
-export const updateDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.put({ url: `/infra/demo03-student/update`, data })
-}
-
-// 删除学生
-export const deleteDemo03Student = async (id: number) => {
-  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
-}
-
-// 导出学生 Excel
-export const exportDemo03Student = async (params) => {
-  return await request.download({ url: `/infra/demo03-student/export-excel`, params })
-}
-
-// ==================== 子表(学生课程) ====================
-
-// 获得学生课程列表
-export const getDemo03CourseListByStudentId = async (studentId) => {
-  return await request.get({
-    url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
-  })
-}
-
-// ==================== 子表(学生班级) ====================
-
-// 获得学生班级
-export const getDemo03GradeByStudentId = async (studentId) => {
-  return await request.get({
-    url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
-  })
-}
diff --git a/src/api/infra/demo/demo03/normal/index.ts b/src/api/infra/demo/demo03/normal/index.ts
deleted file mode 100644
index e366307..0000000
--- a/src/api/infra/demo/demo03/normal/index.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import request from '@/config/axios'
-
-export interface Demo03StudentVO {
-  id: number
-  name: string
-  sex: number
-  birthday: Date
-  description: string
-}
-
-// 查询学生分页
-export const getDemo03StudentPage = async (params) => {
-  return await request.get({ url: `/infra/demo03-student/page`, params })
-}
-
-// 查询学生详情
-export const getDemo03Student = async (id: number) => {
-  return await request.get({ url: `/infra/demo03-student/get?id=` + id })
-}
-
-// 新增学生
-export const createDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.post({ url: `/infra/demo03-student/create`, data })
-}
-
-// 修改学生
-export const updateDemo03Student = async (data: Demo03StudentVO) => {
-  return await request.put({ url: `/infra/demo03-student/update`, data })
-}
-
-// 删除学生
-export const deleteDemo03Student = async (id: number) => {
-  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })
-}
-
-// 导出学生 Excel
-export const exportDemo03Student = async (params) => {
-  return await request.download({ url: `/infra/demo03-student/export-excel`, params })
-}
-
-// ==================== 子表(学生课程) ====================
-
-// 获得学生课程列表
-export const getDemo03CourseListByStudentId = async (studentId) => {
-  return await request.get({
-    url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId
-  })
-}
-
-// ==================== 子表(学生班级) ====================
-
-// 获得学生班级
-export const getDemo03GradeByStudentId = async (studentId) => {
-  return await request.get({
-    url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId
-  })
-}
diff --git a/src/api/infra/storage/index.ts b/src/api/infra/storage/index.ts
new file mode 100644
index 0000000..a1aa7dc
--- /dev/null
+++ b/src/api/infra/storage/index.ts
@@ -0,0 +1,15 @@
+import request from '@/config/axios'
+
+/**
+ * 获取磁盘信息
+ */
+export const getDiskInfo = () => {
+  return request.get({ url: '/infra/actuator/disk/info' })
+}
+
+/**
+ * 获取内存信息
+ */
+export const getMemoryInfo = () => {
+  return request.get({ url: '/infra/actuator/memory/info' })
+}
diff --git a/src/api/infra/storage/types.ts b/src/api/infra/storage/types.ts
new file mode 100644
index 0000000..0f683ef
--- /dev/null
+++ b/src/api/infra/storage/types.ts
@@ -0,0 +1,6 @@
+export interface StorageMonitorInfoVO {
+  name: string
+  max: number
+  rest: number
+  restPPT: number
+}
diff --git a/src/api/login/index.ts b/src/api/login/index.ts
index ef86563..e6d9f52 100644
--- a/src/api/login/index.ts
+++ b/src/api/login/index.ts
@@ -42,6 +42,11 @@
   return request.get({ url: '/system/auth/get-permission-info' })
 }
 
+// 获取用应用户权限信息
+export const getUserAppInfo = (id: number) => {
+  return request.get({ url: '/system/auth/get-app-permission-info?id=' + id })
+}
+
 //获取登录验证码
 export const sendSmsCode = (data: SmsCodeVO) => {
   return request.post({ url: '/system/auth/send-sms-code', data })
diff --git a/src/api/system/app/index.ts b/src/api/system/app/index.ts
index ef8b952..d247a19 100644
--- a/src/api/system/app/index.ts
+++ b/src/api/system/app/index.ts
@@ -19,11 +19,16 @@
   createTime: Date
 }
 
-// 查询列表
+// 查询分页列表
 export const getAppPage = (params: PageParam) => {
   return request.get({ url: '/system/app/page', params })
 }
 
+// 查询列表
+export const getAppList = () => {
+  return request.get({ url: '/system/app/getAppList' })
+}
+
 // 获得
 export const getApp = (id: number) => {
   return request.get({ url: '/system/app/get?id=' + id })
diff --git a/src/api/system/appgroup/index.ts b/src/api/system/appgroup/index.ts
new file mode 100644
index 0000000..b19444a
--- /dev/null
+++ b/src/api/system/appgroup/index.ts
@@ -0,0 +1,41 @@
+import request from '@/config/axios'
+
+export interface AppGroupVO {
+  id: number
+  node: string
+  name: string
+  icon: string
+  sort: number
+  remark: string
+  createTime: Date
+}
+
+// 查询分页列表
+export const getAppGroupPage = (params: PageParam) => {
+  return request.get({ url: '/system/app-group/page', params })
+}
+
+// 查询列表
+export const getAppGroupList = () => {
+  return request.get({ url: '/system/app-group/getAppGroupList' })
+}
+
+// 获得
+export const getAppGroup = (id: number) => {
+  return request.get({ url: '/system/app-group/get?id=' + id })
+}
+
+// 新增
+export const createAppGroup = (data: AppGroupVO) => {
+  return request.post({ url: '/system/app-group/create', data })
+}
+
+// 修改
+export const updateAppGroup = (data: AppGroupVO) => {
+  return request.put({ url: '/system/app-group/update', data })
+}
+
+// 删除
+export const deleteAppGroup = (id: number) => {
+  return request.delete({ url: '/system/app-group/delete?id=' + id })
+}
diff --git a/src/api/system/appmenu/index.ts b/src/api/system/appmenu/index.ts
new file mode 100644
index 0000000..91d39a0
--- /dev/null
+++ b/src/api/system/appmenu/index.ts
@@ -0,0 +1,49 @@
+import request from '@/config/axios'
+
+export interface AppMenuVO {
+  id: number
+  name: string
+  permission: string
+  type: number
+  sort: number
+  parentId: number
+  path: string
+  icon: string
+  component: string
+  componentName?: string
+  status: number
+  visible: boolean
+  keepAlive: boolean
+  alwaysShow?: boolean
+  createTime: Date
+}
+
+// 查询菜单(精简)列表
+export const getSimpleMenusList = () => {
+  return request.get({ url: '/system/app-menu/simple-list' })
+}
+
+// 查询菜单列表
+export const getMenuList = (params) => {
+  return request.get({ url: '/system/app-menu/list', params })
+}
+
+// 获取菜单详情
+export const getMenu = (id: number) => {
+  return request.get({ url: '/system/app-menu/get?id=' + id })
+}
+
+// 新增菜单
+export const createMenu = (data: AppMenuVO) => {
+  return request.post({ url: '/system/app-menu/create', data })
+}
+
+// 修改菜单
+export const updateMenu = (data: AppMenuVO) => {
+  return request.put({ url: '/system/app-menu/update', data })
+}
+
+// 删除菜单
+export const deleteMenu = (id: number) => {
+  return request.delete({ url: '/system/app-menu/delete?id=' + id })
+}
diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts
index 5a80668..1ed651a 100644
--- a/src/api/system/menu/index.ts
+++ b/src/api/system/menu/index.ts
@@ -16,6 +16,7 @@
   keepAlive: boolean
   alwaysShow?: boolean
   createTime: Date
+  appId: number
 }
 
 // 查询菜单(精简)列表
@@ -23,9 +24,19 @@
   return request.get({ url: '/system/menu/simple-list' })
 }
 
+// 查询应用菜单(精简)列表
+export const getSimpleAppMenusList = () => {
+  return request.get({ url: '/system/menu/simple-app-menus' })
+}
+
 // 查询菜单列表
 export const getMenuList = (params) => {
   return request.get({ url: '/system/menu/list', params })
+}
+
+// 查询应用菜单列表
+export const getAppMenuList = (params) => {
+  return request.get({ url: '/system/menu/app-menu-list', params })
 }
 
 // 获取菜单详情
@@ -33,9 +44,19 @@
   return request.get({ url: '/system/menu/get?id=' + id })
 }
 
+// 获取应用菜单详情
+export const getAppMenu = (id: number) => {
+  return request.get({ url: '/system/menu/getAppMenu?id=' + id })
+}
+
 // 新增菜单
 export const createMenu = (data: MenuVO) => {
   return request.post({ url: '/system/menu/create', data })
+}
+
+// 新增应用菜单
+export const createAppMenu = (data: MenuVO) => {
+  return request.post({ url: '/system/menu/createAppMenu', data })
 }
 
 // 修改菜单
@@ -43,7 +64,17 @@
   return request.put({ url: '/system/menu/update', data })
 }
 
+// 修改应用菜单
+export const updateAppMenu = (data: MenuVO) => {
+  return request.put({ url: '/system/menu/updateAppMenu', data })
+}
+
 // 删除菜单
 export const deleteMenu = (id: number) => {
   return request.delete({ url: '/system/menu/delete?id=' + id })
 }
+
+// 删除应用菜单
+export const deleteAppMenu = (id: number) => {
+  return request.delete({ url: '/system/menu/deleteAppMenu?id=' + id })
+}
diff --git a/src/api/system/oauth2/token.ts b/src/api/system/oauth2/token.ts
index ac89ae8..02ea572 100644
--- a/src/api/system/oauth2/token.ts
+++ b/src/api/system/oauth2/token.ts
@@ -1,14 +1,12 @@
 import request from '@/config/axios'
 
 export interface OAuth2TokenVO {
-  id: number
-  accessToken: string
-  refreshToken: string
-  userId: number
-  userType: number
-  clientId: string
-  createTime: Date
-  expiresTime: Date
+  grantType: string
+  scope: string
+  refreshToken: any
+  username: string
+  password: string
+  redirectUri: string
 }
 
 // 查询 token列表
@@ -16,6 +14,11 @@
   return request.get({ url: '/system/oauth2-token/page', params })
 }
 
+// 单点登录授权
+export const OAuth2Login = (params: OAuth2TokenVO) => {
+  return request.post({ url: '/system/oauth2/token', data: params })
+}
+
 // 删除 token
 export const deleteAccessToken = (accessToken: string) => {
   return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
diff --git a/src/api/system/tenant/index.ts b/src/api/system/tenant/index.ts
index 95aa059..33bf327 100644
--- a/src/api/system/tenant/index.ts
+++ b/src/api/system/tenant/index.ts
@@ -37,6 +37,11 @@
   return request.get({ url: '/system/tenant/page', params })
 }
 
+// 查询租户列表
+export const getSimpleTenant = () => {
+  return request.get({ url: '/system/tenant/simple-list' })
+}
+
 // 查询租户详情
 export const getTenant = (id: number) => {
   return request.get({ url: '/system/tenant/get?id=' + id })
diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx
index 8765036..d668cb8 100644
--- a/src/components/FormCreate/src/components/useApiSelect.tsx
+++ b/src/components/FormCreate/src/components/useApiSelect.tsx
@@ -185,7 +185,7 @@
             </el-select>
           )
         }
-        debugger
+        // debugger
         return (
           <el-select
             class="w-1/1"
diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
index de5445c..c557b59 100644
--- a/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
@@ -370,7 +370,7 @@
 }
 // 移除监听器
 const removeListener = (index) => {
-  debugger
+  // debugger
   ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
     confirmButtonText: '确 认',
     cancelButtonText: '取 消'
diff --git a/src/components/bpmnProcessDesigner/package/utils.ts b/src/components/bpmnProcessDesigner/package/utils.ts
index 8996788..a7de5f0 100644
--- a/src/components/bpmnProcessDesigner/package/utils.ts
+++ b/src/components/bpmnProcessDesigner/package/utils.ts
@@ -2,7 +2,7 @@
 const bpmnInstances = () => (window as any)?.bpmnInstances
 // 创建监听器实例
 export function createListenerObject(options, isTask, prefix) {
-  debugger
+  // debugger
   const listenerObj = Object.create(null)
   listenerObj.event = options.event
   isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
diff --git a/src/config/axios/index.ts b/src/config/axios/index.ts
index 79e558d..e37d624 100644
--- a/src/config/axios/index.ts
+++ b/src/config/axios/index.ts
@@ -14,7 +14,7 @@
     ...config,
     responseType: responseType,
     headers: {
-      'Content-Type': headersType || default_headers
+      'Content-Type': headersType || default_headers,
     }
   })
 }
diff --git a/src/layout/components/Logo/src/Logo.vue b/src/layout/components/Logo/src/Logo.vue
index d241130..d5790b0 100644
--- a/src/layout/components/Logo/src/Logo.vue
+++ b/src/layout/components/Logo/src/Logo.vue
@@ -2,6 +2,7 @@
 import { computed, onMounted, ref, unref, watch } from 'vue'
 import { useAppStore } from '@/store/modules/app'
 import { useDesign } from '@/hooks/web/useDesign'
+import * as authUtil from "@/utils/auth";
 
 defineOptions({ name: 'Logo' })
 
@@ -13,11 +14,22 @@
 
 const show = ref(true)
 
+const homePath = ref('/index')
+
 const title = computed(() => appStore.getTitle)
 
 const layout = computed(() => appStore.getLayout)
 
 const collapse = computed(() => appStore.getCollapse)
+
+let tenantId = authUtil.getTenantId()
+console.log(tenantId)
+if (tenantId && tenantId === 1) {
+  homePath.value = '/index'
+} else {
+  homePath.value = '/home2'
+}
+console.log(homePath.value)
 
 onMounted(() => {
   if (unref(collapse)) show.value = false
@@ -64,7 +76,7 @@
         layout !== 'classic' ? `${prefixCls}__Top` : '',
         'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative decoration-none overflow-hidden'
       ]"
-      to="/"
+      :to="homePath"
     >
       <img
         class="h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]"
diff --git a/src/permission.ts b/src/permission.ts
index b04bc3c..9120c1a 100644
--- a/src/permission.ts
+++ b/src/permission.ts
@@ -73,7 +73,8 @@
       }
       if (!userStore.getIsSetUser) {
         isRelogin.show = true
-        await userStore.setUserInfoAction()
+        await userStore.
+        setUserInfoAction()
         isRelogin.show = false
         // 后端过滤菜单
         await permissionStore.generateRoutes()
diff --git a/src/plugins/unocss/index.ts b/src/plugins/unocss/index.ts
index d366b5a..8422174 100644
--- a/src/plugins/unocss/index.ts
+++ b/src/plugins/unocss/index.ts
@@ -1 +1,2 @@
 import 'virtual:uno.css'
+import 'uno.css'
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index b889036..825f9f0 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -1,4 +1,4 @@
-import { Layout } from '@/utils/routerHelper'
+import {Layout} from '@/utils/routerHelper'
 
 const { t } = useI18n()
 /**
@@ -51,11 +51,22 @@
     }
   },
   {
+    path: '/home2',
+    component: () => import('@/views/Home/Index2.vue'),
+    name: 'Home2',
+    meta: {
+      hidden: true,
+      noTagsView: true
+    },
+  },
+  {
     path: '/',
     component: Layout,
-    redirect: '/index',
     name: 'Home',
-    meta: {},
+    meta: {
+      hidden: true,
+      noTagsView: true
+    },
     children: [
       {
         path: 'index',
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index ae06f28..b21f067 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -28,6 +28,15 @@
 }
 
 /**
+ * 应用菜单的类型枚举
+ */
+export const SystemAppMenuTypeEnum = {
+  DIR: 1, // 应用
+  MENU: 2, // 菜单
+  BUTTON: 3 // 按钮
+}
+
+/**
  * 角色的类型枚举
  */
 export const SystemRoleTypeEnum = {
diff --git a/src/utils/dict.ts b/src/utils/dict.ts
index 03ed2fd..12724e0 100644
--- a/src/utils/dict.ts
+++ b/src/utils/dict.ts
@@ -116,6 +116,8 @@
   // ========== SYSTEM 模块 ==========
   SYSTEM_USER_SEX = 'system_user_sex',
   SYSTEM_MENU_TYPE = 'system_menu_type',
+  SYSTEM_APP_MENU_TYPE = 'system_app_menu_type',
+  SYSTEM_APP_TYPE = 'system_app_type',
   SYSTEM_ROLE_TYPE = 'system_role_type',
   SYSTEM_DATA_SCOPE = 'system_data_scope',
   SYSTEM_NOTICE_TYPE = 'system_notice_type',
diff --git a/src/utils/routerHelper.ts b/src/utils/routerHelper.ts
index f292751..e9c8c64 100644
--- a/src/utils/routerHelper.ts
+++ b/src/utils/routerHelper.ts
@@ -21,6 +21,7 @@
 /* Layout */
 export const Layout = () => import('@/layout/Layout.vue')
 
+
 export const getParentLayout = () => {
   return () =>
     new Promise((resolve) => {
diff --git a/src/views/Login/components/LoginForm.vue b/src/views/Login/components/LoginForm.vue
index 6e7f0d8..7b161e3 100644
--- a/src/views/Login/components/LoginForm.vue
+++ b/src/views/Login/components/LoginForm.vue
@@ -82,49 +82,6 @@
         mode="pop"
         @success="handleLogin"
       />
-      <!--<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
-        <el-form-item>
-          <el-row :gutter="5" justify="space-between" style="width: 100%">
-            <el-col :span="8">
-              <XButton
-                :title="t('login.btnMobile')"
-                class="w-[100%]"
-                @click="setLoginState(LoginStateEnum.MOBILE)"
-              />
-            </el-col>
-            <el-col :span="8">
-              <XButton
-                :title="t('login.btnQRCode')"
-                class="w-[100%]"
-                @click="setLoginState(LoginStateEnum.QR_CODE)"
-              />
-            </el-col>
-            <el-col :span="8">
-              <XButton
-                :title="t('login.btnRegister')"
-                class="w-[100%]"
-                @click="setLoginState(LoginStateEnum.REGISTER)"
-              />
-            </el-col>
-          </el-row>
-        </el-form-item>
-      </el-col>-->
-      <!--<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
-      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
-        <el-form-item>
-          <div class="w-[100%] flex justify-between">
-            <Icon
-              v-for="(item, key) in socialList"
-              :key="key"
-              :icon="item.icon"
-              :size="30"
-              class="anticon cursor-pointer"
-              color="#999"
-              @click="doSocialLogin(item.type)"
-            />
-          </div>
-        </el-form-item>
-      </el-col>-->
     </el-row>
   </el-form>
 </template>
@@ -176,13 +133,6 @@
     rememberMe: true // 默认记录我。如果不需要,可手动修改
   }
 })
-
-const socialList = [
-  { icon: 'ant-design:wechat-filled', type: 30 },
-  { icon: 'ant-design:dingtalk-circle-filled', type: 20 },
-  { icon: 'ant-design:github-filled', type: 0 },
-  { icon: 'ant-design:alipay-circle-filled', type: 0 }
-]
 
 // 获取验证码
 const getCode = async () => {
@@ -253,54 +203,24 @@
     if (!redirect.value) {
       redirect.value = '/'
     }
-    // 判断是否为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 })
-    }
+    let tenantId = authUtil.getTenantId()
+    // if(tenantId != 1) {
+    //   //只要不是系统租户,登录成功跳转到home2页面
+    //   window.location.href = '/home2'
+    // } else {
+      // 判断是否为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 })
+      }
+    // }
   } finally {
     loginLoading.value = false
     loading.value.close()
   }
 }
 
-// 社交登录
-const doSocialLogin = async (type: number) => {
-  if (type === 0) {
-    message.error('此方式未配置')
-  } else {
-    loginLoading.value = true
-    if (loginData.tenantEnable === 'true') {
-      // 尝试先通过 tenantName 获取租户
-      await getTenantId()
-      // 如果获取不到,则需要弹出提示,进行处理
-      if (!authUtil.getTenantId()) {
-        try {
-          const data = await message.prompt('请输入租户名称', t('common.reminder'))
-          if (data?.action !== 'confirm') throw 'cancel'
-          const res = await LoginApi.getTenantIdByName(data.value)
-          authUtil.setTenantId(res)
-        } catch (error) {
-          if (error === 'cancel') return
-        } finally {
-          loginLoading.value = false
-        }
-      }
-    }
-    // 计算 redirectUri
-    // tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
-    // 配合 Login/SocialLogin.vue#getUrlValue() 使用
-    const redirectUri =
-      location.origin +
-      '/social-login?' +
-      encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
-
-    // 进行跳转
-    const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
-    window.location.href = res
-  }
-}
 watch(
   () => currentRoute.value,
   (route: RouteLocationNormalizedLoaded) => {
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index 4f4c4bc..3951a83 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -221,7 +221,6 @@
     const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
       row.processDefinitionId
     )
-    debugger
     if (processDefinitionDetail.formType === 20) {
       message.error('重新发起流程失败,原因:该流程使用业务表单,不支持重新发起')
       return
diff --git a/src/views/bpm/task/copy/index.vue b/src/views/bpm/task/copy/index.vue
index d576ac7..dd41b2e 100644
--- a/src/views/bpm/task/copy/index.vue
+++ b/src/views/bpm/task/copy/index.vue
@@ -1,3 +1,5 @@
+<!-- 工作流 - 抄送我的流程 -->
+<template>
   <ContentWrap>
     <!-- 搜索工作栏 -->
     <el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
diff --git a/src/views/data/job/ScheduleJobFormLog.vue b/src/views/data/job/ScheduleJobFormLog.vue
deleted file mode 100644
index e69de29..0000000
--- a/src/views/data/job/ScheduleJobFormLog.vue
+++ /dev/null
diff --git a/src/views/infra/demo/demo01/Demo01ContactForm.vue b/src/views/infra/demo/demo01/Demo01ContactForm.vue
deleted file mode 100644
index 5d28112..0000000
--- a/src/views/infra/demo/demo01/Demo01ContactForm.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="性别" prop="sex">
-        <el-radio-group v-model="formData.sex">
-          <el-radio
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="出生年" prop="birthday">
-        <el-date-picker
-          v-model="formData.birthday"
-          type="date"
-          value-format="x"
-          placeholder="选择出生年"
-        />
-      </el-form-item>
-      <el-form-item label="简介" prop="description">
-        <Editor v-model="formData.description" height="150px" />
-      </el-form-item>
-      <el-form-item label="头像" prop="avatar">
-        <UploadImg v-model="formData.avatar" />
-      </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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import * as Demo01ContactApi from '@/api/infra/demo/demo01'
-
-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: undefined,
-  sex: undefined,
-  birthday: undefined,
-  description: undefined,
-  avatar: undefined
-})
-const formRules = reactive({
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
-  birthday: [{ required: true, message: '出生年不能为空', trigger: 'blur' }],
-  description: [{ 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 Demo01ContactApi.getDemo01Contact(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as Demo01ContactApi.Demo01ContactVO
-    if (formType.value === 'create') {
-      await Demo01ContactApi.createDemo01Contact(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo01ContactApi.updateDemo01Contact(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    name: undefined,
-    sex: undefined,
-    birthday: undefined,
-    description: undefined,
-    avatar: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo01/index.vue b/src/views/infra/demo/demo01/index.vue
deleted file mode 100644
index cf70829..0000000
--- a/src/views/infra/demo/demo01/index.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-<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="sex">
-        <el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-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="['infra:demo01-contact:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['infra:demo01-contact:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column label="性别" align="center" prop="sex">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="出生年"
-        align="center"
-        prop="birthday"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="简介" align="center" prop="description" />
-      <el-table-column label="头像" align="center" prop="avatar" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['infra:demo01-contact:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['infra:demo01-contact: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>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo01ContactForm ref="formRef" @success="getList" />
-</template>
-
-<script setup lang="ts">
-import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import download from '@/utils/download'
-import * as Demo01ContactApi from '@/api/infra/demo/demo01'
-import Demo01ContactForm from './Demo01ContactForm.vue'
-
-defineOptions({ name: 'Demo01Contact' })
-
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  name: null,
-  sex: null,
-  createTime: []
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo01ContactApi.getDemo01ContactPage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo01ContactApi.deleteDemo01Contact(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await Demo01ContactApi.exportDemo01Contact(queryParams)
-    download.excel(data, '示例联系人.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo02/Demo02CategoryForm.vue b/src/views/infra/demo/demo02/Demo02CategoryForm.vue
deleted file mode 100644
index f4c5f8e..0000000
--- a/src/views/infra/demo/demo02/Demo02CategoryForm.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="父级编号" prop="parentId">
-        <el-tree-select
-          v-model="formData.parentId"
-          :data="demo02CategoryTree"
-          :props="defaultProps"
-          check-strictly
-          default-expand-all
-          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">
-import * as Demo02CategoryApi from '@/api/infra/demo/demo02'
-import { defaultProps, handleTree } from '@/utils/tree'
-
-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: undefined,
-  parentId: undefined
-})
-const formRules = reactive({
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  parentId: [{ required: true, message: '父级编号不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-const demo02CategoryTree = 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 Demo02CategoryApi.getDemo02Category(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-  await getDemo02CategoryTree()
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as Demo02CategoryApi.Demo02CategoryVO
-    if (formType.value === 'create') {
-      await Demo02CategoryApi.createDemo02Category(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo02CategoryApi.updateDemo02Category(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    name: undefined,
-    parentId: undefined
-  }
-  formRef.value?.resetFields()
-}
-
-/** 获得示例分类树 */
-const getDemo02CategoryTree = async () => {
-  demo02CategoryTree.value = []
-  const data = await Demo02CategoryApi.getDemo02CategoryList()
-  const root: Tree = { id: 0, name: '顶级示例分类', children: [] }
-  root.children = handleTree(data, 'id', 'parentId')
-  demo02CategoryTree.value.push(root)
-}
-</script>
diff --git a/src/views/infra/demo/demo02/index.vue b/src/views/infra/demo/demo02/index.vue
deleted file mode 100644
index 0870dc5..0000000
--- a/src/views/infra/demo/demo02/index.vue
+++ /dev/null
@@ -1,205 +0,0 @@
-<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="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="['infra:demo02-category:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['infra:demo02-category:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-        <el-button type="danger" plain @click="toggleExpandAll">
-          <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table
-      v-loading="loading"
-      :data="list"
-      :stripe="true"
-      :show-overflow-tooltip="true"
-      row-key="id"
-      :default-expand-all="isExpandAll"
-      v-if="refreshTable"
-    >
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['infra:demo02-category:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['infra:demo02-category: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>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo02CategoryForm ref="formRef" @success="getList" />
-</template>
-
-<script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
-import { handleTree } from '@/utils/tree'
-import download from '@/utils/download'
-import * as Demo02CategoryApi from '@/api/infra/demo/demo02'
-import Demo02CategoryForm from './Demo02CategoryForm.vue'
-
-defineOptions({ name: 'Demo02Category' })
-
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-const queryParams = reactive({
-  name: null,
-  parentId: null,
-  createTime: []
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo02CategoryApi.getDemo02CategoryList(queryParams)
-    list.value = handleTree(data, 'id', 'parentId')
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  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 Demo02CategoryApi.deleteDemo02Category(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await Demo02CategoryApi.exportDemo02Category(queryParams)
-    download.excel(data, '示例分类.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-/** 展开/折叠操作 */
-const isExpandAll = ref(true) // 是否展开,默认全部展开
-const refreshTable = ref(true) // 重新渲染表格状态
-const toggleExpandAll = async () => {
-  refreshTable.value = false
-  isExpandAll.value = !isExpandAll.value
-  await nextTick()
-  refreshTable.value = true
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo03/erp/Demo03StudentForm.vue b/src/views/infra/demo/demo03/erp/Demo03StudentForm.vue
deleted file mode 100644
index 758c7e5..0000000
--- a/src/views/infra/demo/demo03/erp/Demo03StudentForm.vue
+++ /dev/null
@@ -1,121 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="性别" prop="sex">
-        <el-radio-group v-model="formData.sex">
-          <el-radio
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="出生日期" prop="birthday">
-        <el-date-picker
-          v-model="formData.birthday"
-          type="date"
-          value-format="x"
-          placeholder="选择出生日期"
-        />
-      </el-form-item>
-      <el-form-item label="简介" prop="description">
-        <Editor v-model="formData.description" height="150px" />
-      </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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-
-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: undefined,
-  sex: undefined,
-  birthday: undefined,
-  description: undefined
-})
-const formRules = reactive({
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
-  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
-  description: [{ 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 Demo03StudentApi.getDemo03Student(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
-    if (formType.value === 'create') {
-      await Demo03StudentApi.createDemo03Student(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo03StudentApi.updateDemo03Student(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    name: undefined,
-    sex: undefined,
-    birthday: undefined,
-    description: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue b/src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue
deleted file mode 100644
index 9d3888d..0000000
--- a/src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="分数" prop="score">
-        <el-input v-model="formData.score" 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">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-
-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,
-  studentId: undefined,
-  name: undefined,
-  score: undefined
-})
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 打开弹窗 */
-const open = async (type: string, id?: number, studentId: number) => {
-  dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
-  formType.value = type
-  resetForm()
-  formData.value.studentId = studentId
-  // 修改时,设置数据
-  if (id) {
-    formLoading.value = true
-    try {
-      formData.value = await Demo03StudentApi.getDemo03Course(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value
-    if (formType.value === 'create') {
-      await Demo03StudentApi.createDemo03Course(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo03StudentApi.updateDemo03Course(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    studentId: undefined,
-    name: undefined,
-    score: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue b/src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue
deleted file mode 100644
index 3dce77e..0000000
--- a/src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue
+++ /dev/null
@@ -1,130 +0,0 @@
-<template>
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-button
-      v-hasPermi="['infra:demo03-student:create']"
-      plain
-      type="primary"
-      @click="openForm('create')"
-    >
-      <Icon class="mr-5px" icon="ep:plus" />
-      新增
-    </el-button>
-    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
-      <el-table-column align="center" label="编号" prop="id" />
-      <el-table-column align="center" label="名字" prop="name" />
-      <el-table-column align="center" label="分数" prop="score" />
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="创建时间"
-        prop="createTime"
-        width="180px"
-      />
-      <el-table-column align="center" label="操作">
-        <template #default="scope">
-          <el-button
-            v-hasPermi="['infra:demo03-student:update']"
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-          >
-            编辑
-          </el-button>
-          <el-button
-            v-hasPermi="['infra:demo03-student:delete']"
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      v-model:limit="queryParams.pageSize"
-      v-model:page="queryParams.pageNo"
-      :total="total"
-      @pagination="getList"
-    />
-  </ContentWrap>
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo03CourseForm ref="formRef" @success="getList" />
-</template>
-
-<script lang="ts" setup>
-import { dateFormatter } from '@/utils/formatTime'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-import Demo03CourseForm from './Demo03CourseForm.vue'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const props = defineProps<{
-  studentId?: number // 学生编号(主表的关联字段)
-}>()
-const loading = ref(false) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  studentId: undefined as unknown
-})
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  (val: number) => {
-    if (!val) {
-      return
-    }
-    queryParams.studentId = val
-    handleQuery()
-  },
-  { immediate: true, deep: true }
-)
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03CoursePage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  if (!props.studentId) {
-    message.error('请选择一个学生')
-    return
-  }
-  formRef.value.open(type, id, props.studentId)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo03StudentApi.deleteDemo03Course(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-</script>
diff --git a/src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue b/src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue
deleted file mode 100644
index 5687294..0000000
--- a/src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="班主任" prop="teacher">
-        <el-input v-model="formData.teacher" 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">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-
-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,
-  studentId: undefined,
-  name: undefined,
-  teacher: undefined
-})
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 打开弹窗 */
-const open = async (type: string, id?: number, studentId: number) => {
-  dialogVisible.value = true
-  dialogTitle.value = t('action.' + type)
-  formType.value = type
-  resetForm()
-  formData.value.studentId = studentId
-  // 修改时,设置数据
-  if (id) {
-    formLoading.value = true
-    try {
-      formData.value = await Demo03StudentApi.getDemo03Grade(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value
-    if (formType.value === 'create') {
-      await Demo03StudentApi.createDemo03Grade(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo03StudentApi.updateDemo03Grade(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    studentId: undefined,
-    name: undefined,
-    teacher: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue b/src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue
deleted file mode 100644
index 635f321..0000000
--- a/src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue
+++ /dev/null
@@ -1,130 +0,0 @@
-<template>
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-button
-      v-hasPermi="['infra:demo03-student:create']"
-      plain
-      type="primary"
-      @click="openForm('create')"
-    >
-      <Icon class="mr-5px" icon="ep:plus" />
-      新增
-    </el-button>
-    <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
-      <el-table-column align="center" label="编号" prop="id" />
-      <el-table-column align="center" label="名字" prop="name" />
-      <el-table-column align="center" label="班主任" prop="teacher" />
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="创建时间"
-        prop="createTime"
-        width="180px"
-      />
-      <el-table-column align="center" label="操作">
-        <template #default="scope">
-          <el-button
-            v-hasPermi="['infra:demo03-student:update']"
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-          >
-            编辑
-          </el-button>
-          <el-button
-            v-hasPermi="['infra:demo03-student:delete']"
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      v-model:limit="queryParams.pageSize"
-      v-model:page="queryParams.pageNo"
-      :total="total"
-      @pagination="getList"
-    />
-  </ContentWrap>
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo03GradeForm ref="formRef" @success="getList" />
-</template>
-
-<script lang="ts" setup>
-import { dateFormatter } from '@/utils/formatTime'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-import Demo03GradeForm from './Demo03GradeForm.vue'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const props = defineProps<{
-  studentId?: number // 学生编号(主表的关联字段)
-}>()
-const loading = ref(false) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  studentId: undefined as unknown
-})
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  (val: number) => {
-    if (!val) {
-      return
-    }
-    queryParams.studentId = val
-    handleQuery()
-  },
-  { immediate: true, deep: true }
-)
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03GradePage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  if (!props.studentId) {
-    message.error('请选择一个学生')
-    return
-  }
-  formRef.value.open(type, id, props.studentId)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo03StudentApi.deleteDemo03Grade(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-</script>
diff --git a/src/views/infra/demo/demo03/erp/index.vue b/src/views/infra/demo/demo03/erp/index.vue
deleted file mode 100644
index b8e65b2..0000000
--- a/src/views/infra/demo/demo03/erp/index.vue
+++ /dev/null
@@ -1,246 +0,0 @@
-<template>
-  <ContentWrap>
-    <!-- 搜索工作栏 -->
-    <el-form
-      ref="queryFormRef"
-      :inline="true"
-      :model="queryParams"
-      class="-mb-15px"
-      label-width="68px"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input
-          v-model="queryParams.name"
-          class="!w-240px"
-          clearable
-          placeholder="请输入名字"
-          @keyup.enter="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="性别" prop="sex">
-        <el-select v-model="queryParams.sex" class="!w-240px" clearable placeholder="请选择性别">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
-        <el-date-picker
-          v-model="queryParams.createTime"
-          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
-          class="!w-240px"
-          end-placeholder="结束日期"
-          start-placeholder="开始日期"
-          type="daterange"
-          value-format="YYYY-MM-DD HH:mm:ss"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button @click="handleQuery">
-          <Icon class="mr-5px" icon="ep:search" />
-          搜索
-        </el-button>
-        <el-button @click="resetQuery">
-          <Icon class="mr-5px" icon="ep:refresh" />
-          重置
-        </el-button>
-        <el-button
-          v-hasPermi="['infra:demo03-student:create']"
-          plain
-          type="primary"
-          @click="openForm('create')"
-        >
-          <Icon class="mr-5px" icon="ep:plus" />
-          新增
-        </el-button>
-        <el-button
-          v-hasPermi="['infra:demo03-student:export']"
-          :loading="exportLoading"
-          plain
-          type="success"
-          @click="handleExport"
-        >
-          <Icon class="mr-5px" icon="ep:download" />
-          导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table
-      v-loading="loading"
-      :data="list"
-      :show-overflow-tooltip="true"
-      :stripe="true"
-      highlight-current-row
-      @current-change="handleCurrentChange"
-    >
-      <el-table-column align="center" label="编号" prop="id" />
-      <el-table-column align="center" label="名字" prop="name" />
-      <el-table-column align="center" label="性别" prop="sex">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="出生日期"
-        prop="birthday"
-        width="180px"
-      />
-      <el-table-column align="center" label="简介" prop="description" />
-      <el-table-column
-        :formatter="dateFormatter"
-        align="center"
-        label="创建时间"
-        prop="createTime"
-        width="180px"
-      />
-      <el-table-column align="center" label="操作">
-        <template #default="scope">
-          <el-button
-            v-hasPermi="['infra:demo03-student:update']"
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-          >
-            编辑
-          </el-button>
-          <el-button
-            v-hasPermi="['infra:demo03-student:delete']"
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <!-- 分页 -->
-    <Pagination
-      v-model:limit="queryParams.pageSize"
-      v-model:page="queryParams.pageNo"
-      :total="total"
-      @pagination="getList"
-    />
-  </ContentWrap>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo03StudentForm ref="formRef" @success="getList" />
-  <!-- 子表的列表 -->
-  <ContentWrap>
-    <el-tabs model-value="demo03Course">
-      <el-tab-pane label="学生课程" name="demo03Course">
-        <Demo03CourseList :student-id="currentRow?.id" />
-      </el-tab-pane>
-      <el-tab-pane label="学生班级" name="demo03Grade">
-        <Demo03GradeList :student-id="currentRow?.id" />
-      </el-tab-pane>
-    </el-tabs>
-  </ContentWrap>
-</template>
-
-<script lang="ts" setup>
-import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import download from '@/utils/download'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'
-import Demo03StudentForm from './Demo03StudentForm.vue'
-import Demo03CourseList from './components/Demo03CourseList.vue'
-import Demo03GradeList from './components/Demo03GradeList.vue'
-
-defineOptions({ name: 'Demo03Student' })
-
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  name: null,
-  sex: null,
-  description: null,
-  createTime: []
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo03StudentApi.deleteDemo03Student(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await Demo03StudentApi.exportDemo03Student(queryParams)
-    download.excel(data, '学生.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-/** 选中行操作 */
-const currentRow = ref({}) // 选中行
-const handleCurrentChange = (row) => {
-  currentRow.value = row
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo03/inner/Demo03StudentForm.vue b/src/views/infra/demo/demo03/inner/Demo03StudentForm.vue
deleted file mode 100644
index 98c1b7b..0000000
--- a/src/views/infra/demo/demo03/inner/Demo03StudentForm.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="性别" prop="sex">
-        <el-radio-group v-model="formData.sex">
-          <el-radio
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="出生日期" prop="birthday">
-        <el-date-picker
-          v-model="formData.birthday"
-          type="date"
-          value-format="x"
-          placeholder="选择出生日期"
-        />
-      </el-form-item>
-      <el-form-item label="简介" prop="description">
-        <Editor v-model="formData.description" height="150px" />
-      </el-form-item>
-    </el-form>
-    <!-- 子表的表单 -->
-    <el-tabs v-model="subTabsName">
-      <el-tab-pane label="学生课程" name="demo03Course">
-        <Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData.id" />
-      </el-tab-pane>
-      <el-tab-pane label="学生班级" name="demo03Grade">
-        <Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData.id" />
-      </el-tab-pane>
-    </el-tabs>
-    <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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-import Demo03CourseForm from './components/Demo03CourseForm.vue'
-import Demo03GradeForm from './components/Demo03GradeForm.vue'
-
-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: undefined,
-  sex: undefined,
-  birthday: undefined,
-  description: undefined
-})
-const formRules = reactive({
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
-  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
-  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 子表的表单 */
-const subTabsName = ref('demo03Course')
-const demo03CourseFormRef = ref()
-const demo03GradeFormRef = 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 Demo03StudentApi.getDemo03Student(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 校验子表单
-  try {
-    await demo03CourseFormRef.value.validate()
-  } catch (e) {
-    subTabsName.value = 'demo03Course'
-    return
-  }
-  try {
-    await demo03GradeFormRef.value.validate()
-  } catch (e) {
-    subTabsName.value = 'demo03Grade'
-    return
-  }
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
-    // 拼接子表的数据
-    data.demo03Courses = demo03CourseFormRef.value.getData()
-    data.demo03Grade = demo03GradeFormRef.value.getData()
-    if (formType.value === 'create') {
-      await Demo03StudentApi.createDemo03Student(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo03StudentApi.updateDemo03Student(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    name: undefined,
-    sex: undefined,
-    birthday: undefined,
-    description: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue b/src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue
deleted file mode 100644
index 77da45f..0000000
--- a/src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<template>
-  <el-form
-    ref="formRef"
-    :model="formData"
-    :rules="formRules"
-    v-loading="formLoading"
-    label-width="0px"
-    :inline-message="true"
-  >
-    <el-table :data="formData" class="-mt-10px">
-      <el-table-column label="序号" type="index" width="100" />
-      <el-table-column label="名字" min-width="150">
-        <template #default="{ row, $index }">
-          <el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
-            <el-input v-model="row.name" placeholder="请输入名字" />
-          </el-form-item>
-        </template>
-      </el-table-column>
-      <el-table-column label="分数" min-width="150">
-        <template #default="{ row, $index }">
-          <el-form-item :prop="`${$index}.score`" :rules="formRules.score" class="mb-0px!">
-            <el-input v-model="row.score" placeholder="请输入分数" />
-          </el-form-item>
-        </template>
-      </el-table-column>
-      <el-table-column align="center" fixed="right" label="操作" width="60">
-        <template #default="{ $index }">
-          <el-button @click="handleDelete($index)" link>—</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-  </el-form>
-  <el-row justify="center" class="mt-3">
-    <el-button @click="handleAdd" round>+ 添加学生课程</el-button>
-  </el-row>
-</template>
-<script setup lang="ts">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const formLoading = ref(false) // 表单的加载中
-const formData = ref([])
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  async (val) => {
-    // 1. 重置表单
-    formData.value = []
-    // 2. val 非空,则加载数据
-    if (!val) {
-      return
-    }
-    try {
-      formLoading.value = true
-      formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)
-    } finally {
-      formLoading.value = false
-    }
-  },
-  { immediate: true }
-)
-
-/** 新增按钮操作 */
-const handleAdd = () => {
-  const row = {
-    id: undefined,
-    studentId: undefined,
-    name: undefined,
-    score: undefined
-  }
-  row.studentId = props.studentId
-  formData.value.push(row)
-}
-
-/** 删除按钮操作 */
-const handleDelete = (index) => {
-  formData.value.splice(index, 1)
-}
-
-/** 表单校验 */
-const validate = () => {
-  return formRef.value.validate()
-}
-
-/** 表单值 */
-const getData = () => {
-  return formData.value
-}
-
-defineExpose({ validate, getData })
-</script>
diff --git a/src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue b/src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue
deleted file mode 100644
index 965b473..0000000
--- a/src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<template>
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column label="分数" align="center" prop="score" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-    </el-table>
-  </ContentWrap>
-</template>
-<script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const loading = ref(false) // 列表的加载中
-const list = ref([]) // 列表的数据
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    list.value = await Demo03StudentApi.getDemo03CourseListByStudentId(props.studentId)
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue b/src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue
deleted file mode 100644
index e14bac4..0000000
--- a/src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<template>
-  <el-form
-    ref="formRef"
-    :model="formData"
-    :rules="formRules"
-    label-width="100px"
-    v-loading="formLoading"
-  >
-    <el-form-item label="名字" prop="name">
-      <el-input v-model="formData.name" placeholder="请输入名字" />
-    </el-form-item>
-    <el-form-item label="班主任" prop="teacher">
-      <el-input v-model="formData.teacher" placeholder="请输入班主任" />
-    </el-form-item>
-  </el-form>
-</template>
-<script setup lang="ts">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const formLoading = ref(false) // 表单的加载中
-const formData = ref([])
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  async (val) => {
-    // 1. 重置表单
-    formData.value = {
-      id: undefined,
-      studentId: undefined,
-      name: undefined,
-      teacher: undefined
-    }
-    // 2. val 非空,则加载数据
-    if (!val) {
-      return
-    }
-    try {
-      formLoading.value = true
-      const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)
-      if (!data) {
-        return
-      }
-      formData.value = data
-    } finally {
-      formLoading.value = false
-    }
-  },
-  { immediate: true }
-)
-
-/** 表单校验 */
-const validate = () => {
-  return formRef.value.validate()
-}
-
-/** 表单值 */
-const getData = () => {
-  return formData.value
-}
-
-defineExpose({ validate, getData })
-</script>
diff --git a/src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue b/src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue
deleted file mode 100644
index e631384..0000000
--- a/src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<template>
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column label="班主任" align="center" prop="teacher" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-    </el-table>
-  </ContentWrap>
-</template>
-<script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const loading = ref(false) // 列表的加载中
-const list = ref([]) // 列表的数据
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03GradeByStudentId(props.studentId)
-    if (!data) {
-      return
-    }
-    list.value.push(data)
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo03/inner/index.vue b/src/views/infra/demo/demo03/inner/index.vue
deleted file mode 100644
index 6d662cc..0000000
--- a/src/views/infra/demo/demo03/inner/index.vue
+++ /dev/null
@@ -1,227 +0,0 @@
-<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="sex">
-        <el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-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="['infra:demo03-student:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['infra:demo03-student:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <!-- 子表的列表 -->
-      <el-table-column type="expand">
-        <template #default="scope">
-          <el-tabs model-value="demo03Course">
-            <el-tab-pane label="学生课程" name="demo03Course">
-              <Demo03CourseList :student-id="scope.row.id" />
-            </el-tab-pane>
-            <el-tab-pane label="学生班级" name="demo03Grade">
-              <Demo03GradeList :student-id="scope.row.id" />
-            </el-tab-pane>
-          </el-tabs>
-        </template>
-      </el-table-column>
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column label="性别" align="center" prop="sex">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="出生日期"
-        align="center"
-        prop="birthday"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="简介" align="center" prop="description" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['infra:demo03-student:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['infra:demo03-student: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>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo03StudentForm ref="formRef" @success="getList" />
-</template>
-
-<script setup lang="ts">
-import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import download from '@/utils/download'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'
-import Demo03StudentForm from './Demo03StudentForm.vue'
-import Demo03CourseList from './components/Demo03CourseList.vue'
-import Demo03GradeList from './components/Demo03GradeList.vue'
-
-defineOptions({ name: 'Demo03Student' })
-
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  name: null,
-  sex: null,
-  description: null,
-  createTime: []
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo03StudentApi.deleteDemo03Student(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await Demo03StudentApi.exportDemo03Student(queryParams)
-    download.excel(data, '学生.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/demo/demo03/normal/Demo03StudentForm.vue b/src/views/infra/demo/demo03/normal/Demo03StudentForm.vue
deleted file mode 100644
index 4815357..0000000
--- a/src/views/infra/demo/demo03/normal/Demo03StudentForm.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-<template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
-    <el-form
-      ref="formRef"
-      :model="formData"
-      :rules="formRules"
-      label-width="100px"
-      v-loading="formLoading"
-    >
-      <el-form-item label="名字" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名字" />
-      </el-form-item>
-      <el-form-item label="性别" prop="sex">
-        <el-radio-group v-model="formData.sex">
-          <el-radio
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.value"
-          >
-            {{ dict.label }}
-          </el-radio>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="出生日期" prop="birthday">
-        <el-date-picker
-          v-model="formData.birthday"
-          type="date"
-          value-format="x"
-          placeholder="选择出生日期"
-        />
-      </el-form-item>
-      <el-form-item label="简介" prop="description">
-        <Editor v-model="formData.description" height="150px" />
-      </el-form-item>
-    </el-form>
-    <!-- 子表的表单 -->
-    <el-tabs v-model="subTabsName">
-      <el-tab-pane label="学生课程" name="demo03Course">
-        <Demo03CourseForm ref="demo03CourseFormRef" :student-id="formData.id" />
-      </el-tab-pane>
-      <el-tab-pane label="学生班级" name="demo03Grade">
-        <Demo03GradeForm ref="demo03GradeFormRef" :student-id="formData.id" />
-      </el-tab-pane>
-    </el-tabs>
-    <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 { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
-import Demo03CourseForm from './components/Demo03CourseForm.vue'
-import Demo03GradeForm from './components/Demo03GradeForm.vue'
-
-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: undefined,
-  sex: undefined,
-  birthday: undefined,
-  description: undefined
-})
-const formRules = reactive({
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
-  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
-  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 子表的表单 */
-const subTabsName = ref('demo03Course')
-const demo03CourseFormRef = ref()
-const demo03GradeFormRef = 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 Demo03StudentApi.getDemo03Student(id)
-    } finally {
-      formLoading.value = false
-    }
-  }
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-/** 提交表单 */
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  await formRef.value.validate()
-  // 校验子表单
-  try {
-    await demo03CourseFormRef.value.validate()
-  } catch (e) {
-    subTabsName.value = 'demo03Course'
-    return
-  }
-  try {
-    await demo03GradeFormRef.value.validate()
-  } catch (e) {
-    subTabsName.value = 'demo03Grade'
-    return
-  }
-  // 提交请求
-  formLoading.value = true
-  try {
-    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO
-    // 拼接子表的数据
-    data.demo03Courses = demo03CourseFormRef.value.getData()
-    data.demo03Grade = demo03GradeFormRef.value.getData()
-    if (formType.value === 'create') {
-      await Demo03StudentApi.createDemo03Student(data)
-      message.success(t('common.createSuccess'))
-    } else {
-      await Demo03StudentApi.updateDemo03Student(data)
-      message.success(t('common.updateSuccess'))
-    }
-    dialogVisible.value = false
-    // 发送操作成功的事件
-    emit('success')
-  } finally {
-    formLoading.value = false
-  }
-}
-
-/** 重置表单 */
-const resetForm = () => {
-  formData.value = {
-    id: undefined,
-    name: undefined,
-    sex: undefined,
-    birthday: undefined,
-    description: undefined
-  }
-  formRef.value?.resetFields()
-}
-</script>
diff --git a/src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue b/src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue
deleted file mode 100644
index f439c3f..0000000
--- a/src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-<template>
-  <el-form
-    ref="formRef"
-    :model="formData"
-    :rules="formRules"
-    v-loading="formLoading"
-    label-width="0px"
-    :inline-message="true"
-  >
-    <el-table :data="formData" class="-mt-10px">
-      <el-table-column label="序号" type="index" width="100" />
-      <el-table-column label="名字" min-width="150">
-        <template #default="{ row, $index }">
-          <el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
-            <el-input v-model="row.name" placeholder="请输入名字" />
-          </el-form-item>
-        </template>
-      </el-table-column>
-      <el-table-column label="分数" min-width="150">
-        <template #default="{ row, $index }">
-          <el-form-item :prop="`${$index}.score`" :rules="formRules.score" class="mb-0px!">
-            <el-input v-model="row.score" placeholder="请输入分数" />
-          </el-form-item>
-        </template>
-      </el-table-column>
-      <el-table-column align="center" fixed="right" label="操作" width="60">
-        <template #default="{ $index }">
-          <el-button @click="handleDelete($index)" link>—</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-  </el-form>
-  <el-row justify="center" class="mt-3">
-    <el-button @click="handleAdd" round>+ 添加学生课程</el-button>
-  </el-row>
-</template>
-<script setup lang="ts">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const formLoading = ref(false) // 表单的加载中
-const formData = ref([])
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  async (val) => {
-    // 1. 重置表单
-    formData.value = []
-    // 2. val 非空,则加载数据
-    if (!val) {
-      return
-    }
-    try {
-      formLoading.value = true
-      formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)
-    } finally {
-      formLoading.value = false
-    }
-  },
-  { immediate: true }
-)
-
-/** 新增按钮操作 */
-const handleAdd = () => {
-  const row = {
-    id: undefined,
-    studentId: undefined,
-    name: undefined,
-    score: undefined
-  }
-  row.studentId = props.studentId
-  formData.value.push(row)
-}
-
-/** 删除按钮操作 */
-const handleDelete = (index) => {
-  formData.value.splice(index, 1)
-}
-
-/** 表单校验 */
-const validate = () => {
-  return formRef.value.validate()
-}
-
-/** 表单值 */
-const getData = () => {
-  return formData.value
-}
-
-defineExpose({ validate, getData })
-</script>
diff --git a/src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue b/src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue
deleted file mode 100644
index c711954..0000000
--- a/src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue
+++ /dev/null
@@ -1,72 +0,0 @@
-<template>
-  <el-form
-    ref="formRef"
-    :model="formData"
-    :rules="formRules"
-    label-width="100px"
-    v-loading="formLoading"
-  >
-    <el-form-item label="名字" prop="name">
-      <el-input v-model="formData.name" placeholder="请输入名字" />
-    </el-form-item>
-    <el-form-item label="班主任" prop="teacher">
-      <el-input v-model="formData.teacher" placeholder="请输入班主任" />
-    </el-form-item>
-  </el-form>
-</template>
-<script setup lang="ts">
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
-
-const props = defineProps<{
-  studentId: undefined // 学生编号(主表的关联字段)
-}>()
-const formLoading = ref(false) // 表单的加载中
-const formData = ref([])
-const formRules = reactive({
-  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],
-  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
-  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]
-})
-const formRef = ref() // 表单 Ref
-
-/** 监听主表的关联字段的变化,加载对应的子表数据 */
-watch(
-  () => props.studentId,
-  async (val) => {
-    // 1. 重置表单
-    formData.value = {
-      id: undefined,
-      studentId: undefined,
-      name: undefined,
-      teacher: undefined
-    }
-    // 2. val 非空,则加载数据
-    if (!val) {
-      return
-    }
-    try {
-      formLoading.value = true
-      const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)
-      if (!data) {
-        return
-      }
-      formData.value = data
-    } finally {
-      formLoading.value = false
-    }
-  },
-  { immediate: true }
-)
-
-/** 表单校验 */
-const validate = () => {
-  return formRef.value.validate()
-}
-
-/** 表单值 */
-const getData = () => {
-  return formData.value
-}
-
-defineExpose({ validate, getData })
-</script>
diff --git a/src/views/infra/demo/demo03/normal/index.vue b/src/views/infra/demo/demo03/normal/index.vue
deleted file mode 100644
index 0ea677d..0000000
--- a/src/views/infra/demo/demo03/normal/index.vue
+++ /dev/null
@@ -1,212 +0,0 @@
-<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="sex">
-        <el-select v-model="queryParams.sex" placeholder="请选择性别" clearable class="!w-240px">
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
-      <el-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="['infra:demo03-student:create']"
-        >
-          <Icon icon="ep:plus" class="mr-5px" /> 新增
-        </el-button>
-        <el-button
-          type="success"
-          plain
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['infra:demo03-student:export']"
-        >
-          <Icon icon="ep:download" class="mr-5px" /> 导出
-        </el-button>
-      </el-form-item>
-    </el-form>
-  </ContentWrap>
-
-  <!-- 列表 -->
-  <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名字" align="center" prop="name" />
-      <el-table-column label="性别" align="center" prop="sex">
-        <template #default="scope">
-          <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
-        </template>
-      </el-table-column>
-      <el-table-column
-        label="出生日期"
-        align="center"
-        prop="birthday"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="简介" align="center" prop="description" />
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        :formatter="dateFormatter"
-        width="180px"
-      />
-      <el-table-column label="操作" align="center">
-        <template #default="scope">
-          <el-button
-            link
-            type="primary"
-            @click="openForm('update', scope.row.id)"
-            v-hasPermi="['infra:demo03-student:update']"
-          >
-            编辑
-          </el-button>
-          <el-button
-            link
-            type="danger"
-            @click="handleDelete(scope.row.id)"
-            v-hasPermi="['infra:demo03-student: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>
-
-  <!-- 表单弹窗:添加/修改 -->
-  <Demo03StudentForm ref="formRef" @success="getList" />
-</template>
-
-<script setup lang="ts">
-import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
-import { dateFormatter } from '@/utils/formatTime'
-import download from '@/utils/download'
-import * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'
-import Demo03StudentForm from './Demo03StudentForm.vue'
-
-defineOptions({ name: 'Demo03Student' })
-
-const message = useMessage() // 消息弹窗
-const { t } = useI18n() // 国际化
-
-const loading = ref(true) // 列表的加载中
-const list = ref([]) // 列表的数据
-const total = ref(0) // 列表的总页数
-const queryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  name: null,
-  sex: null,
-  description: null,
-  createTime: []
-})
-const queryFormRef = ref() // 搜索的表单
-const exportLoading = ref(false) // 导出的加载中
-
-/** 查询列表 */
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)
-    list.value = data.list
-    total.value = data.total
-  } finally {
-    loading.value = false
-  }
-}
-
-/** 搜索按钮操作 */
-const handleQuery = () => {
-  queryParams.pageNo = 1
-  getList()
-}
-
-/** 重置按钮操作 */
-const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  handleQuery()
-}
-
-/** 添加/修改操作 */
-const formRef = ref()
-const openForm = (type: string, id?: number) => {
-  formRef.value.open(type, id)
-}
-
-/** 删除按钮操作 */
-const handleDelete = async (id: number) => {
-  try {
-    // 删除的二次确认
-    await message.delConfirm()
-    // 发起删除
-    await Demo03StudentApi.deleteDemo03Student(id)
-    message.success(t('common.delSuccess'))
-    // 刷新列表
-    await getList()
-  } catch {}
-}
-
-/** 导出按钮操作 */
-const handleExport = async () => {
-  try {
-    // 导出的二次确认
-    await message.exportConfirm()
-    // 发起导出
-    exportLoading.value = true
-    const data = await Demo03StudentApi.exportDemo03Student(queryParams)
-    download.excel(data, '学生.xls')
-  } catch {
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-/** 初始化 **/
-onMounted(() => {
-  getList()
-})
-</script>
diff --git a/src/views/infra/server/index.vue b/src/views/infra/server/index.vue
index da839bd..84e4bce 100644
--- a/src/views/infra/server/index.vue
+++ b/src/views/infra/server/index.vue
@@ -14,9 +14,6 @@
 /** 初始化 */
 onMounted(async () => {
   try {
-    // 友情提示:如果访问出现 404 问题:
-    // 1)boot 参考 https://doc.iocoder.cn/server-monitor/ 解决;
-    // 2)cloud 参考 https://cloud.iocoder.cn/server-monitor/ 解决
     const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
     if (data && data.length > 0) {
       src.value = data
diff --git a/src/views/infra/storage/index_rec.vue b/src/views/infra/storage/index_rec.vue
new file mode 100644
index 0000000..f6dfc23
--- /dev/null
+++ b/src/views/infra/storage/index_rec.vue
@@ -0,0 +1,161 @@
+<template>
+  <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
+    <el-row>
+      <!-- 磁盘使用量统计 -->
+        <el-col :span="12" class="mt-3">
+          <el-card class="ml-3" :gutter="12" shadow="hover">
+<!--            <div ref="chartRef"  style="width: 100%; height: 90%"></div>-->
+            <Echart :options="usedDiskEchartChika" :height="420" />
+<!--            <Echart :options="usedDiskEchartChika" :height="420" />-->
+          </el-card>
+        </el-col>
+    </el-row>
+  </el-scrollbar>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted } from "vue";
+import * as StorageApi from '@/api/infra/storage'
+import { StorageMonitorInfoVO } from '@/api/infra/storage/types'
+const disks = ref<StorageMonitorInfoVO>()
+const disk = ref<StorageMonitorInfoVO>()
+
+// 基本信息
+const readDiskInfo = async () => {
+  const data = await StorageApi.getDiskInfo()
+  disks.value = data
+  disk.value = data[0]
+}
+
+// 内存使用情况
+const usedDiskEchartChika = reactive<any>({
+  title: {
+    // 仪表盘标题。
+    text: '磁盘使用情况',
+    left: 'center',
+    show: true, // 是否显示标题,默认 true。
+    offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
+    color: 'yellow', // 文字的颜色,默认 #333。
+    fontSize: 20 // 文字的字体大小,默认 15。
+  },
+  toolbox: {
+    show: false,
+    feature: {
+      restore: { show: true },
+      saveAsImage: { show: true }
+    }
+  },
+  series: [
+    {
+      name: '峰值',
+      type: 'gauge',
+      min: 0,
+      max: 500,
+      splitNumber: 10,
+      //这是指针的颜色
+      color: '#F5C74E',
+      radius: '85%',
+      center: ['50%', '50%'],
+      startAngle: 225,
+      endAngle: -45,
+      axisLine: {
+        // 坐标轴线
+        lineStyle: {
+          // 属性lineStyle控制线条样式
+          color: [
+            [0.2, '#7FFF00'],
+            [0.8, '#00FFFF'],
+            [1, '#FF0000']
+          ],
+          //width: 6 外框的大小(环的宽度)
+          width: 10
+        }
+      },
+      axisTick: {
+        // 坐标轴小标记
+        //里面的线长是5(短线)
+        length: 5, // 属性length控制线长
+        lineStyle: {
+          // 属性lineStyle控制线条样式
+          color: '#76D9D7'
+        }
+      },
+      splitLine: {
+        // 分隔线
+        length: 20, // 属性length控制线长
+        lineStyle: {
+          // 属性lineStyle(详见lineStyle)控制线条样式
+          color: '#76D9D7'
+        }
+      },
+      axisLabel: {
+        color: '#76D9D7',
+        distance: 15,
+        fontSize: 15
+      },
+      pointer: {
+        // 指针的大小
+        width: 7,
+        show: true
+      },
+      detail: {
+        textStyle: {
+          fontWeight: 'normal',
+          // 里面文字下的数值大小(50)
+          fontSize: 15,
+          color: '#FFFFFF'
+        },
+        valueAnimation: true
+      },
+      progress: {
+        show: true
+      }
+    }
+  ]
+})
+
+
+/** 加载数据 */
+const getSummary = () => {
+  // 初始化命令图表
+  usedDiskInstance()
+}
+
+const usedDiskInstance = async () => {
+  try {
+    const data = await StorageApi.getDiskInfo()
+    disks.value = data
+    disk.value = data[0]
+    // data.forEach((disk) => {
+      console.log(disk.value)
+      console.log(disk.value.name)
+      console.log(disk.value!.restPPT)
+      // 仪表盘详情,用于显示数据。
+      usedDiskEchartChika.series[0].detail = {
+        show: true, // 是否显示详情,默认 true。
+        offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
+        color: 'auto', // 文字的颜色,默认 auto。
+        fontSize: 30, // 文字的字体大小,默认 15。
+        formatter: disk.value!.restPPT // 格式化函数或者字符串
+      }
+      console.log(disk.value.restPPT)
+      usedDiskEchartChika.series[0].data[0] = {
+        value: disk.value!.restPPT,
+        name: '磁盘消耗'
+      }
+      console.log(disk.value)
+      usedDiskEchartChika.tooltip = {
+        formatter: '{b} <br/>{a} : ' + disk.value!.restPPT
+      }
+    // })
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  readDiskInfo()
+  // 读取 redis 信息
+  // readDiskInfo()
+  // // 加载数据
+  getSummary()
+})
+</script>
diff --git a/src/views/system/appgroup/AppGroupForm.vue b/src/views/system/appgroup/AppGroupForm.vue
new file mode 100644
index 0000000..6b60e72
--- /dev/null
+++ b/src/views/system/appgroup/AppGroupForm.vue
@@ -0,0 +1,130 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="80px"
+    >
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="分组编号" prop="appCode">
+            <el-input v-model="formData.code" placeholder="请输入应用分组编号" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="分组名称" prop="appName">
+            <el-input v-model="formData.name" placeholder="请输入应用分组名称" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="分组图标" prop="icon">
+            <UploadImg v-model="formData.icon" :limit="1" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="排序" prop="orderNum">
+            <el-input-number v-model="formData.sort" placeholder="请输入排序" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row>
+        <el-col :span="24">
+          <el-form-item label="备注" prop="remark">
+            <el-input v-model="formData.remark" clearable type="textarea" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+  import * as AppGroupApi from '@/api/system/appgroup'
+
+  defineOptions({ name: 'SystemAppGroupForm' })
+
+  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,
+    code: undefined,
+    name: undefined,
+    icon: undefined,
+    sort: undefined,
+    remark: undefined
+  })
+  const formRules = reactive({
+    code: [{ required: true, message: '分组编号不能为空', trigger: 'blur' }],
+    name: [{ 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 AppGroupApi.getAppGroup(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 unknown as AppGroupApi.AppGroupVO
+      if (formType.value === 'create') {
+        await AppGroupApi.createAppGroup(data)
+        message.success(t('common.createSuccess'))
+      } else {
+        await AppGroupApi.updateAppGroup(data)
+        message.success(t('common.updateSuccess'))
+      }
+      dialogVisible.value = false
+      // 发送操作成功的事件
+      emit('success')
+    } finally {
+      formLoading.value = false
+    }
+  }
+
+  /** 重置表单 */
+  const resetForm = () => {
+    formData.value = {
+      id: undefined,
+      code: undefined,
+      name: undefined,
+      icon: undefined,
+      sort: undefined,
+      remark: undefined,
+    }
+    formRef.value?.resetFields()
+  }
+</script>
diff --git a/src/views/system/appgroup/index.vue b/src/views/system/appgroup/index.vue
new file mode 100644
index 0000000..0e662ba
--- /dev/null
+++ b/src/views/system/appgroup/index.vue
@@ -0,0 +1,176 @@
+<template>
+  <!-- 搜索 -->
+  <ContentWrap>
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="分组编号" prop="appCode">
+        <el-input
+          v-model="queryParams.code"
+          placeholder="请输入分组编号"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="分组名称" prop="appName">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入分组名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px" />
+          重置
+        </el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['system:app-group:create']"
+        >
+          <Icon icon="ep:plus" 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="code" />
+      <el-table-column label="分组名称" align="center" prop="name" />
+      <el-table-column label="分组图标" align="center" prop="logo">
+        <template #default="scope">
+          <img width="40px" height="40px" :src="scope.row.icon" />
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column label="备注" align="center" prop="remark" width="200" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        width="180"
+        :formatter="dateFormatter"
+      />
+      <el-table-column label="操作" align="center" min-width="110" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['system:app-group:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['system:app-group:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <AppGroupForm ref="formRef" @success="getList" />
+
+</template>
+<script lang="ts" setup>
+  import {dateFormatter} from '@/utils/formatTime'
+  import * as AppGroupApi from '@/api/system/appgroup'
+  import AppGroupForm from './AppGroupForm.vue'
+  import * as TenantApi from "@/api/system/tenant";
+
+  defineOptions({name: 'SystemAppGroup'})
+
+  const message = useMessage() // 消息弹窗
+  const {t} = useI18n() // 国际化
+
+  const loading = ref(true) // 列表的加载中
+  const total = ref(0) // 列表的总页数
+  const list = ref([]) // 列表的数据
+  const queryParams = reactive({
+    pageNo: 1,
+    pageSize: 10,
+    code: undefined,
+    name: undefined,
+    createTime: []
+  })
+  const queryFormRef = ref() // 搜索的表单
+
+  /** 查询列表 */
+  const getList = async () => {
+    loading.value = true
+    try {
+      const data = await AppGroupApi.getAppGroupPage(queryParams)
+      list.value = data.list
+      total.value = data.total
+    } finally {
+      loading.value = false
+    }
+  }
+
+  /** 搜索按钮操作 */
+  const handleQuery = () => {
+    queryParams.pageNo = 1
+    getList()
+  }
+
+  /** 重置按钮操作 */
+  const resetQuery = () => {
+    queryFormRef.value.resetFields()
+    handleQuery()
+  }
+
+  /** 添加/修改操作 */
+  const formRef = ref()
+  const openForm = (type: string, id?: number) => {
+    formRef.value.open(type, id)
+  }
+
+  /** 删除按钮操作 */
+  const handleDelete = async (id: number) => {
+    try {
+      // 删除的二次确认
+      await message.delConfirm()
+      // 发起删除
+      await AppGroupApi.deleteAppGroup(id)
+      message.success(t('common.delSuccess'))
+      // 刷新列表
+      await getList()
+    } catch {
+    }
+  }
+
+  /** 初始化 **/
+  onMounted(async () => {
+    await getList()
+  })
+
+</script>
diff --git a/src/views/system/appmenu/AppMenuForm.vue b/src/views/system/appmenu/AppMenuForm.vue
new file mode 100644
index 0000000..3a7ad72
--- /dev/null
+++ b/src/views/system/appmenu/AppMenuForm.vue
@@ -0,0 +1,256 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+    >
+      <el-form-item label="应用名称">
+        <el-tree-select
+          v-model="formData.parentId"
+          :data="appMenuTree"
+          :default-expanded-keys="[0]"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+        />
+      </el-form-item>
+      <el-form-item label="菜单名称" prop="name">
+        <el-input v-model="formData.name" clearable placeholder="请输入菜单名称" />
+      </el-form-item>
+      <el-form-item label="菜单类型" prop="type">
+        <el-radio-group v-model="formData.type">
+          <el-radio-button
+            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_APP_MENU_TYPE)"
+            :key="dict.label"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="formData.type !== 3" label="菜单图标">
+        <IconSelect v-model="formData.icon" clearable />
+      </el-form-item>
+      <el-form-item v-if="formData.type !== 3" label="路由地址" prop="path">
+        <template #label>
+          <Tooltip
+            message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
+            title="路由地址"
+          />
+        </template>
+        <el-input v-model="formData.path" clearable placeholder="请输入路由地址" />
+      </el-form-item>
+      <el-form-item v-if="formData.type === 2" label="组件地址" prop="component">
+        <el-input v-model="formData.component" clearable placeholder="例如说:system/user/index" />
+      </el-form-item>
+      <el-form-item v-if="formData.type === 2" label="组件名字" prop="componentName">
+        <el-input v-model="formData.componentName" clearable placeholder="例如说:SystemUser" />
+      </el-form-item>
+      <el-form-item v-if="formData.type !== 1" label="权限标识" prop="permission">
+        <template #label>
+          <Tooltip
+            message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
+            title="权限标识"
+          />
+        </template>
+        <el-input v-model="formData.permission" clearable placeholder="请输入权限标识" />
+      </el-form-item>
+      <el-form-item label="显示排序" prop="sort">
+        <el-input-number v-model="formData.sort" :min="0" clearable controls-position="right" />
+      </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.label"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible">
+        <template #label>
+          <Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" title="显示状态" />
+        </template>
+        <el-radio-group v-model="formData.visible">
+          <el-radio key="true" :label="true" border>显示</el-radio>
+          <el-radio key="false" :label="false" border>隐藏</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="formData.type !== 3" label="总是显示" prop="alwaysShow">
+        <template #label>
+          <Tooltip
+            message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
+            title="总是显示"
+          />
+        </template>
+        <el-radio-group v-model="formData.alwaysShow">
+          <el-radio key="true" :label="true" border>总是</el-radio>
+          <el-radio key="false" :label="false" border>不是</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item v-if="formData.type === 2" label="缓存状态" prop="keepAlive">
+        <template #label>
+          <Tooltip
+            message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
+            title="缓存状态"
+          />
+        </template>
+        <el-radio-group v-model="formData.keepAlive">
+          <el-radio key="true" :label="true" border>缓存</el-radio>
+          <el-radio key="false" :label="false" border>不缓存</el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import * as MenuApi from '@/api/system/menu'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { CommonStatusEnum, SystemAppMenuTypeEnum } from '@/utils/constants'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+defineOptions({ name: 'SystemAppMenuForm' })
+
+const { wsCache } = useCache()
+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: '',
+  permission: '',
+  type: SystemAppMenuTypeEnum.DIR,
+  sort: Number(undefined),
+  parentId: 0,
+  path: '',
+  icon: '',
+  component: '',
+  componentName: '',
+  status: CommonStatusEnum.ENABLE,
+  visible: true,
+  keepAlive: true,
+  alwaysShow: true
+})
+const formRules = reactive({
+  name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
+  path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number, parentId?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  if (parentId) {
+    formData.value.parentId = parentId
+  }
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await MenuApi.getAppMenu(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得菜单列表
+  await getTree()
+}
+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 {
+    if (
+      formData.value.type === SystemAppMenuTypeEnum.DIR ||
+      formData.value.type === SystemAppMenuTypeEnum.MENU
+    ) {
+      if (!isExternal(formData.value.path)) {
+        if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') {
+          message.error('路径必须以 / 开头')
+          return
+        } /*else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') {
+          message.error('路径不能以 / 开头')
+          return
+        }*/
+      }
+    }
+    const data = formData.value as unknown as MenuApi.MenuVO
+    if (formType.value === 'create') {
+      await MenuApi.createAppMenu(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await MenuApi.updateAppMenu(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+    // 清空,从而触发刷新
+    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+  }
+}
+
+/** 获取下拉框[上级菜单]的数据  */
+const appMenuTree = ref<Tree[]>([]) // 树形结构
+const getTree = async () => {
+  appMenuTree.value = []
+  const res = await MenuApi.getSimpleAppMenusList()
+  let menu: Tree = { id: 0, name: '主应用', children: [] }
+  menu.children = handleTree(res)
+  appMenuTree.value.push(menu)
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    name: '',
+    permission: '',
+    type: SystemAppMenuTypeEnum.DIR,
+    sort: Number(undefined),
+    parentId: 0,
+    path: '',
+    icon: '',
+    component: '',
+    componentName: '',
+    status: CommonStatusEnum.ENABLE,
+    visible: true,
+    keepAlive: true,
+    alwaysShow: true
+  }
+  formRef.value?.resetFields()
+}
+
+/** 判断 path 是不是外部的 HTTP 等链接 */
+const isExternal = (path: string) => {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+</script>
diff --git a/src/views/system/appmenu/index.vue b/src/views/system/appmenu/index.vue
new file mode 100644
index 0000000..66b4f35
--- /dev/null
+++ b/src/views/system/appmenu/index.vue
@@ -0,0 +1,203 @@
+<template>
+  <!-- 搜索工作栏 -->
+  <ContentWrap>
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="68px"
+    >
+      <el-form-item label="菜单名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          class="!w-240px"
+          clearable
+          placeholder="请输入菜单名称"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          class="!w-240px"
+          clearable
+          placeholder="请选择菜单状态"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          重置
+        </el-button>
+        <el-button plain type="danger" @click="toggleExpandAll">
+          <Icon class="mr-5px" icon="ep:sort" />
+          展开/折叠
+        </el-button>
+        <el-button plain @click="refreshMenu">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          刷新菜单缓存
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="list"
+      :default-expand-all="isExpandAll"
+      row-key="id"
+    >
+      <el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
+      <el-table-column align="center" label="图标" prop="icon" width="100">
+        <template #default="scope">
+          <Icon :icon="scope.row.icon" />
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" prop="sort" width="60" />
+      <el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
+      <el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
+      <el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
+      <el-table-column label="状态" prop="status" width="80">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="操作">
+        <template #default="scope">
+          <el-button
+            v-hasPermi="['system:app-menu:update']"
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+          >
+            修改
+          </el-button>
+          <el-button
+            v-hasPermi="['system:app-menu:create']"
+            link
+            type="primary"
+            @click="openForm('create', undefined, scope.row.id)"
+          >
+            新增
+          </el-button>
+          <el-button
+            v-hasPermi="['system:app-menu:delete']"
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <AppMenuForm ref="formRef" @success="getList" />
+</template>
+<script lang="ts" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { handleTree } from '@/utils/tree'
+import * as MenuApi from '@/api/system/menu'
+import AppMenuForm from './AppMenuForm.vue'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+
+defineOptions({ name: 'SystemAppMenu' })
+
+const { wsCache } = useCache()
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const loading = ref(true) // 列表的加载中
+const list = ref<any>([]) // 列表的数据
+const queryParams = reactive({
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const isExpandAll = ref(false) // 是否展开,默认全部折叠
+const refreshTable = ref(true) // 重新渲染表格状态
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await MenuApi.getAppMenuList(queryParams)
+    list.value = handleTree(data)
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number, parentId?: number) => {
+  formRef.value.open(type, id, parentId)
+}
+
+/** 展开/折叠操作 */
+const toggleExpandAll = () => {
+  refreshTable.value = false
+  isExpandAll.value = !isExpandAll.value
+  nextTick(() => {
+    refreshTable.value = true
+  })
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await MenuApi.deleteAppMenu(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 刷新菜单缓存按钮操作 */
+const refreshMenu = async () => {
+  try {
+    await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
+    // 清空,从而触发刷新
+    wsCache.delete(CACHE_KEY.USER)
+    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    // 刷新浏览器
+    location.reload()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

--
Gitblit v1.9.3