暂无可配置列
@@ -37,23 +59,29 @@
diff --git a/client/src/components/elderListDialog/index.vue b/client/src/components/elderListDialog/index.vue
index d5a8ee0..172f22c 100644
--- a/client/src/components/elderListDialog/index.vue
+++ b/client/src/components/elderListDialog/index.vue
@@ -1,26 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
- 选择
+
+
+
+
+
+ 选择
@@ -30,27 +51,36 @@
diff --git a/client/src/layout/components/NavBar/index.vue b/client/src/layout/components/NavBar/index.vue
index fdccd75..4e8f846 100644
--- a/client/src/layout/components/NavBar/index.vue
+++ b/client/src/layout/components/NavBar/index.vue
@@ -1,32 +1,50 @@
+
diff --git a/client/src/layout/components/SideBar/components/MenuItem.vue b/client/src/layout/components/SideBar/components/MenuItem.vue
index 3b2b0d4..e1a2de2 100644
--- a/client/src/layout/components/SideBar/components/MenuItem.vue
+++ b/client/src/layout/components/SideBar/components/MenuItem.vue
@@ -1,15 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{
- menu.meta.title
- }}
+ menu.meta.title
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{
- menu.meta.title
- }}
+ menu.meta.title
+ }}
@@ -52,26 +84,35 @@
diff --git a/client/src/layout/components/SideBar/index.vue b/client/src/layout/components/SideBar/index.vue
index 70cccae..547e1aa 100644
--- a/client/src/layout/components/SideBar/index.vue
+++ b/client/src/layout/components/SideBar/index.vue
@@ -1,12 +1,25 @@
+
diff --git a/client/src/layout/components/Tags/index.vue b/client/src/layout/components/Tags/index.vue
index 97c40c2..4ac0bb9 100644
--- a/client/src/layout/components/Tags/index.vue
+++ b/client/src/layout/components/Tags/index.vue
@@ -1,17 +1,30 @@
+
+
tags
diff --git a/client/src/layout/index.vue b/client/src/layout/index.vue
index 6debadf..b8f9bd5 100644
--- a/client/src/layout/index.vue
+++ b/client/src/layout/index.vue
@@ -1,19 +1,32 @@
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -24,10 +37,14 @@
diff --git a/client/src/mock/getters.ts b/client/src/mock/getters.ts
index 256c799..bbf9ef5 100644
--- a/client/src/mock/getters.ts
+++ b/client/src/mock/getters.ts
@@ -1,15 +1,23 @@
+// 从当前目录的 index 文件中导入 routes、users、IRoute 和 IUser
import { routes, users, IRoute, IUser } from './index'
+// 导入项目的 Vuex 存储实例
import store from '@/store'
-// 获取路由列表
+// 获取路由列表的函数,接受一个用户 ID(uid)作为参数
export const getRouterList = (uid: number) => {
+ // 从 Vuex 存储中获取当前用户的权限 ID 列表
const authIdList: number[] = store.state.app.userPeofile.authIdList
+ // 过滤出当前用户有权限访问的路由列表
const routeList: any[] = routes.filter(route => authIdList.includes(route.id))
+
+ // 如果传入了用户 ID(uid)
if (uid) {
- // userInfo 有可能是 undefined
+ // 查找具有指定 ID 的用户信息,userInfo 有可能是 undefined
const userInfo: IUser | undefined = users.find(user => user.id === uid)
+ // 如果找到了用户信息
if (userInfo) {
+ // 以下是一段被注释掉的代码,功能可能是进一步筛选用户的授权路由列表
// const authRouteList: IRoute[] = []
// rid = router id
// userInfo.auth.map(rid => {
@@ -19,12 +27,14 @@ export const getRouterList = (uid: number) => {
// }
// })
// })
+ // 返回成功响应,包含状态码、消息和筛选后的路由列表
return {
code: 0,
msg: 'ok',
data: routeList
}
} else {
+ // 如果没有找到用户信息,返回错误响应
return {
code: 1001,
msg: 'No userInfo for this UID',
@@ -32,6 +42,7 @@ export const getRouterList = (uid: number) => {
}
}
} else {
+ // 如果没有传入用户 ID,返回错误响应
return {
code: 1002,
msg: 'No UID received',
diff --git a/client/src/mock/index.ts b/client/src/mock/index.ts
index 8bed4b6..4a645d7 100644
--- a/client/src/mock/index.ts
+++ b/client/src/mock/index.ts
@@ -1,5 +1,9 @@
+// 从当前目录下的 routes 文件中导入 routes 变量和 IRoute 类型
import routes, { IRoute } from './routes'
+// 从当前目录下的 users 文件中导入 users 变量和 IUser 类型
import users, { IUser } from './users'
+// 导出 routes 变量,这样在其他文件中可以导入使用该变量
export { routes, users }
+// 导出 IRoute 和 IUser 类型,方便其他文件导入使用这些类型定义
export type { IRoute, IUser }
diff --git a/client/src/mock/routes.ts b/client/src/mock/routes.ts
index a46c70a..e547280 100644
--- a/client/src/mock/routes.ts
+++ b/client/src/mock/routes.ts
@@ -1,17 +1,29 @@
+// 定义一个接口 IRoute,用于描述路由的结构
export interface IRoute {
+ // 路由的唯一标识 ID,类型为数字
id: number
+ // 父路由的 ID,类型为数字
pid: number
+ // 路由的名称,类型为字符串
name: string
+ // 路由的路径,类型为字符串
path: string
+ // 重定向的路径,是一个可选属性,类型为字符串
redirect?: string
+ // 路由对应的组件路径,类型为字符串
component: string
+ // 路由的元数据,是一个对象
meta: {
+ // 路由的标题,类型为字符串
title: string
+ // 路由的图标名称,是一个可选属性,类型为字符串
icon?: string
}
}
+// 定义一个名为 routes 的数组,元素类型为 IRoute,存储路由信息
const routes: IRoute[] = [
+ // 第一个路由对象
{
id: 1,
pid: 0,
@@ -23,6 +35,7 @@ const routes: IRoute[] = [
icon: 'home'
}
},
+ // 第二个路由对象
{
id: 2,
pid: 0,
@@ -35,39 +48,43 @@ const routes: IRoute[] = [
icon: 'sale'
}
},
+ // 第三个路由对象
{
id: 3,
pid: 2,
path: 'counsel',
name: 'CounselSale',
- component: 'sale/counsel/index.vue',
+ component:'sale/counsel/index.vue',
meta: {
title: '咨询管理',
icon: ''
}
},
+ // 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 4,
// pid: 2,
// path: 'intention',
// name: 'IntentionSale',
- // component: 'sale/intention/index.vue',
+ // component:'sale/intention/index.vue',
// meta: {
// title: '意向客户',
// icon: ''
// }
// },
+ // 第五个路由对象
{
id: 5,
pid: 2,
path: 'book',
name: 'BookSale',
- component: 'sale/book/index.vue',
+ component:'sale/book/index.vue',
meta: {
title: '预定管理',
icon: ''
}
},
+ // 第六个路由对象
{
id: 6,
pid: 0,
@@ -80,6 +97,7 @@ const routes: IRoute[] = [
icon: 'live'
}
},
+ // 第七个路由对象
{
id: 7,
pid: 6,
@@ -91,6 +109,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第八个路由对象
{
id: 8,
pid: 6,
@@ -102,6 +121,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第九个路由对象
{
id: 9,
pid: 6,
@@ -113,6 +133,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第十个路由对象
{
id: 10,
pid: 6,
@@ -124,6 +145,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第十一个路由对象
{
id: 11,
pid: 6,
@@ -135,6 +157,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第十二个路由对象
{
id: 12,
pid: 6,
@@ -146,6 +169,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第十三个路由对象
{
id: 13,
pid: 0,
@@ -158,6 +182,7 @@ const routes: IRoute[] = [
icon: 'people'
}
},
+ // 第十四个路由对象
{
id: 14,
pid: 13,
@@ -169,10 +194,11 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第十五个路由对象
{
id: 15,
pid: 13,
- path: 'staff',
+ path:'staff',
name: 'StaffPeople',
component: 'people/staff/index.vue',
meta: {
@@ -180,6 +206,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 16,
// pid: 13,
@@ -191,61 +218,66 @@ const routes: IRoute[] = [
// icon: ''
// }
// },
+ // 第十七个路由对象
{
id: 17,
pid: 0,
path: '/serve',
name: 'Serve',
redirect: '/serve/project',
- component: 'serve/index.vue',
+ component:'serve/index.vue',
meta: {
title: '服务管理',
- icon: 'serve'
+ icon:'serve'
}
},
+ // 第十八个路由对象
{
id: 18,
pid: 17,
path: 'project',
name: 'ProjectServe',
- component: 'serve/project/index.vue',
+ component:'serve/project/index.vue',
meta: {
title: '服务项目',
icon: ''
}
},
+ // 第十九个路由对象
{
id: 19,
pid: 17,
path: 'level',
name: 'LevelServe',
- component: 'serve/level/index.vue',
+ component:'serve/level/index.vue',
meta: {
title: '护理等级',
icon: ''
}
},
+ // 第二十个路由对象
{
id: 20,
pid: 17,
path: 'book',
name: 'BookServe',
- component: 'serve/book/index.vue',
+ component:'serve/book/index.vue',
meta: {
title: '服务预定',
icon: ''
}
},
+ // 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 21,
// pid: 0,
// path: '/resource',
// name: 'Resource',
// redirect: '/resource/info',
- // component: 'resource/index.vue',
+ // component:'resource/index.vue',
// meta: {
// title: '物资管理',
- // icon: 'resource'
+ // icon:'resource'
// }
// },
// {
@@ -253,7 +285,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'info',
// name: 'InfoResource',
- // component: 'resource/info/index.vue',
+ // component:'resource/info/index.vue',
// meta: {
// title: '物资信息',
// icon: ''
@@ -264,7 +296,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'Storage',
// name: 'StorageResource',
- // component: 'resource/storage/index.vue',
+ // component:'resource/storage/index.vue',
// meta: {
// title: '仓库设置',
// icon: ''
@@ -275,7 +307,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'enter',
// name: 'EnterResource',
- // component: 'resource/enter/index.vue',
+ // component:'resource/enter/index.vue',
// meta: {
// title: '入库管理',
// icon: ''
@@ -286,7 +318,7 @@ const routes: IRoute[] = [
// pid: 21,
// path: 'leave',
// name: 'LeaveResource',
- // component: 'resource/leave/index.vue',
+ // component:'resource/leave/index.vue',
// meta: {
// title: '出库管理',
// icon: ''
@@ -295,14 +327,15 @@ const routes: IRoute[] = [
// {
// id: 26,
// pid: 21,
- // path: 'search',
+ // path:'search',
// name: 'SearchResource',
- // component: 'resource/search/index.vue',
+ // component:'resource/search/index.vue',
// meta: {
// title: '库存查询',
// icon: ''
// }
// },
+ // 第二十七个路由对象
{
id: 27,
pid: 0,
@@ -315,6 +348,7 @@ const routes: IRoute[] = [
icon: 'diet'
}
},
+ // 第二十八个路由对象
{
id: 28,
pid: 27,
@@ -326,6 +360,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第二十九个路由对象
{
id: 29,
pid: 27,
@@ -337,6 +372,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十个路由对象
{
id: 30,
pid: 27,
@@ -348,6 +384,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十一个路由对象
{
id: 31,
pid: 0,
@@ -360,6 +397,7 @@ const routes: IRoute[] = [
icon: 'charge'
}
},
+ // 第三十二个路由对象
{
id: 32,
pid: 31,
@@ -371,10 +409,11 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十三个路由对象
{
id: 33,
pid: 31,
- path: 'record',
+ path:'record',
name: 'RecordCharge',
component: 'charge/record/index.vue',
meta: {
@@ -382,6 +421,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十四个路由对象
{
id: 34,
pid: 31,
@@ -393,6 +433,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十五个路由对象
{
id: 35,
pid: 0,
@@ -405,10 +446,11 @@ const routes: IRoute[] = [
icon: 'base'
}
},
+ // 第三十六个路由对象
{
id: 36,
pid: 35,
- path: 'sale',
+ path:'sale',
name: 'SaleBase',
redirect: '/base/sale/origin',
component: 'base/sale/index.vue',
@@ -417,6 +459,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第三十七个路由对象
{
id: 37,
pid: 36,
@@ -428,6 +471,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 38,
// pid: 36,
@@ -439,6 +483,7 @@ const routes: IRoute[] = [
// icon: ''
// }
// },
+ // 第三十九个路由对象
{
id: 39,
pid: 35,
@@ -451,6 +496,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第四十个路由对象
{
id: 40,
pid: 39,
@@ -462,6 +508,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 第四十一个路由对象
{
id: 41,
pid: 39,
@@ -473,6 +520,7 @@ const routes: IRoute[] = [
icon: ''
}
},
+ // 以下是一些被注释掉的路由对象,可根据需求启用
// {
// id: 42,
// pid: 35,
@@ -483,4 +531,5 @@ const routes: IRoute[] = [
// }
]
+// 导出 routes 数组,使其可以在其他模块中被导入和使用
export default routes
diff --git a/client/src/mock/users.ts b/client/src/mock/users.ts
index db8e05e..3fb3645 100644
--- a/client/src/mock/users.ts
+++ b/client/src/mock/users.ts
@@ -1,13 +1,20 @@
+// 定义一个接口 IUser,用于描述用户对象的结构
export interface IUser {
+ // 用户的唯一标识 ID,类型为数字
id: number
+ // 用户的用户名,类型为字符串
username: string
+ // 用户拥有的权限 ID 数组,数组中的元素类型为数字
auth: number[]
}
+// 导出一个默认的用户数组,类型为 IUser 数组
+// 数组中包含一个用户对象
export default [
{
id: 1,
username: 'zhangsan',
+ // 该用户拥有的权限 ID 数组,包含多个权限 ID
auth: [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
diff --git a/client/src/plugins/element-plus/index.ts b/client/src/plugins/element-plus/index.ts
index b28a690..9901b73 100644
--- a/client/src/plugins/element-plus/index.ts
+++ b/client/src/plugins/element-plus/index.ts
@@ -1,11 +1,17 @@
+// 从 '@element-plus/icons-vue' 模块中导入所有的图标组件
import * as ElIcons from '@element-plus/icons-vue'
+// 从 'vue' 模块中导入 App 类型,用于表示 Vue 应用实例
import { App } from 'vue'
+// 定义一个名为 useElementPlus 的函数,该函数接受一个 App 类型的参数 app
const useElementPlus = (app: App) => {
- // 注册图标
+ // 遍历 ElIcons 对象的所有属性
for (const [key, component] of Object.entries(ElIcons)) {
+ // 使用 app.component 方法将每个图标组件注册为全局组件
+ // 组件的名称为属性名 key,组件本身为 component
app.component(key, component)
}
}
+// 导出 useElementPlus 函数,使其可以在其他模块中被导入和使用
export default useElementPlus
diff --git a/client/src/plugins/index.ts b/client/src/plugins/index.ts
index c6e49f2..09d5a2e 100644
--- a/client/src/plugins/index.ts
+++ b/client/src/plugins/index.ts
@@ -1,3 +1,6 @@
+// 从当前目录下的 element-plus 文件中导入名为 useElementPlus 的函数
import useElementPlus from './element-plus'
+// 重新导出 useElementPlus 函数,这样其他模块在导入这个文件时
+// 可以通过解构赋值等方式获取到 useElementPlus 函数
export { useElementPlus }
diff --git a/client/src/router/index.ts b/client/src/router/index.ts
index a63c0cf..44cf2a0 100644
--- a/client/src/router/index.ts
+++ b/client/src/router/index.ts
@@ -1,62 +1,87 @@
+// 从 vue-router 库中导入创建路由实例、路由记录类型和哈希路由历史模式的方法
import { createRouter, RouteRecordRaw, createWebHashHistory } from 'vue-router'
+// 导入布局组件
import Layout from '@/layout/index.vue'
+// 导入 Vuex 存储实例
import store from '@/store'
+// 导入初始化动态路由的工具函数
import { initRoutes } from './utils'
-// 静态路由
+// 定义静态路由数组,包含应用中固定的路由配置
const routes: Array = [
{
+ // 根路径
path: '/',
+ // 路由名称
name: 'Layout',
+ // 路由对应的组件
component: Layout,
+ // 重定向到 /home 路径
redirect: '/home',
+ // 子路由数组,初始为空
children: []
},
{
+ // 登录页面路径
path: '/login',
+ // 路由名称
name: 'Login',
+ // 路由元信息,包含页面标题
meta: {
title: '登录'
},
+ // 使用动态导入方式加载登录页面组件
component: () =>
import(/* webpackChunkName: "Login" */ '@/views/login/index.vue')
},
{
+ // 匹配所有未定义的路径
path: '/:pathMatch(.*)*',
+ // 路由名称
name: 'NotFound',
+ // 路由元信息,包含页面标题
meta: {
title: '404'
},
+ // 使用动态导入方式加载 404 错误页面组件
component: () =>
import(/* webpackChunkName: "404" */ '@/views/error/404.vue')
}
]
+// 创建路由实例
const router = createRouter({
+ // 使用哈希路由历史模式
history: createWebHashHistory(),
+ // 路由配置
routes
})
+// 定义路由白名单,包含无需验证即可访问的路径
const ROUTER_WHITE_LIST = ['/login']
+// 全局前置守卫,在每次路由跳转前执行
router.beforeEach(async (to, from, next) => {
- // 设置页面标题
- document["title"] = to.meta.title + " | 敬老院管理系统"
+ // 设置页面标题,将目标路由的元信息中的标题与固定后缀拼接
+ document.title = to.meta.title + ' | 敬老院管理系统'
- // .判断访问页面是否在路由白名单地址中,如果存在直接放行
+ // 判断访问的页面路径是否在路由白名单中,如果存在则直接放行
if (ROUTER_WHITE_LIST.includes(to.path)) return next()
- //.判断 是否有 Token,没有重定向到 login
+ // 判断是否有 Token,如果没有则重定向到登录页面
if (!store.state.app.token) return next({ path: '/login', replace: true })
- //如果没有初始化动态路由就初始化
+ // 如果没有初始化动态路由且存在 Token,则初始化动态路由
if (!store.state.app.hasAuth && store.state.app.token) {
+ // 调用初始化动态路由的工具函数
await initRoutes()
+ // 重新跳转至目标路径
return next({ path: to.path })
}
- // 7.正常访问页面
+ // 正常访问页面,放行路由跳转
next()
})
+// 导出路由实例,供应用使用
export default router
diff --git a/client/src/router/types.ts b/client/src/router/types.ts
index d90861b..5c4a69f 100644
--- a/client/src/router/types.ts
+++ b/client/src/router/types.ts
@@ -1,14 +1,25 @@
/** 路由 */
+// 定义一个接口 IRoute,用于规范路由对象的结构
export interface IRoute {
+ // 路由的唯一标识 ID,类型为数字
id: number
+ // 父路由的 ID,类型为数字,用于表示路由的层级关系
pid: number
+ // 路由的名称,在路由配置和导航中使用,类型为字符串
name: string
+ // 路由的路径,用于在浏览器地址栏中显示和导航,类型为字符串
path: string
+ // 重定向的路径,是一个可选属性,当访问该路由时会自动重定向到指定路径,类型为字符串
redirect?: string
+ // 路由对应的组件路径,指定该路由应渲染的 Vue 组件的路径,类型为字符串
component: string
+ // 路由的元数据,是一个对象,包含与路由相关的额外信息
meta: {
+ // 路由的标题,可用于页面标题、菜单显示等,类型为字符串
title: string
+ // 路由的图标名称,是一个可选属性,可用于在菜单中显示图标,类型为字符串
icon?: string
}
+ // 子路由数组,是一个可选属性,用于表示该路由下的子路由列表,元素类型为 IRoute
children?: IRoute[]
}
diff --git a/client/src/router/utils.ts b/client/src/router/utils.ts
index ec5fc53..4153b9a 100644
--- a/client/src/router/utils.ts
+++ b/client/src/router/utils.ts
@@ -1,31 +1,53 @@
+// 从项目的路由类型文件中导入 IRoute 接口,用于描述路由的结构
import { IRoute } from "@/router/types";
+// 从 vue-router 库中导入 Router 和 RouteRecordRaw 类型
+// Router 是 Vue Router 的实例类型,RouteRecordRaw 用于定义路由记录
import { Router, RouteRecordRaw } from "vue-router";
+// 导入项目的路由实例
import router from "./index";
+// 导入项目的 Vuex 存储实例
import store from "@/store";
+// 从项目的工具函数文件中导入数组去重的工具函数
import { arrayDeduplicationByFiled } from "@/utils/commonUtil";
/** 将后端返回的路由转换成生成树形结构 */
function formatRouteTree(data: IRoute[]) {
+ // 过滤出父路由,即 pid 为 0 的路由
const parents = data.filter(routeInfo => routeInfo.pid === 0);
+ // 过滤出子路由,即 pid 不为 0 的路由
const children = data.filter(routeInfo => routeInfo.pid !== 0);
+ // 调用递归函数将数据转换为树形结构
dataToTree(parents, children);
+ // 返回转换后的父路由数组,此时父路由数组中包含了子路由信息
return parents;
+ /**
+ * 递归函数,将子路由挂载到对应的父路由上
+ * @param parents 父路由数组
+ * @param children 子路由数组
+ */
function dataToTree(parents: IRoute[], children: IRoute[]) {
+ // 遍历父路由数组
parents.map(parent => {
+ // 遍历子路由数组
children.map((child, index) => {
+ // 如果子路由的 pid 等于父路由的 id
if (child.pid === parent.id) {
+ // 复制一份子路由数组,避免在原数组上进行操作
const _children: IRoute[] = JSON.parse(JSON.stringify(children));
+ // 从复制的子路由数组中移除当前处理的子路由
_children.splice(index, 1);
+ // 递归调用 dataToTree 函数,处理当前子路由的子路由
dataToTree([child], _children);
if (parent.children) {
- // 添加
+ // 如果父路由已经有子路由数组,将当前子路由添加到该数组中
parent.children.push(child);
- // 菜单去重
+ // 对父路由的子路由数组进行去重操作,根据 id 字段去重
parent.children = arrayDeduplicationByFiled(parent.children, "id");
} else {
+ // 如果父路由还没有子路由数组,创建一个包含当前子路由的数组
parent.children = [child];
}
}
@@ -36,46 +58,66 @@ function formatRouteTree(data: IRoute[]) {
/** 将树形结构路由转化成真实的路由 */
function generateRouter(routeTree: IRoute[]) {
+ // 遍历树形结构的路由数组
const newRoutes = routeTree.map(route => {
+ // 创建一个新的路由记录对象
const _route: RouteRecordRaw = {
+ // 路由的路径
path: route.path,
+ // 路由的名称
name: route.name,
+ // 路由的元信息
meta: route.meta,
+ // 路由的重定向路径
redirect: route.redirect,
- component: () => import("@/views/" + route.component), // 注意:views目录下才能引入,否则ts不识别
+ // 使用动态导入的方式加载路由对应的组件,组件位于 @/views 目录下
+ component: () => import("@/views/" + route.component),
+ // 子路由数组,初始为空
children: []
};
+ // 如果当前路由有子路由
if (route.children) {
+ // 递归调用 generateRouter 函数,处理子路由
_route.children = generateRouter(route.children);
}
+ // 返回新创建的路由记录对象
return _route;
});
+ // 返回转换后的路由记录数组
return newRoutes;
}
-//初始化动态路由
+// 初始化动态路由的函数
export async function initRoutes() {
+ // 调用 Vuex 的 action,获取路由树形结构数据
await store.dispatch("app/getRouterTree");
+ // 将获取到的路由树形结构数据转换为真实的路由记录数组
const newRoutes = generateRouter(store.state.app.routeTree);
+ // 遍历新的路由记录数组,将每个路由添加到名为 "Layout" 的父路由下
newRoutes.forEach(route => router.addRoute("Layout", route));
}
-//清除路由
+// 清除路由的函数
export function clearRoutes() {
- //删除之前注册的路由 默认状态下只有三个,如果超过三个删除多余的路由
+ // 定义默认的路由名称数组
const defaultRoutes = ["Layout", "Login", "NotFound"];
+ // 获取当前路由实例中的所有路由记录
const currentRoutes = router.getRoutes();
+ // 如果当前路由记录的数量不等于 3
if (currentRoutes.length != 3) {
- //执行删除
+ // 遍历当前的路由记录数组
currentRoutes.forEach(item => {
+ // 如果当前路由的名称不在默认路由名称数组中
if (!defaultRoutes.includes(String(item.name))) {
+ // 从路由实例中移除该路由
router.removeRoute(String(item.name));
}
});
}
}
+// 导出 formatRouteTree 和 generateRouter 函数,供其他模块使用
export { formatRouteTree, generateRouter };
diff --git a/client/src/store/index.ts b/client/src/store/index.ts
index 79a8523..10041a7 100644
--- a/client/src/store/index.ts
+++ b/client/src/store/index.ts
@@ -1,29 +1,46 @@
+// 从 vue 库中导入 InjectionKey 类型,用于在 Vue 3 中为 provide 和 inject 提供类型约束
import { InjectionKey } from 'vue'
+// 从 vuex 库中导入创建 store 的函数、基础的 useStore 函数和 Store 类型
import { createStore, useStore as baseUseStore, Store } from 'vuex'
+// 从项目的 store 类型文件中导入根状态类型
import { IRootState } from '@/store/types'
+// 从当前模块的 app 子模块中导入名为 store 的对象
import { store as app } from './modules/app'
+// 从当前模块的工具文件中导入自定义的 CommonStore 类型
import { CommonStore } from './utils'
+// 导入 vuex-persistedstate 插件,用于将 store 中的状态持久化到本地存储
import createPersistedState from 'vuex-persistedstate'
+// 定义一个包含所有模块的对象,这里目前只有 app 模块
export const modules = {
app
}
-// injectionKey:约束state类型
+// 定义一个注入键,用于在使用 useStore 时约束 state 的类型
+// 这里使用 Symbol 创建一个唯一的键,类型为 Store
export const key: InjectionKey> = Symbol()
+// 创建一个 Vuex store 实例,指定根状态类型为 IRootState
const store = createStore({
+ // 注册模块
modules,
+ // 应用插件
plugins: [
+ // 使用 vuex-persistedstate 插件
createPersistedState({
+ // 存储在本地存储中的键名
key: 'stateData',
+ // 指定需要持久化的模块路径,这里只持久化 app 模块
paths: ['app']
})
]
}) as CommonStore
+// 定义一个自定义的 useStore 函数,返回类型为 CommonStore
+// 调用 baseUseStore 函数并传入注入键,确保类型安全
export function useStore(): CommonStore {
return baseUseStore(key)
}
+// 导出创建好的 store 实例,供应用使用
export default store
diff --git a/client/src/store/modules/app/index.ts b/client/src/store/modules/app/index.ts
index 7ebbf04..31b30a1 100644
--- a/client/src/store/modules/app/index.ts
+++ b/client/src/store/modules/app/index.ts
@@ -1,26 +1,46 @@
+// 从 vuex 中导入 ActionContext 类型,用于在 action 中获取上下文
import { ActionContext } from 'vuex'
+// 从项目的 store 类型文件中导入根状态类型
import { IRootState } from '@/store/types'
+// 从当前模块的类型文件中导入当前模块的状态类型
import { IAppState } from './types'
+// 从项目的路由类型文件中导入路由类型
import { IRoute } from '@/router/types'
+// 从项目的模拟数据获取器中导入获取路由列表的函数
import { getRouterList } from '@/mock/getters'
+// 从项目的路由工具文件中导入清除路由和格式化路由树的函数
import { clearRoutes, formatRouteTree } from '@/router/utils'
+// 从项目的 API 模块中导入获取用户路由列表的函数(当前注释掉未使用)
// import { getUserRouteList } from '@/apis'
+// 从项目的用户 API 模块中导入登录请求函数
import { getLogin } from '@/apis/user'
+// 导入项目的路由实例
import router from '@/router'
+// 定义默认状态对象
const defaultState = {
+ // 用户 ID,初始为 1
uid: 1,
+ // 用户令牌,初始为空字符串
token: '',
+ // 是否有路由权限,初始为 false
hasAuth: false,
+ // 路由树,初始为空数组
routeTree: [],
+ // 侧边栏是否展开,初始为 true 表示展开
siderType: true,
+ // 用户信息,初始为空对象
userPeofile: {},
+ // 是否记住密码,初始为 false
rememberPWD: false
}
+// 导出当前模块的 store 配置
export const store = {
+ // 开启命名空间,使模块的 action、mutation 和 getter 名称唯一
namespaced: true,
+ // 模块的状态
state: {
uid: 1,
token: '',
@@ -30,38 +50,53 @@ export const store = {
userPeofile: {},
rememberPWD: false
},
+ // 模块的 mutations,用于修改状态
mutations: {
+ // 设置用户令牌的 mutation
setToken(state: IAppState, token: string) {
state.token = token
},
+ // 设置路由树的 mutation
setRouteTree(state: IAppState, routeTree: IRoute[]) {
state.routeTree = routeTree
},
+ // 设置是否有路由权限的 mutation
setAuth(state: IAppState, auth: boolean) {
state.hasAuth = auth
},
+ // 切换侧边栏展开状态的 mutation
setSiderType(state: IAppState) {
state.siderType = !state.siderType
},
+ // 设置用户信息的 mutation
setUserProfile(state: IAppState, userPeofile: any) {
state.userPeofile = userPeofile
},
+ // 设置是否记住密码的 mutation
setRememberPWD(state: IAppState, rememberPWD: boolean) {
state.rememberPWD = rememberPWD
},
+ // 清除 store 状态的 mutation,将状态重置为默认状态
clearStore(state: IAppState) {
Object.assign(state, defaultState)
}
},
+ // 模块的 actions,用于处理异步操作和提交 mutations
actions: {
- //登录
+ // 登录 action
async actionLogin(
+ // 从上下文获取 commit 和 state
{ commit, state }: ActionContext,
+ // 登录数据
data: any
) {
+ // 调用登录请求函数
const res: any = await getLogin(data)
+ // 如果登录成功
if (res.code === 200) {
+ // 提交设置令牌的 mutation
commit('setToken', res?.data?.token)
+ // 提交设置用户信息的 mutation
commit('setUserProfile', {
username: res?.data.name,
userid: res?.data.id,
@@ -69,22 +104,31 @@ export const store = {
authIdList: res?.data.authIdList
})
}
+ // 提交设置是否记住密码的 mutation
commit('setRememberPWD', data?.rememberPWD)
+ // 返回登录请求的响应
return res
},
- //获取权限菜单
+ // 获取权限菜单 action
getRouterTree({ commit, state }: ActionContext) {
- // 模拟数据
+ // 模拟获取路由列表
const routeList = getRouterList(state.uid).data as unknown as IRoute[]
+ // 格式化路由列表为路由树
const routeTree = formatRouteTree(routeList)
+ // 提交设置路由树的 mutation
commit('setRouteTree', routeTree)
+ // 提交设置有路由权限的 mutation
commit('setAuth', true)
},
- // 点击登出
+ // 点击登出 action
logout({ commit, state }: ActionContext) {
+ // 提交清除 store 状态的 mutation
commit('clearStore')
+ // 清除本地存储
localStorage.clear()
+ // 清除动态添加的路由
clearRoutes()
+ // 路由跳转到登录页
router.replace('/login')
}
}
diff --git a/client/src/store/modules/app/types.ts b/client/src/store/modules/app/types.ts
index b064c07..6ac80e7 100644
--- a/client/src/store/modules/app/types.ts
+++ b/client/src/store/modules/app/types.ts
@@ -1,11 +1,20 @@
+// 从项目的路由类型文件中导入 IRoute 接口,用于描述路由的结构
import { IRoute } from '@/router/types'
+// 定义一个接口 IAppState,用于描述应用状态的结构
export interface IAppState {
+ // 用户 ID,类型为数字
uid: number
+ // 用户令牌,类型为字符串
token: string
+ // 是否有路由权限,类型为布尔值
hasAuth: boolean
+ // 路由树,是一个 IRoute 类型的数组,用于存储路由信息
routeTree: IRoute[]
+ // 侧边栏是否展开,类型为布尔值
siderType: boolean
+ // 用户信息,类型为 any,表示可以是任意类型的数据
userPeofile: any
+ // 是否记住密码,类型为布尔值
rememberPWD: boolean
}
diff --git a/client/src/store/modules/soldManage/index.ts b/client/src/store/modules/soldManage/index.ts
index fb5688c..5417709 100644
--- a/client/src/store/modules/soldManage/index.ts
+++ b/client/src/store/modules/soldManage/index.ts
@@ -1,9 +1,16 @@
+// 从 vuex 中导入 ActionContext 类型,它用于在 actions 里获取上下文,能访问 state、getters、mutations 等
import { ActionContext } from 'vuex'
+// 从项目的路由配置文件导入路由实例,后续可能会在 actions 里使用路由进行页面跳转
import router from '@/router'
+// 导出一个名为 store 的对象,这是一个 Vuex 模块
export const store = {
+ // 设置为 true 表示开启命名空间,可避免不同模块间的 action、mutation 和 getter 名称冲突
namespaced: true,
+ // state 是存储数据的地方,这里初始为空对象,后续可添加应用所需的状态数据
state: {},
+ // mutations 用于修改 state 中的数据,是同步操作,这里初始为空对象,后续可添加具体的 mutation 函数
mutations: {},
+ // actions 用于处理异步操作,如发起网络请求等,这里初始为空对象,后续可添加具体的 action 函数
actions: {}
}
diff --git a/client/src/store/types.ts b/client/src/store/types.ts
index 28f5937..335ec26 100644
--- a/client/src/store/types.ts
+++ b/client/src/store/types.ts
@@ -1,5 +1,11 @@
+// 从项目的 app 模块的类型文件中导入 IAppState 类型,该类型描述了 app 模块的状态结构
import { IAppState } from '@/store/modules/app/types'
+/**
+ * 定义一个名为 IRootState 的类型,它表示整个 Vuex 存储的根状态结构。
+ * 在 Vuex 中,根状态可以包含多个模块的状态,这里根状态只包含一个名为 app 的模块状态。
+ */
export type IRootState = {
+ // app 字段的类型为 IAppState,意味着根状态中的 app 模块状态遵循 IAppState 类型的定义
app: IAppState
}
diff --git a/client/src/store/utils.ts b/client/src/store/utils.ts
index f96b603..509eab0 100644
--- a/client/src/store/utils.ts
+++ b/client/src/store/utils.ts
@@ -1,25 +1,29 @@
/** 智能提示 */
+// 从当前目录的 types 文件中导入 IRootState 类型,用于表示根状态
import { IRootState } from './types'
+// 从当前目录的 index 文件中导入 modules 对象,该对象包含了所有的 Vuex 模块
import { modules } from './index'
+// 从 vuex 库中导入 CommitOptions、DispatchOptions 和 Store 类型
import { CommitOptions, DispatchOptions, Store as VuexStore } from 'vuex'
-// 获取modules的类型
+
+// 获取 modules 的类型
type Modules = typeof modules
-// 获取所有模块下的mutations
-type GetMutation = T extends { mutations: infer G } ? G : never
+// 获取所有模块下的 mutations
+type GetMutation = T extends { mutations: infer G }? G : never
type GetMutations = {
[K in keyof T]: GetMutation
}
type mutationsObj = GetMutations
-// 获取所有模块下的actions
-type GetAction = T extends { actions: infer G } ? G : never
+// 获取所有模块下的 actions
+type GetAction = T extends { actions: infer G }? G : never
type GetActions = {
[K in keyof T]: GetAction
}
type actionsObj = GetActions
-// 获取所有模块下的getters
-type GetGetter = T extends { getters: infer G } ? G : never
+// 获取所有模块下的 getters
+type GetGetter = T extends { getters: infer G }? G : never
type GetGetters = {
[K in keyof T]: GetGetter
}
diff --git a/client/src/styles/element.scss b/client/src/styles/element.scss
index 81e2fba..d1e83cf 100644
--- a/client/src/styles/element.scss
+++ b/client/src/styles/element.scss
@@ -1,22 +1,27 @@
+/* 为.el-breadcrumb__inner 和.el-breadcrumb__inner a 元素设置字体加粗为 400,!important 提高优先级 */
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
+/* 隐藏.el-upload 元素内的 input[type='file'] 元素,!important 提高优先级 */
.el-upload {
input[type='file'] {
display: none !important;
}
}
+/* 隐藏.el-upload__input 元素 */
.el-upload__input {
display: none;
}
+/* 为.upload-container 元素内的.el-upload 元素设置宽度为 100% */
.upload-container {
.el-upload {
width: 100%;
+ /* 为.upload-container 元素内的.el-upload 元素的.el-upload-dragger 子元素设置宽度为 100%,高度为 200px */
.el-upload-dragger {
width: 100%;
height: 200px;
@@ -24,62 +29,70 @@
}
}
+/* 为.el-dropdown-menu 元素设置内边距为 0,!important 提高优先级 */
.el-dropdown-menu {
padding: 0 !important;
}
+/* 为.el-range-separator 元素设置盒模型为 content-box */
.el-range-separator {
box-sizing: content-box;
}
+/* 为.is-dark 元素设置 z-index 为 9999,!important 提高优先级 */
.is-dark {
z-index: 9999 !important;
}
-/* 重置 el-button 中 icon 的 margin */
+/* 重置带有 [class*='el-icon'] 类且后面跟着 span 元素的 margin-left 为 2px,!important 提高优先级 */
.reset-margin [class*='el-icon'] + span {
margin-left: 2px !important;
}
-/* 自定义 popover 的类名 */
+/* 为.pure-popper 元素设置内边距为 0,!important 提高优先级 */
.pure-popper {
padding: 0 !important;
}
-/* 自定义 tooltip 的类名 */
+/* 为.pure-tooltip 元素设置 z-index 为 41000,!important 提高优先级,因为右侧操作面板 right-panel 类名的 z-index 为 40000,tooltip 需要大于它才能显示 */
.pure-tooltip {
- // 右侧操作面板right-panel类名的z-index为40000,tooltip需要大于它才能显示
z-index: 41000 !important;
}
-/* nprogress 适配 element-plus 的主题色 */
+/* 为 #nprogress 元素及其子元素设置样式 */
#nprogress {
- & .bar {
+ /* 为 #nprogress 元素的.bar 子元素设置背景颜色为 --el-color-primary 变量的值,!important 提高优先级 */
+ &.bar {
background-color: var(--el-color-primary) !important;
}
- & .peg {
+ /* 为 #nprogress 元素的.peg 子元素设置 box-shadow,颜色为 --el-color-primary 变量的值,!important 提高优先级 */
+ &.peg {
box-shadow: 0 0 10px var(--el-color-primary),
0 0 5px var(--el-color-primary) !important;
}
- & .spinner-icon {
+ /* 为 #nprogress 元素的.spinner-icon 子元素设置边框颜色为 --el-color-primary 变量的值 */
+ &.spinner-icon {
border-top-color: var(--el-color-primary);
border-left-color: var(--el-color-primary);
}
}
-/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式,表现更鲜明 */
+/* 为.el-dialog__headerbtn 和.el-message-box__headerbtn 元素的:hover 状态设置样式 */
.el-dialog__headerbtn,
.el-message-box__headerbtn {
&:hover {
+ /* 为.el-dialog__headerbtn 和.el-message-box__headerbtn 元素在:hover 状态下的.el-dialog__close 子元素设置颜色为 --el-color-info 变量的值,!important 提高优先级 */
.el-dialog__close {
color: var(--el-color-info) !important;
}
}
}
+/* 为.el-icon 元素及其特定子元素设置样式 */
.el-icon {
+ /* 为.el-icon 元素的.el-dialog__close、.el-drawer__close、.el-message-box__close 和.el-notification__closeBtn 子元素设置样式 */
&.el-dialog__close,
&.el-drawer__close,
&.el-message-box__close,
@@ -90,6 +103,7 @@
border-radius: 4px;
transition: background-color 0.2s, color 0.2s;
+ /* 为这些子元素的:hover 状态设置样式 */
&:hover {
color: rgba(0, 0, 0, 0.88) !important;
background-color: rgba(0, 0, 0, 0.06);
@@ -98,7 +112,7 @@
}
}
-/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
+/* 克隆并自定义 ElMessage 样式,为.pure-message 元素设置样式 */
.pure-message {
border-width: 0 !important;
background: #fff !important;
@@ -106,69 +120,85 @@
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014,
0 9px 28px 8px #0000000d !important;
- &.el-message.is-closable .el-message__content {
+ /* 为.pure-message 元素的.is-closable 子元素的.el-message__content 子元素设置内边距 */
+ &.el-message.is-closable.el-message__content {
padding-right: 17px !important;
}
- & .el-message__content {
+ /* 为.pure-message 元素的.el-message__content 子元素设置颜色、指针事件和背景图像 */
+ &.el-message__content {
color: #000000d9 !important;
pointer-events: all !important;
background-image: initial !important;
}
- & .el-message__icon {
+ /* 为.pure-message 元素的.el-message__icon 子元素设置外边距 */
+ &.el-message__icon {
margin-right: 8px !important;
}
- & .el-message__closeBtn {
+ /* 为.pure-message 元素的.el-message__closeBtn 子元素设置样式 */
+ &.el-message__closeBtn {
outline: none;
border-radius: 4px;
right: 9px !important;
transition: background-color 0.2s, color 0.2s;
+ /* 为.el-message__closeBtn 子元素的:hover 状态设置背景颜色 */
&:hover {
background-color: rgba(0, 0, 0, 0.06);
}
}
}
+/* 为.el-popconfirm 元素的.el-popconfirm__action 子元素内的不同类型的.el-button 元素设置样式 */
.el-popconfirm {
.el-popconfirm__action {
+ /* 为.el-popconfirm__action 子元素内的.el-button--primary 类型的按钮设置背景颜色 */
.el-button--primary {
background-color: #409eff !important;
+ /* 为.el-button--primary 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #79bbff !important;
}
}
+ /* 为.el-popconfirm__action 子元素内的.el-button--success 类型的按钮设置背景颜色 */
.el-button--success {
background-color: #67C23A !important;
+ /* 为.el-button--success 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #95d475 !important;
}
}
+ /* 为.el-popconfirm__action 子元素内的.el-button--warning 类型的按钮设置背景颜色 */
.el-button--warning {
background-color: #E6A23C !important;
+ /* 为.el-button--warning 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #eebe77 !important;
}
}
+ /* 为.el-popconfirm__action 子元素内的.el-button--danger 类型的按钮设置背景颜色 */
.el-button--danger {
- background-color: #f56c6c !important;
+ background-color: #F56C6C !important;
+ /* 为.el-button--danger 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #f89898 !important;
}
}
+ /* 为.el-popconfirm__action 子元素内的.el-button--info 类型的按钮设置背景颜色 */
.el-button--info {
background-color: #909399 !important;
+ /* 为.el-button--info 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #b1b3b8 !important;
}
@@ -176,46 +206,56 @@
}
}
+/* 为.el-button--primary 类型的按钮设置颜色和背景颜色 */
.el-button--primary {
color: white !important;
background-color: #409EFF !important;
+ /* 为.el-button--primary 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #79bbff !important;
}
}
+/* 为.el-button--success 类型的按钮设置颜色和背景颜色 */
.el-button--success {
color: white !important;
background-color: #67C23A !important;
+ /* 为.el-button--success 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #95d475 !important;
}
}
+/* 为.el-button--warning 类型的按钮设置颜色和背景颜色 */
.el-button--warning {
color: white !important;
background-color: #E6A23C !important;
+ /* 为.el-button--warning 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #eebe77 !important;
}
}
+/* 为.el-button--danger 类型的按钮设置颜色和背景颜色 */
.el-button--danger {
color: white !important;
background-color: #F56C6C !important;
+ /* 为.el-button--danger 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #f89898 !important;
}
}
+/* 为.el-button--info 类型的按钮设置颜色和背景颜色 */
.el-button--info {
color: white !important;
background-color: #909399 !important;
+ /* 为.el-button--info 类型的按钮的:hover 状态设置背景颜色 */
&:hover {
background-color: #b1b3b8 !important;
}
diff --git a/client/src/styles/index.scss b/client/src/styles/index.scss
index ca69fc8..eb00703 100644
--- a/client/src/styles/index.scss
+++ b/client/src/styles/index.scss
@@ -1,9 +1,26 @@
-@import './variables.module.scss'; // 常用变量
-@import './mixin.scss'; // 常用方法
-@import './base.scss'; // 基础样式
-@import './reset.scss'; // 重置样式
+// 导入常用变量文件,该文件可能包含颜色、字体大小、间距等常用变量的定义
+@import './variables.module.scss';
+
+// 导入常用方法文件,该文件可能包含一些自定义的混合宏(mixin)或函数,用于简化样式编写
+@import './mixin.scss';
+
+// 导入基础样式文件,该文件可能包含一些全局的基础样式,如字体、文本对齐、行高等
+@import './base.scss';
+
+// 导入重置样式文件,该文件通常用于重置浏览器的默认样式,使不同浏览器的样式更加一致
+@import './reset.scss';
+
+// 导入登录页面的样式文件,该文件可能包含登录页面特有的样式
@import './login.scss';
+
+// 导入导航栏的样式文件,该文件可能包含导航栏的样式
@import './navbar.scss';
+
+// 导入侧边栏的样式文件,该文件可能包含侧边栏的样式
@import './sidebar.scss';
+
+// 导入 Element Plus 组件库的样式文件,该文件可能包含对 Element Plus 组件的样式覆盖或自定义
@import './element.scss';
+
+// 导入过渡效果的样式文件,该文件可能包含一些过渡动画的样式定义
@import './transition.scss';
diff --git a/client/src/styles/login.scss b/client/src/styles/login.scss
index 83c9408..4bd7cca 100644
--- a/client/src/styles/login.scss
+++ b/client/src/styles/login.scss
@@ -1,70 +1,116 @@
+// 定义一个名为.login-container 的类选择器,用于设置登录容器的样式
.login-container {
+ // 设置定位为相对定位
position: relative;
+ // 设置高度为视口高度的 100%
height: 100vh;
+ // 设置宽度为视口宽度的 100%
width: 100vw;
+ // 设置为弹性盒子布局
display: flex;
+ // 使弹性盒子内的子元素在水平方向上两端对齐
justify-content: space-between;
+ // 选择.login-container 内的.left-box 和.right-box 元素,设置它们在弹性布局中平均分配剩余空间
.left-box,
.right-box {
flex: 1;
}
+ // 选择.login-container 内的.right-box 元素,设置其样式
.right-box {
+ // 设置为弹性盒子布局
display: flex;
+ // 使弹性盒子内的子元素在垂直方向上居中对齐
align-items: center;
+ // 向上偏移 90 像素
margin-top: -90px;
- .login-form ,
+ // 选择.right-box 内的.login-form 和.forget-pass-form 元素,设置它们的样式
+ .login-form,
.forget-pass-form {
+ // 设置宽度为 360 像素
width: 360px;
+ // 设置左边距为 50 像素
margin-left: 50px;
+ // 选择.login-form 和.forget-pass-form 内的.title 元素,设置其样式
.title {
+ // 设置为弹性盒子布局,子元素垂直排列
display: flex;
flex-direction: column;
+ // 使弹性盒子内的子元素在水平方向上居中对齐
align-items: center;
+ // 选择.title 内的 img 元素,设置其样式
img {
width: 80px;
height: 80px;
}
+ // 选择.title 内的 h2 元素,设置其样式
h2 {
+ // 设置文本颜色
color: #9193a3;
+ // 设置外边距,上 10 像素,右 0 像素,下 15 像素,左 0 像素
margin: 10px 0 15px 0;
}
}
+ // 选择.login-form 和.forget-pass-form 内的.password-set 元素,设置其样式
.password-set {
+ // 设置为弹性盒子布局
display: flex;
+ // 设置宽度为父元素的 100%
width: 100%;
+ // 设置外边距,上 -10 像素,右 0 像素,下 10 像素,左 0 像素
margin: -10px 0 10px 0;
+ // 使弹性盒子内的子元素在水平方向上向右对齐
justify-content: right;
}
}
}
+ // 选择.login-container 内的.left-box 元素,设置其样式
.left-box {
+ // 设置定位为相对定位
position: relative;
+ // 设置宽度为父元素的 100%
width: 100%;
+ // 为.left-box 元素添加一个伪元素,作为其背景图像容器
&::before {
content: '';
+ // 设置定位为绝对定位
position: absolute;
+ // 定位到元素的顶部
top: 0;
+ // 定位到元素的左侧
left: 0;
+ // 设置宽度为父元素的 100%
width: 100%;
+ // 设置高度为父元素的 100%
height: 100%;
+ // 设置背景图像,使用相对路径引用项目中的 bg.png 图片
background-image: url('~@/assets/imgs/bg.png');
+ // 设置背景图像不重复
background-repeat: no-repeat;
+ // 设置背景图像的大小,宽度自动,高度为父元素的 100%
background-size: auto 100%;
+ // 设置 z 轴层级为 -1,使其在.left-box 内的其他元素下方
z-index: -1;
}
+ // 选择.left-box 内的.img 元素,设置其样式
.img {
+ // 设置宽度为父元素的 100%
width: 100%;
+ // 设置高度为父元素的 100%
height: 100%;
+ // 设置为弹性盒子布局
display: flex;
+ // 使弹性盒子内的子元素在垂直方向上居中对齐
align-items: center;
+ // 使弹性盒子内的子元素在水平方向上居中对齐
justify-content: center;
+ // 选择.img 内的 img 元素,设置其样式
img {
width: 500px;
}
diff --git a/client/src/styles/mixin.scss b/client/src/styles/mixin.scss
index 3ca7168..68a03c8 100644
--- a/client/src/styles/mixin.scss
+++ b/client/src/styles/mixin.scss
@@ -1,26 +1,34 @@
+// 定义一个名为 clearfix 的混合宏(mixin)
@mixin clearfix {
+ // 为使用该混合宏的元素添加一个伪元素:after
&:after {
content: '';
+ // 设置伪元素的显示方式为表格,用于清除浮动
display: table;
clear: both;
}
}
+// 定义一个名为 scrollBar 的混合宏(mixin),用于设置滚动条样式
@mixin scrollBar {
+ // 设置滚动条轨道的样式
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
+ // 设置滚动条的宽度
&::-webkit-scrollbar {
width: 6px;
}
+ // 设置滚动条滑块的样式
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
+// 定义一个名为 relative 的混合宏(mixin),用于设置元素的相对定位和尺寸
@mixin relative {
position: relative;
width: 100%;
diff --git a/client/src/styles/navbar.scss b/client/src/styles/navbar.scss
index a4af7eb..b7f5985 100644
--- a/client/src/styles/navbar.scss
+++ b/client/src/styles/navbar.scss
@@ -1,19 +1,31 @@
-// 导航划过阴影
+/* 定义一个名为.navbar-bg-hover 的类选择器,用于设置导航栏背景悬停效果的样式 */
.navbar-bg-hover {
+ /* 设置为弹性盒子布局 */
display: flex;
+ /* 使弹性盒子内的子元素在垂直方向上居中对齐 */
align-items: center;
+ /* 使弹性盒子内的子元素在水平方向上居中对齐 */
justify-content: center;
+ /* 设置高度为 48 像素 */
height: 48px;
+ /* 设置内边距为 12 像素 */
padding: 12px;
+ /* 设置所有属性的过渡效果,持续时间为 0.3 秒 */
transition: all 0.3s;
+}
- &:hover {
- background-color: #f6f6f6;
- cursor: pointer;
- transition: all 0.3s;
- }
+/* 当.navbar-bg-hover 元素被鼠标悬停时的样式 */
+&:hover {
+ /* 设置背景颜色为 #f6f6f6 */
+ background-color: #f6f6f6;
+ /* 设置鼠标指针样式为指针 */
+ cursor: pointer;
+ /* 再次设置所有属性的过渡效果,持续时间为 0.3 秒 */
+ transition: all 0.3s;
+}
- .svg-icon {
- outline: none;
- }
+/* 选择.navbar-bg-hover 元素内的.svg-icon 元素,设置其样式 */
+.svg-icon {
+ /* 去除轮廓线 */
+ outline: none;
}
diff --git a/client/src/styles/reset.scss b/client/src/styles/reset.scss
index 9ccb917..7d17a6d 100644
--- a/client/src/styles/reset.scss
+++ b/client/src/styles/reset.scss
@@ -1,61 +1,97 @@
+/* 对 html 和 body 元素设置样式 */
html,
body {
+ /* 设置高度为视口高度的 100% */
height: 100%;
+ /* 去除外边距 */
margin: 0;
+ /* 去除内边距 */
padding: 0;
+ /* 针对 Firefox 浏览器,设置字体抗锯齿为灰度模式,提高字体显示质量 */
-moz-osx-font-smoothing: grayscale;
+ /* 针对 WebKit 内核的浏览器(如 Chrome、Safari),设置字体抗锯齿,提高字体显示质量 */
-webkit-font-smoothing: antialiased;
+ /* 设置文本渲染方式为优化可读性 */
text-rendering: optimizeLegibility;
+ // 这行代码被注释掉,原本是设置字体族,由于被注释,当前不生效。
// font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
// Microsoft YaHei, Arial, sans-serif;
}
+/* 对 id 为 app 的元素设置样式 */
#app {
+ /* 设置高度为视口高度的 100% */
height: 100%;
}
+/* 对所有元素及其伪元素(:before 和 :after)设置样式 */
*,
*:before,
*:after {
+ /* 使元素的 box-sizing 属性继承自父元素,通常用于统一盒模型的计算方式 */
box-sizing: inherit;
+ /* 去除外边距 */
margin: 0;
+ /* 去除内边距 */
padding: 0;
}
+/* 对处于激活(active)和获得焦点(focus)状态的 a 元素设置样式 */
a:focus,
a:active {
+ /* 去除轮廓线 */
outline: none;
}
+/* 对 a 元素及其处于获得焦点(focus)和鼠标悬停(hover)状态时设置样式 */
a,
a:focus,
a:hover {
+ /* 设置鼠标指针样式为指针 */
cursor: pointer;
+ /* 使链接文本颜色继承自父元素 */
color: inherit;
+ /* 去除链接的下划线 */
text-decoration: none;
}
+/* 对获得焦点的 div 元素设置样式 */
div:focus {
+ /* 去除轮廓线 */
outline: none;
}
+/* 定义一个名为 clearfix 的类选择器,用于清除浮动,通过伪元素:after 实现 */
.clearfix {
&:after {
+ /* 使伪元素不可见 */
visibility: hidden;
+ /* 设置伪元素的显示方式为块级元素 */
display: block;
+ /* 设置伪元素的字体大小为 0 */
font-size: 0;
+ /* 设置伪元素的内容为空字符串 */
content: ' ';
+ /* 清除浮动 */
clear: both;
+ /* 设置伪元素的高度为 0 */
height: 0;
}
}
-div#driver-popover-item .driver-popover-title {
+/* 选择 id 为 driver-popover-item 的 div 元素内的.driver-popover-title 元素,设置其文本颜色 */
+div#driver-popover-item.driver-popover-title {
color: #1890ff;
}
-div#driver-popover-item .driver-popover-footer .driver-next-btn {
+
+/* 选择 id 为 driver-popover-item 的 div 元素内的.driver-popover-footer 元素内的.driver-next-btn 元素,设置其样式 */
+div#driver-popover-item.driver-popover-footer.driver-next-btn {
+ /* 设置背景颜色 */
background-color: #1890ff;
+ /* 设置文本颜色为白色 */
color: white;
+ /* 去除文本阴影 */
text-shadow: none;
+ /* 设置元素的边框圆角为 4 像素 */
border-radius: 4px;
}
diff --git a/client/src/styles/sidebar.scss b/client/src/styles/sidebar.scss
index 74840bd..d09e538 100644
--- a/client/src/styles/sidebar.scss
+++ b/client/src/styles/sidebar.scss
@@ -1,144 +1,221 @@
+// 定义主要内容区域的样式
.main-container {
+ // 宽度为视口宽度减去侧边栏宽度
width: calc(100% - $sideBarWidth);
+ // 高度为视口高度
height: 100%;
+ // 固定定位在视口右上角
position: fixed;
top: 0;
right: 0;
+ // 设置 z-index 为 9,用于控制元素的层叠顺序
z-index: 9;
+ // 所有属性的过渡效果,持续时间为 0.28 秒
transition: all 0.28s;
}
-// 重写菜单样式
+// 重写菜单样式,定义侧边栏容器的样式
.sidebar-container {
+ // 所有属性的过渡效果,持续时间为 0.3 秒
transition: all 0.3s;
+ // 宽度为侧边栏宽度变量的值,!important 提高优先级
width: $sideBarWidth !important;
+ // 高度为视口高度
height: 100%;
+ // 固定定位在视口左侧
position: fixed;
top: 0;
bottom: 0;
left: 0;
+ // 设置 z-index 为 1001,确保侧边栏在其他元素之上
z-index: 1001;
+ // 侧边栏的背景颜色,使用变量 $menuBg
background-color: $menuBg;
+ // 侧边栏滚动条包裹元素的样式
.scrollbar-wrapper {
+ // 隐藏水平滚动条,!important 提高优先级
overflow-x: hidden !important;
}
+ // 垂直滚动条的样式
.el-scrollbar__bar.is-vertical {
+ // 滚动条靠右显示
right: 0;
}
+ // 滚动条组件的样式
.el-scrollbar {
+ // 高度为视口高度减去 92px
height: calc(100% - 92px);
}
+ // 侧边栏有 logo 时的样式
&.has-logo {
+ // PC 端滚动条的样式
.el-scrollbar.pc {
/* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
+ // 高度为视口高度减去 92px
height: calc(100% - 92px);
}
+ // 移动端滚动条的样式
.el-scrollbar.mobile {
+ // 高度为视口高度
height: 100%;
}
}
+ // 水平菜单的样式
.is-horizontal {
+ // 隐藏水平菜单
display: none;
}
+ // 侧边栏链接的样式
a {
+ // 先设置为内联块元素,再设置为弹性布局
display: inline-block;
display: flex;
+ // 左侧内边距为 10px
padding-left: 10px;
+ // 子元素换行显示
flex-wrap: wrap;
+ // 宽度为 100%
width: 100%;
}
+ // Element UI 菜单组件的样式
.el-menu {
+ // 去除菜单边框
border: none;
+ // 高度为视口高度
height: 100%;
+ // 背景颜色为透明,!important 提高优先级
background-color: transparent !important;
}
+ // 菜单项和子菜单标题的样式
.el-menu-item,
.el-sub-menu__title {
+ // 高度为 50px
height: 50px;
+ // 文字颜色,使用变量 $menuText
color: $menuText;
+ // 背景颜色为透明,!important 提高优先级
background-color: transparent !important;
+ // 左侧内边距为 18px,!important 提高优先级
padding-left: 18px !important;
+ // 鼠标悬停时的样式
&:hover {
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuActiveText
color: $menuActiveText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
}
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuText
color: $menuText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
+ // 菜单项内的 div 和 span 元素样式
div,
span {
+ // 高度为 50px
height: 50px;
+ // 行高为 50px
line-height: 50px;
+ // 隐藏溢出内容
overflow: hidden;
+ // 溢出内容显示省略号
text-overflow: ellipsis;
}
+ // 图标样式
.svg-icon {
+ // 最小宽度为 1em
min-width: 1em;
+ // 右侧外边距为 5px
margin-right: 5px;
}
}
+ // 无子菜单的标题和子菜单标题鼠标悬停时的样式
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
+ // 背景颜色为透明
background-color: transparent;
}
}
- // 子级选中父级也跟着亮
+ // 子级选中时,父级菜单高亮显示
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 图标样式
i {
+ // 图标颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
}
}
+ // 激活状态的菜单项样式
.is-active {
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuActiveText
color: $menuActiveText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
}
// 子级菜单样式
.el-menu .el-sub-menu .el-menu-item {
+ // 字体大小为 12px
font-size: 12px;
+ // 最小宽度为 100px,!important 提高优先级
min-width: 100px !important;
+ // 背景颜色,使用变量 $subMenuBg,!important 提高优先级
background-color: $subMenuBg !important;
+ // 激活状态的子级菜单项样式
&.is-active {
+ // 背景颜色,使用变量 $menuActiveBg,!important 提高优先级
background: $menuActiveBg !important;
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
}
}
+ // 菜单项样式
.el-menu .el-menu-item {
+ // 菜单项提示框触发器样式
.el-menu-tooltip__trigger {
+ // 内边距为 18px
padding: 18px;
}
+ // 激活状态的菜单项样式
&.is-active {
+ // 背景颜色,使用变量 $menuActiveBg,!important 提高优先级
background: $menuActiveBg !important;
}
}
@@ -146,153 +223,224 @@
// 菜单栏折叠时的宽度
.el-menu--collapse {
+ // 宽度为折叠后侧边栏宽度变量的值
width: $hideSideBarWidth;
}
// 折叠菜单时的样式
.hideSidebar {
- // 菜单栏容器
+ // 菜单栏容器样式
.sidebar-container {
+ // 宽度过渡效果,持续时间为 0.3 秒
transition: width 0.3s;
+ // 宽度为折叠后侧边栏宽度变量的值,!important 提高优先级
width: $hideSideBarWidth !important;
+ // 子菜单样式
.el-sub-menu {
+ // 激活状态的子菜单样式
&.is-active {
+ // 所有属性过渡效果,持续时间为 0.28 秒
transition: all 0.28s;
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 相对定位
position: relative;
+ // 激活状态子菜单的伪元素样式
&::after {
+ // 伪元素内容为空
content: '';
+ // 高度为 50px
height: 50px;
+ // 左侧距离为 2px
left: 2px;
+ // 绝对定位
position: absolute;
+ // 顶部距离为 0
top: 0;
+ // 过渡延迟 0.28 秒,!important 提高优先级
transition-delay: 0.28s !important;
+ // 宽度为 2px
width: 2px;
+ // 背景颜色为主题色变量的值,!important 提高优先级
background-color: var(--el-color-primary) !important;
+ // 不透明度为 1
opacity: 1;
}
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuActiveText
color: $menuActiveText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
}
}
- // 可折叠菜单
+ // 可折叠菜单标题样式
.el-sub-menu__title {
+ // 内边距为 18px
padding: 18px;
}
- // 不可折叠菜单
+ // 不可折叠菜单项样式
.el-menu-item {
+ // 菜单项提示框触发器样式
.el-menu-tooltip__trigger {
+ // 内边距为 18px
padding: 18px;
}
}
}
- // 菜单的最外层
+ // 菜单最外层样式
.el-menu--vertical {
+ // 宽度为折叠后侧边栏宽度变量的值,!important 提高优先级
width: $hideSideBarWidth !important;
+ // 宽度过渡效果,持续时间为 0.3 秒
transition: width 0.3s;
}
- // 主要内容
+ // 主要内容区域样式
.main-container {
+ // 宽度为视口宽度减去折叠后侧边栏宽度
width: calc(100% - $hideSideBarWidth);
}
}
-// 展开时的菜单
+// 展开时的菜单样式
.el-menu--vertical {
+ // 宽度为侧边栏宽度变量的值,!important 提高优先级
width: $sideBarWidth !important;
+ // 宽度过渡效果,持续时间为 0.3 秒
transition: width 0.3s;
/* 子菜单中还有子菜单 */
+ // 子菜单标题样式
.el-menu .el-sub-menu__title {
+ // 字体大小为 12px
font-size: 12px;
+ // 背景颜色,使用变量 $subMenuBg,!important 提高优先级
background-color: $subMenuBg !important;
}
}
-// 折叠时的菜单
+// 折叠时的菜单弹出框样式
.el-menu--popup-container {
+ // 菜单样式
.el-menu {
+ // 背景颜色,使用变量 $subMenuBg,!important 提高优先级
background-color: $subMenuBg !important;
}
+ // 菜单项和子菜单标题样式
.el-menu-item,
.el-sub-menu__title {
+ // 高度为 50px
height: 50px;
+ // 文字颜色,使用变量 $menuText
color: $menuText;
+ // 背景颜色为透明,!important 提高优先级
background-color: transparent !important;
+ // 鼠标悬停时的样式
&:hover {
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuActiveText
color: $menuActiveText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
}
+ // 图标样式
.svg-icon {
+ // 图标颜色,使用变量 $menuText
color: $menuText;
+ // 颜色过渡效果,持续时间为 0.3 秒
transition: color 0.3s;
}
+ // 菜单项内的 div 和 span 元素样式
div,
span {
+ // 高度为 50px
height: 50px;
+ // 行高为 50px
line-height: 50px;
+ // 隐藏溢出内容
overflow: hidden;
+ // 溢出内容显示省略号
text-overflow: ellipsis;
}
}
+ // 无子菜单的标题和子菜单标题鼠标悬停时的样式
.submenu-title-noDropdown,
.el-sub-menu__title {
&:hover {
+ // 背景颜色为透明
background-color: transparent;
}
}
+ // 子级选中时,父级菜单高亮显示
.is-active > .el-sub-menu__title,
.is-active.submenu-title-noDropdown {
+ // 文字颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
+ // 图标样式
i {
+ // 图标颜色,使用变量 $menuActiveText,!important 提高优先级
color: $menuActiveText !important;
}
}
+ // 菜单项样式
.el-menu .el-menu-item {
+ // 字体大小为 12px,!important 提高优先级
font-size: 12px !important;
+ // 最小宽度为 100px,!important 提高优先级
min-width: 100px !important;
+ // 背景颜色,使用变量 $subMenuBg,!important 提高优先级
background-color: $subMenuBg !important;
+ // 激活状态的菜单项样式
&.is-active {
+ // 背景颜色,使用变量 $menuHover,!important 提高优先级
background: $menuHover !important;
}
}
+ // 激活状态的菜单项样式
.el-menu .el-menu-item {
&.is-active {
+ // 背景颜色,使用变量 $menuHover,!important 提高优先级
background: $menuHover !important;
}
}
}
-// 修复折叠后闪烁的bug
+// 修复折叠后闪烁的 bug
.is-active {
+ // 激活状态元素的伪元素样式
&::after {
+ // 伪元素内容为空
content: '';
+ // 不透明度为 1,!important 提高优先级
opacity: 1 !important;
}
+ // 菜单图标样式
.menu-icon {
+ // 无过渡效果,!important 提高优先级
transition: none !important;
+ // 图标颜色为白色,!important 提高优先级
color: #fff !important;
}
}
diff --git a/client/src/styles/theme.scss b/client/src/styles/theme.scss
index dbdc497..5599412 100644
--- a/client/src/styles/theme.scss
+++ b/client/src/styles/theme.scss
@@ -1,15 +1,19 @@
-// 卡片数字
+/* 定义一个名为.large-card-font 的类选择器,用于设置卡片上数字的样式 */
.large-card-font {
+ /* 使用 @apply 指令,应用其他样式类的样式,这里应用了 font-semibold(半粗体字体)和 text-2xl(字体大小为 2 倍大)这两个样式类 */
@apply font-semibold text-2xl;
+ /* 设置文本颜色为 RGB 值为 (41, 137, 255) 的颜色 */
color: rgb(41, 137, 255);
}
-// 字体颜色
+/* 定义一个名为.text-main 的类选择器,用于设置文本的主要颜色 */
.text-main {
+ /* 设置文本颜色为十六进制颜色值 #cecece */
color: #cecece;
}
-// 默认边框颜色
+/* 定义一个名为.border-main 的类选择器,用于设置默认的边框颜色和样式 */
.border-main {
+ /* 设置边框为 1 像素宽的实线,颜色为十六进制颜色值 #cfcfcf */
border: #cfcfcf solid 1px;
}
diff --git a/client/src/styles/transition.scss b/client/src/styles/transition.scss
index 2cc15ce..bc96ddd 100644
--- a/client/src/styles/transition.scss
+++ b/client/src/styles/transition.scss
@@ -1,31 +1,43 @@
-// 面包屑动画
+/* 定义面包屑进入和离开时的活跃状态样式,设置过渡效果 */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
+ /* 所有属性的过渡效果,持续时间为 0.5 秒 */
transition: all 0.5s;
}
+/* 定义面包屑进入前的状态和离开时的活跃状态(这里似乎重复定义了离开活跃状态,可能存在错误)的样式 */
.breadcrumb-enter-from,
.breadcrumb-leave-active {
+ /* 透明度设置为 0 */
opacity: 0;
+ /* 水平方向平移 20 像素 */
transform: translateX(20px);
}
+/* 再次定义面包屑离开时的活跃状态样式,设置为绝对定位 */
.breadcrumb-leave-active {
position: absolute;
}
-/* 页面切换过渡动画 */
+/* 定义页面切换时的过渡动画相关类的样式,设置进入和离开时的活跃状态的过渡效果 */
.fade-transform-leave-active,
.fade-transform-enter-active {
+ /* 所有属性的过渡效果,持续时间为 0.28 秒 */
transition: all 0.28s;
}
+/* 定义页面切换时进入前的状态样式 */
.fade-transform-enter-from {
+ /* 透明度设置为 0 */
opacity: 0;
+ /* 水平向左平移 30 像素 */
transform: translateX(-30px);
}
+/* 定义页面切换时离开到的状态样式 */
.fade-transform-leave-to {
+ /* 透明度设置为 0 */
opacity: 0;
+ /* 水平向右平移 30 像素 */
transform: translateX(30px);
}
diff --git a/client/src/styles/variables.module.scss b/client/src/styles/variables.module.scss
index 9099d65..82562d4 100644
--- a/client/src/styles/variables.module.scss
+++ b/client/src/styles/variables.module.scss
@@ -1,24 +1,43 @@
-// sidebar
+// 定义侧边栏相关的样式变量
+
+// 侧边栏菜单文本的默认颜色,这里的颜色值 #fefefea6 表示带有一定透明度的白色
$menuText: #fefefea6;
+// 侧边栏菜单激活时(选中时)文本的颜色,为纯白色
$menuActiveText: #fff;
+// 侧边栏菜单的背景颜色,设置为深色调 #031429
$menuBg: #031429;
+// 侧边栏菜单激活时(选中时)的背景颜色,为蓝色调 #558df6
$menuActiveBg: #558df6;
+// 侧边栏菜单项鼠标悬停时的背景颜色,同样为蓝色调 #558df6
$menuHover: #558df6;
+// 子菜单的背景颜色,设置为较深的颜色 #0e0303
$subMenuBg: #0e0303;
+// 隐藏的子菜单背景颜色,设置为更深的颜色 #00061a
$subHideMenuBg: #00061a;
+// 侧边栏展开时的宽度,设置为 210 像素
$sideBarWidth: 210px;
+// 侧边栏折叠时的宽度,设置为 54 像素
$hideSideBarWidth: 54px;
+// 使用 :export 指令将定义的变量导出,以便在其他地方(如 JavaScript 中)使用
:export {
+ // 将 $menuText 变量导出为 menuText
menuText: $menuText;
+ // 将 $menuActiveText 变量导出为 menuActiveText
menuActiveText: $menuActiveText;
+ // 将 $menuBg 变量导出为 menuBg
menuBg: $menuBg;
+ // 将 $menuHover 变量导出为 menuHover
menuHover: $menuHover;
+ // 将 $subMenuBg 变量导出为 subMenuBg
subMenuBg: $subMenuBg;
+ // 将 $subHideMenuBg 变量导出为 subHideMenuBg
subHideMenuBg: $subHideMenuBg;
+ // 将 $sideBarWidth 变量导出为 sideBarWidth
sideBarWidth: $sideBarWidth;
+ // 将 $hideSideBarWidth 变量导出为 hideSideBarWidth
hideSideBarWidth: $hideSideBarWidth;
}
diff --git a/client/src/styles/variables.module.scss.d.ts b/client/src/styles/variables.module.scss.d.ts
index a4be38b..1576325 100644
--- a/client/src/styles/variables.module.scss.d.ts
+++ b/client/src/styles/variables.module.scss.d.ts
@@ -1,12 +1,25 @@
+// 定义一个接口 IVariables,用于描述包含侧边栏样式相关变量的对象结构
export interface IVariables {
+ // 菜单文本的颜色,类型为字符串
menuText: string
+ // 菜单激活时文本的颜色,类型为字符串
menuActiveText: string
+ // 菜单的背景颜色,类型为字符串
menuBg: string
+ // 菜单鼠标悬停时的背景颜色,类型为字符串
menuHover: string
+ // 子菜单的背景颜色,类型为字符串
subMenuBg: string
+ // 隐藏子菜单的背景颜色,类型为字符串
subHideMenuBg: string
+ // 侧边栏展开时的宽度,类型为字符串
sideBarWidth: string
+ // 侧边栏折叠时的宽度,类型为字符串
hideSideBarWidth: string
}
+
+// 声明一个名为 styles 的变量,其类型为 IVariables,但未初始化
export const styles: IVariables
+
+// 默认导出 styles 变量,但由于未初始化,可能在使用时需要注意
export default styles
diff --git a/client/src/types/global.d.ts b/client/src/types/global.d.ts
index e0636d7..3fc7117 100644
--- a/client/src/types/global.d.ts
+++ b/client/src/types/global.d.ts
@@ -1,11 +1,23 @@
/** 文件类型 */
+// 声明一个模块,用于处理.svg 结尾的文件,这样在 TypeScript 中就可以导入.svg 文件而不会报错,
+// 但这里只是简单声明,没有定义具体的类型,意味着导入该类型文件时会被视为 any 类型
declare module '*.svg'
+// 声明用于处理.png 结尾的文件的模块
declare module '*.png'
+// 声明用于处理.jpg 结尾的文件的模块
declare module '*.jpg'
+// 声明用于处理.jpeg 结尾的文件的模块
declare module '*.jpeg'
+// 声明用于处理.gif 结尾的文件的模块
declare module '*.gif'
+// 声明用于处理.bmp 结尾的文件的模块
declare module '*.bmp'
+// 声明用于处理.tiff 结尾的文件的模块
declare module '*.tiff'
+// 声明用于处理.yaml 结尾的文件的模块
declare module '*.yaml'
+// 声明用于处理.json 结尾的文件的模块
declare module '*.json'
+// 声明用于处理 vue-count-to 模块,可能是为了在项目中使用 vue-count-to 这个 Vue 插件时,
+// 让 TypeScript 知道它的存在,即使没有具体的类型定义,也能正常导入和使用(类型为 any)
declare module 'vue-count-to'
diff --git a/client/src/types/vuex.d.ts b/client/src/types/vuex.d.ts
index 80d2f40..7b5e133 100644
--- a/client/src/types/vuex.d.ts
+++ b/client/src/types/vuex.d.ts
@@ -1,12 +1,17 @@
-// vuex.d.ts
+// 从 'vuex' 库中导入 Store 和 Module 类型
import { Store, Module } from 'vuex'
+// 从 './store/types' 文件中导入 IStoreType 类型,这个类型应该定义了 Vuex 存储的状态结构
import { type IStoreType } from './store/types'
-// 模块扩展
-// 模板内$store强类型支持
+// 模块扩展,用于在 Vue 运行时核心模块中添加自定义类型声明
+// 这样做可以让 TypeScript 知道在 Vue 组件中 this.$store 的具体类型
declare module '@vue/runtime-core' {
// 为 `this.$store` 提供类型声明
+ // ComponentCustomProperties 是 Vue 中用于扩展组件自定义属性的接口
interface ComponentCustomProperties {
+ // 声明 $store 属性,其类型为 Store
+ // Store 是 Vuex 中的存储类,IStoreType 是之前导入的状态类型
+ // 这意味着在 Vue 组件中使用 this.$store 时,TypeScript 会进行类型检查,确保操作符合 IStoreType 的定义
$store: Store
}
}
diff --git a/client/src/utils/commonUtil.ts b/client/src/utils/commonUtil.ts
index 8258bd6..191e548 100644
--- a/client/src/utils/commonUtil.ts
+++ b/client/src/utils/commonUtil.ts
@@ -1,46 +1,47 @@
-// TODO 初始化Array
+// 导出一个函数initArray,用于初始化数组
+// 如果传入的array存在则返回array,否则返回一个空数组
export const initArray = (array: any) => {
- return array ? array : [];
+ return array? array : [];
};
-// TODO 将一个Array的某个字段所有的值全部添加到了一个Array
+// 导出一个函数arrayPushAllByFiled,用于将一个数组中某个字段的所有值添加到另一个数组
export const arrayPushAllByFiled = (dataArray: any, pushArray: any, field: any) => {
- // 初始化
+ // 初始化dataArray和pushArray
dataArray = initArray(dataArray);
pushArray = initArray(pushArray);
- // 法一
+ // 法一:使用for...of循环遍历dataArray,将每个元素的指定字段值添加到pushArray中
// for (const item of dataArray) {
// pushArray.push(item[field]);
// }
// return pushArray;
- // 法二
+ // 法二:使用map方法遍历dataArray,返回一个包含所有指定字段值的新数组
return dataArray.map((item: any) => item[field]);
};
-// TODO 将一个Array的数据全部添加到了一个Array
+// 导出一个函数arrayPushAllByObj,用于将一个数组的数据全部添加到另一个数组
export const arrayPushAllByObj = (dataArray: any, pushArray: any) => {
- // 初始化
+ // 初始化dataArray和pushArray
dataArray = initArray(dataArray);
pushArray = initArray(pushArray);
- // push
+ // 使用展开运算符将dataArray的所有元素添加到pushArray中
pushArray.push(...dataArray);
return pushArray;
};
-// TODO Array根据字段去重
+// 导出一个函数arrayDeduplicationByFiled,用于根据指定字段对数组进行去重
export const arrayDeduplicationByFiled = (array: any, field: string) => {
- // 初始化
+ // 初始化array
array = initArray(array);
- // 去重 法一:该方法对编号进行去重返回的是[1,3,5]的编号数组
+ // 去重 法一:使用Set和map方法对指定字段进行去重,返回一个包含去重后字段值的新数组
// const idArray = [...new Set(array.map((item: any) => item[field]))];
// 再使用for循环依次取出id对应数据即可
- // 去重 法二
+ // 去重 法二:使用reduce方法遍历数组,检查当前对象的指定字段是否已存在于新数组中,不存在则添加
return array.reduce((newArray: any, thisObj: any) => {
// 检查当前对象的属性是否已经存在于新数组中
const exists = newArray.some((item: any) => thisObj[field] === item[field]);
@@ -52,16 +53,16 @@ export const arrayDeduplicationByFiled = (array: any, field: string) => {
}, []);
};
-// TODO Array根据对象去重(适用于非对象数组)
+// 导出一个函数arrayDeduplicationByObj,用于根据对象对数组进行去重(适用于非对象数组)
export const arrayDeduplicationByObj = (array: any) => {
- // 初始化
+ // 初始化array
array = initArray(array);
- // 去重 法一:该方法对编号进行去重返回的是[1,3,5]的编号数组
+ // 去重 法一:使用Set和map方法对数组元素进行去重,返回一个包含去重后元素的新数组
// const idArray = [...new Set(array.map((item: any) => item))];
// 再使用for循环依次取出id对应数据即可
- // 去重 法二
+ // 去重 法二:使用reduce方法遍历数组,检查当前对象是否已存在于新数组中,不存在则添加
return array.reduce((newArray: any, thisObj: any) => {
// 检查当前对象的属性是否已经存在于新数组中
const exists = newArray.some((item: any) => thisObj === item);
diff --git a/client/src/utils/formRules.ts b/client/src/utils/formRules.ts
index 9953f35..1b6ac04 100644
--- a/client/src/utils/formRules.ts
+++ b/client/src/utils/formRules.ts
@@ -1,4 +1,4 @@
-// 电话规则
+// 从 @/utils/is 模块中导入多个用于验证的函数
import {
isNotEmail,
isNotIdNum,
@@ -7,43 +7,58 @@ import {
isNotNumber
} from "@/utils/is";
-// 电话规则
+// 电话规则验证函数
export function phoneRule(rule: any, value: any, callback: any, field: string) {
+ // 去除输入值的首尾空格
const trim = value?.trim();
+ // 如果输入值为空
if (!trim) {
+ // 调用回调函数并传递错误信息,提示电话不能为空
callback(new Error(field + "电话不能为空"));
} else if (isNotPhone(trim)) {
+ // 如果输入值不是有效的电话号码格式
callback(new Error(field + "电话格式有误"));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
-// 邮箱规则
+// 邮箱规则验证函数
export function emailRule(rule: any, value: any, callback: any, field: string) {
+ // 去除输入值的首尾空格
const trim = value?.trim();
+ // 如果输入值为空
if (!trim) {
+ // 调用回调函数并传递错误信息,提示邮箱不能为空
callback(new Error(field + "邮箱不能为空"));
} else if (isNotEmail(trim)) {
+ // 如果输入值不是有效的邮箱格式
callback(new Error(field + "邮箱格式有误"));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
-// 身份证号规则
+// 身份证号规则验证函数
export function idNumRule(rule: any, value: any, callback: any, field: string) {
+ // 去除输入值的首尾空格
const trim = value?.trim();
+ // 如果输入值为空
if (!trim) {
+ // 调用回调函数并传递错误信息,提示身份证号不能为空
callback(new Error(field + "身份证号不能为空"));
} else if (isNotIdNum(trim)) {
+ // 如果输入值不是有效的身份证号格式
callback(new Error(field + "身份证号格式有误"));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
-// 字符串规则
+// 字符串规则验证函数
export function stringRule(
rule: any,
value: any,
@@ -52,17 +67,22 @@ export function stringRule(
mixLen: number,
maxLen: number
) {
+ // 去除输入值的首尾空格
const trim = value?.trim();
+ // 如果输入值为空
if (!trim) {
+ // 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else if (trim.length < mixLen || trim.length > maxLen) {
+ // 如果输入值的长度不在指定范围内
callback(new Error(field + "长度应为" + mixLen + "~" + maxLen));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
-// 数字(整数/小数)规则
+// 数字(整数/小数)规则验证函数
export function numberRule(
rule: any,
value: any,
@@ -71,21 +91,27 @@ export function numberRule(
mix: number,
max: number
) {
+ // 如果输入值为空
if (!value) {
+ // 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else {
+ // 将输入值转换为字符串并去除首尾空格
const trim = value.toString()?.trim();
+ // 如果输入值不是有效的数字格式
if (isNotNumber(trim)) {
callback(new Error(field + "格式有误"));
} else if (trim < mix || trim > max) {
+ // 如果输入值不在指定的数字范围内
callback(new Error(field + "应该在" + mix + "~" + max + "之间"));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
}
-// 整数规则
+// 整数规则验证函数
export function integerRule(
rule: any,
value: any,
@@ -94,24 +120,32 @@ export function integerRule(
mix: number,
max: number
) {
+ // 如果输入值为空
if (!value) {
+ // 调用回调函数并传递错误信息,提示该字段不能为空
callback(new Error(field + "不能为空"));
} else {
+ // 将输入值转换为字符串并去除首尾空格
const trim = value.toString()?.trim();
+ // 如果输入值不是有效的整数格式
if (isNotInteger(trim)) {
callback(new Error(field + "格式有误"));
} else if (trim < mix || trim > max) {
+ // 如果输入值不在指定的整数范围内
callback(new Error(field + "应该在" + mix + "~" + max + "之间"));
} else {
+ // 如果输入值有效,调用回调函数且不传递错误信息
callback();
}
}
}
-// 日期规则
+// 日期规则验证函数,禁用当前时间之前的日期
export const disabledNowBeforeDate = (time: Date) => {
return time.getTime() < Date.now();
};
+
+// 日期规则验证函数,禁用当前时间之后的日期
export const disabledNowAfterDate = (time: Date) => {
return time.getTime() > Date.now();
};
diff --git a/client/src/utils/http/index.ts b/client/src/utils/http/index.ts
index 7aad11f..f2af8ec 100644
--- a/client/src/utils/http/index.ts
+++ b/client/src/utils/http/index.ts
@@ -1,35 +1,52 @@
+// 从 '@/store' 导入 Vuex 的 store 实例
import store from "@/store";
+// 导入 axios 库,并解构出 AxiosRequestConfig 和 AxiosResponse 类型
import baseAxios, { AxiosRequestConfig, AxiosResponse } from "axios";
+// 从 'element-plus' 导入 ElMessage 用于显示消息提示
import { ElMessage } from "element-plus";
+// 定义基础 URL
export const baseUrl = "http://127.0.0.1:9001/";
+// 创建一个 axios 实例,设置基础 URL、超时时间和默认 headers
const axios = baseAxios.create({
- baseURL: "",
- timeout: 5000,
- headers: {}
+ baseURL: "", // 这里可以根据实际情况设置基础 URL,如果为空则使用全局的 baseUrl
+ timeout: 5000, // 设置请求超时时间为 5000 毫秒
+ headers: {} // 可以在这里设置默认的请求头
});
-// 请求拦截器
+// 请求拦截器,在请求发送前会被调用
axios.interceptors.request.use((config: any) => {
+ // 从 localStorage 中获取名为 "stateData" 的数据,并解析为 JSON 对象
+ // 然后尝试获取其中的 app.token 作为令牌
const token = JSON.parse(localStorage.getItem("stateData")!)?.app?.token;
+ // 将令牌设置到请求头的 "token" 字段中
config.headers.token = token;
+ // 返回配置好的请求对象
return config;
});
-// 响应拦截器
+// 响应拦截器,在接收到响应后会被调用
axios.interceptors.response.use(
(res: AxiosResponse) => {
+ // 如果响应数据中的 code 为 500 且 msg 为 "令牌无效"
if (res.data.code === 500 && res.data.msg === "令牌无效") {
+ // 调用 store 的 app/logout 方法进行登出操作
store.dispatch("app/logout");
+ // 显示错误消息提示 "登录过期,请重新登录"
ElMessage.error({ message: "登录过期,请重新登录" });
}
+ // 如果响应数据中的 err 为 1
if (res.data.err === 1) {
+ // 则返回一个被拒绝的 Promise,携带响应数据
return Promise.reject(res.data);
}
+ // 返回响应数据
return res.data;
},
- err => Promise.reject(err) // 响应出问题
+ // 如果响应出现错误,返回一个被拒绝的 Promise,携带错误信息
+ err => Promise.reject(err)
);
+// 默认导出创建好的 axios 实例
export default axios;
diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts
index 0135629..bf6a3d8 100644
--- a/client/src/utils/index.ts
+++ b/client/src/utils/index.ts
@@ -1,3 +1,7 @@
+// 从 './http' 文件中导入名为 http 的模块
+// 这里假设 './http' 文件中导出了一个名为 http 的对象,这个对象可能是封装好的 HTTP 请求工具,例如使用 axios 封装的请求实例
import http from './http'
+// 导出导入的 http 对象
+// 这样其他文件就可以通过导入这个文件来使用 http 对象进行 HTTP 请求操作
export { http }
diff --git a/client/src/utils/is/index.ts b/client/src/utils/is/index.ts
index 3f4c3ff..77c8660 100644
--- a/client/src/utils/is/index.ts
+++ b/client/src/utils/is/index.ts
@@ -1,174 +1,277 @@
-const toString = Object.prototype.toString
-// 保留两位小数的实数校验规则
-const REGEXP_NUMBER = /^[+-]?(0|([1-9]\d*))(\.\d{1,2})?$/
-// 密码校验规则
-const REGEXP_EMAIL = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/
-// 手机号校验规则
-const REGEXP_PHONE = /^1[3456789]\d{9}$/
-// 身份证号校验规则
+// 获取 Object.prototype.toString 方法的引用,用于后续类型判断
+const toString = Object.prototype.toString;
+// 定义一个正则表达式,用于校验保留两位小数的实数
+// 支持正负号,整数部分可以是 0 或者以 1 - 9 开头的数字串,小数部分可以有 1 到 2 位
+const REGEXP_NUMBER = /^[+-]?(0|([1-9]\d*))(\.\d{1,2})?$/;
+// 定义一个正则表达式,用于校验邮箱格式
+// 要求邮箱由字母、数字组成,中间有 @ 符号,并且有正确的域名和顶级域名
+const REGEXP_EMAIL = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/;
+// 定义一个正则表达式,用于校验手机号格式
+// 要求手机号以 1 开头,第二位是 3 - 9 中的一个数字,后面跟着 9 位数字
+const REGEXP_PHONE = /^1[3456789]\d{9}$/;
+// 定义一个正则表达式,用于校验身份证号格式
+// 符合中国 18 位身份证号码的格式规则
const REGEXP_ID_NUM =
- /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
+ /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
/**
* @description: 判断值是否为账号(手机号/邮箱)
+ * @param {string} account - 待判断的账号字符串
+ * @returns {boolean} - 如果既不是手机号也不是邮箱,返回 true;否则返回 false
*/
export function isNotAccount(account: string) {
- return !REGEXP_PHONE.test(account) && !REGEXP_EMAIL.test(account)
+ // 使用正则表达式检查账号是否既不是手机号也不是邮箱
+ return !REGEXP_PHONE.test(account) && !REGEXP_EMAIL.test(account);
}
/**
* @description: 判断值是否为手机号
+ * @param {string} phone - 待判断的手机号字符串
+ * @returns {boolean} - 如果不是手机号,返回 true;否则返回 false
*/
export function isNotPhone(phone: string) {
- return !REGEXP_PHONE.test(phone)
+ // 使用正则表达式检查是否不是手机号
+ return !REGEXP_PHONE.test(phone);
}
/**
* @description: 判断值是否为邮箱
+ * @param {string} email - 待判断的邮箱字符串
+ * @returns {boolean} - 如果不是邮箱,返回 true;否则返回 false
*/
export function isNotEmail(email: string) {
- return !REGEXP_EMAIL.test(email)
+ // 使用正则表达式检查是否不是邮箱
+ return !REGEXP_EMAIL.test(email);
}
/**
* @description: 判断值是否为身份证号
+ * @param {string} idNum - 待判断的身份证号字符串
+ * @returns {boolean} - 如果不是身份证号,返回 true;否则返回 false
*/
export function isNotIdNum(idNum: string) {
- return !REGEXP_ID_NUM.test(idNum)
+ // 使用正则表达式检查是否不是身份证号
+ return !REGEXP_ID_NUM.test(idNum);
}
/**
* @description: 判断值是否为数字
+ * @param {string} num - 待判断的数字字符串
+ * @returns {boolean} - 如果不是符合规则的数字,返回 true;否则返回 false
*/
export function isNotNumber(num: string) {
- return !REGEXP_NUMBER.test(num)
+ // 使用正则表达式检查是否不是符合规则的数字
+ return !REGEXP_NUMBER.test(num);
}
/**
* @description: 判断值是否为整数
+ * @param {number} num - 待判断的数字
+ * @returns {boolean} - 如果不是整数,返回 true;否则返回 false
*/
export function isNotInteger(num: number) {
- return !Number.isInteger(Number(num))
+ // 使用 Number.isInteger 方法检查是否不是整数
+ return !Number.isInteger(Number(num));
}
/**
- * @description: 判断值是否未某个类型
+ * @description: 判断值是否为某个类型
+ * @param {unknown} val - 待判断的值
+ * @param {string} type - 要判断的类型名称
+ * @returns {boolean} - 如果值是指定类型,返回 true;否则返回 false
*/
export function is(val: unknown, type: string) {
- return toString.call(val) === `[object ${type}]`
+ // 使用 toString 方法获取值的类型字符串,并与指定类型比较
+ return toString.call(val) === `[object ${type}]`;
}
/**
- * @description: 是否为函数
+ * @description: 是否为函数
+ * @param {unknown} val - 待判断的值
+ * @returns {val is T} - 如果是函数,返回 true;否则返回 false
*/
export function isFunction void>(val: unknown): val is T {
- return is(val, 'Function')
+ // 调用 is 函数判断是否为 Function 类型
+ return is(val, 'Function');
}
/**
* @description: 是否已定义
+ * @param {T} val - 待判断的值
+ * @returns {val is T} - 如果值已定义(不是 undefined),返回 true;否则返回 false
*/
export const isDef = (val?: T): val is T => {
- return typeof val !== 'undefined'
+ // 使用 typeof 检查值是否不是 undefined
+ return typeof val !== 'undefined';
}
+/**
+ * @description: 是否未定义
+ * @param {T} val - 待判断的值
+ * @returns {val is T} - 如果值未定义(是 undefined),返回 true;否则返回 false
+ */
export const isUnDef = (val?: T): val is T => {
- return !isDef(val)
+ // 调用 isDef 函数取反判断是否未定义
+ return !isDef(val);
}
+
/**
* @description: 是否为对象
+ * @param {any} val - 待判断的值
+ * @returns {val is Record} - 如果是对象且不为 null,返回 true;否则返回 false
*/
export const isObject = (val: any): val is Record => {
- return val !== null && is(val, 'Object')
+ // 检查值不为 null 且是 Object 类型
+ return val !== null && is(val, 'Object');
}
/**
- * @description: 是否为时间
+ * @description: 是否为时间
+ * @param {unknown} val - 待判断的值
+ * @returns {val is Date} - 如果是 Date 类型,返回 true;否则返回 false
*/
export function isDate(val: unknown): val is Date {
- return is(val, 'Date')
+ // 调用 is 函数判断是否为 Date 类型
+ return is(val, 'Date');
}
/**
- * @description: 是否为数值
+ * @description: 是否为数值
+ * @param {unknown} val - 待判断的值
+ * @returns {val is number} - 如果是 Number 类型,返回 true;否则返回 false
*/
export function isNumber(val: unknown): val is number {
- return is(val, 'Number')
+ // 调用 is 函数判断是否为 Number 类型
+ return is(val, 'Number');
}
/**
- * @description: 是否为AsyncFunction
+ * @description: 是否为 AsyncFunction
+ * @param {unknown} val - 待判断的值
+ * @returns {val is Promise} - 如果是 AsyncFunction 类型,返回 true;否则返回 false
+ * 注:这里类型标注有误,实际应是检查是否为异步函数类型
*/
export function isAsyncFunction(val: unknown): val is Promise {
- return is(val, 'AsyncFunction')
+ // 调用 is 函数判断是否为 AsyncFunction 类型
+ return is(val, 'AsyncFunction');
}
/**
- * @description: 是否为promise
+ * @description: 是否为 promise
+ * @param {unknown} val - 待判断的值
+ * @returns {val is Promise} - 如果是 Promise 对象,返回 true;否则返回 false
*/
export function isPromise(val: unknown): val is Promise {
+ // 检查值是 Promise 类型,是对象,且有 then 和 catch 方法
return (
is(val, 'Promise') &&
isObject(val) &&
isFunction(val.then) &&
isFunction(val.catch)
- )
+ );
}
/**
- * @description: 是否为字符串
+ * @description: 是否为字符串
+ * @param {unknown} val - 待判断的值
+ * @returns {val is string} - 如果是 String 类型,返回 true;否则返回 false
*/
export function isString(val: unknown): val is string {
- return is(val, 'String')
+ // 调用 is 函数判断是否为 String 类型
+ return is(val, 'String');
}
/**
- * @description: 是否为boolean类型
+ * @description: 是否为 boolean 类型
+ * @param {unknown} val - 待判断的值
+ * @returns {val is boolean} - 如果是 Boolean 类型,返回 true;否则返回 false
*/
export function isBoolean(val: unknown): val is boolean {
- return is(val, 'Boolean')
+ // 调用 is 函数判断是否为 Boolean 类型
+ return is(val, 'Boolean');
}
/**
- * @description: 是否为数组
+ * @description: 是否为数组
+ * @param {any} val - 待判断的值
+ * @returns {val is Array} - 如果是数组,返回 true;否则返回 false
*/
export function isArray(val: any): val is Array {
- return val && Array.isArray(val)
+ // 检查值存在且是数组
+ return val && Array.isArray(val);
}
/**
* @description: 是否客户端
+ * @returns {boolean} - 如果在客户端环境(window 对象存在),返回 true;否则返回 false
*/
export const isClient = () => {
- return typeof window !== 'undefined'
+ // 检查 window 对象是否已定义
+ return typeof window !== 'undefined';
}
/**
* @description: 是否为浏览器
+ * @param {any} val - 待判断的值
+ * @returns {val is Window} - 如果是 window 对象,返回 true;否则返回 false
*/
export const isWindow = (val: any): val is Window => {
- return typeof window !== 'undefined' && is(val, 'Window')
+ // 检查 window 对象已定义且值是 Window 类型
+ return typeof window !== 'undefined' && is(val, 'Window');
}
+/**
+ * @description: 是否为元素
+ * @param {unknown} val - 待判断的值
+ * @returns {val is Element} - 如果是 DOM 元素,返回 true;否则返回 false
+ */
export const isElement = (val: unknown): val is Element => {
- return isObject(val) && !!val.tagName
+ // 检查值是对象且有 tagName 属性
+ return isObject(val) && !!val.tagName;
}
-export const isServer = typeof window === 'undefined'
+/**
+ * @description: 是否为服务器端
+ * @type {boolean} - 如果在服务器端环境(window 对象不存在),值为 true;否则为 false
+ */
+export const isServer = typeof window === 'undefined';
-// 是否为图片节点
+/**
+ * @description: 是否为图片节点
+ * @param {Element} o - 待判断的 DOM 元素
+ * @returns {boolean} - 如果是图片元素,返回 true;否则返回 false
+ */
export function isImageDom(o: Element) {
- return o && ['IMAGE', 'IMG'].includes(o.tagName)
+ // 检查元素存在且标签名是 IMAGE 或 IMG
+ return o && ['IMAGE', 'IMG'].includes(o.tagName);
}
+/**
+ * @description: 是否为 null
+ * @param {unknown} val - 待判断的值
+ * @returns {val is null} - 如果值为 null,返回 true;否则返回 false
+ */
export function isNull(val: unknown): val is null {
- return val === null
+ // 检查值是否等于 null
+ return val === null;
}
+/**
+ * @description: 是否同时为 null 和 undefined(此逻辑有误,实际不可能同时成立)
+ * @param {unknown} val - 待判断的值
+ * @returns {val is null | undefined} - 如果值同时为 null 和 undefined,返回 true;否则返回 false
+ */
export function isNullAndUnDef(val: unknown): val is null | undefined {
- return isUnDef(val) && isNull(val)
+ // 调用 isUnDef 和 isNull 函数判断
+ return isUnDef(val) && isNull(val);
}
+/**
+ * @description: 是否为 null 或 undefined
+ * @param {unknown} val - 待判断的值
+ * @returns {val is null | undefined} - 如果值为 null 或 undefined,返回 true;否则返回 false
+ */
export function isNullOrUnDef(val: unknown): val is null | undefined {
- return isUnDef(val) || isNull(val)
+ // 调用 isUnDef 和 isNull 函数取或判断
+ return isUnDef(val) || isNull(val);
}
diff --git a/client/src/utils/util.ts b/client/src/utils/util.ts
index 3714383..5a739fb 100644
--- a/client/src/utils/util.ts
+++ b/client/src/utils/util.ts
@@ -1,37 +1,55 @@
+// 从 './is' 文件中导入 isArray 函数,用于判断一个值是否为数组
import { isArray } from './is'
+// 定义一个函数 formatValue,用于格式化传入的值
export function formatValue(callValue: any) {
- // 如果当前值为数组,使用 / 拼接(根据需求自定义)
- if (isArray(callValue)) return callValue.length ? callValue.join(' / ') : '--'
- return callValue ?? '--'
+ // 如果传入的值是数组,使用 '/' 拼接数组元素(如果数组有元素),否则返回 '--'
+ if (isArray(callValue)) return callValue.length? callValue.join(' / ') : '--'
+ // 如果传入的值不是数组,直接返回该值,如果值为 null 或 undefined,则返回 '--'
+ return callValue?? '--'
}
+// 定义一个函数 handleRowAccordingToProp,根据属性路径处理行数据
export function handleRowAccordingToProp(
- row: { [key: string]: any },
- prop: string
+ row: { [key: string]: any }, // 表示一个对象,键为字符串类型,值为任意类型
+ prop: string // 表示属性路径字符串
) {
- if (!prop.includes('.')) return row[prop] ?? '--'
- prop.split('.').forEach(item => (row = row[item] ?? '--'))
+ // 如果属性路径中不包含 '.',直接返回行数据中该属性的值,如果值为 null 或 undefined,则返回 '--'
+ if (!prop.includes('.')) return row[prop]?? '--'
+ // 如果属性路径中包含 '.',按 '.' 分割属性路径,依次获取嵌套对象的属性值
+ prop.split('.').forEach(item => (row = row[item]?? '--'))
+ // 返回最终获取到的值
return row
}
+// 定义一个函数 handleProp,处理属性路径字符串
export function handleProp(prop: string) {
+ // 按 '.' 分割属性路径字符串,得到一个数组
const propArr = prop.split('.')
+ // 如果分割后的数组长度为 1,说明属性路径没有嵌套,直接返回原属性路径字符串
if (propArr.length == 1) return prop
+ // 如果数组长度大于 1,返回属性路径字符串中最后一个部分
return propArr[propArr.length - 1]
}
+// 定义一个函数 filterEnum,根据枚举数据过滤并格式化值
export function filterEnum(
- callValue: any,
- enumData: any[] | undefined,
- fieldNames?: { label: string; value: string },
- type?: 'tag'
+ callValue: any, // 要过滤的值
+ enumData: any[] | undefined, // 枚举数据数组或 undefined
+ fieldNames?: { label: string; value: string }, // 可选的字段名称配置对象,默认为 { label: 'label', value: 'value' }
+ type?: 'tag' // 可选的类型,默认为 undefined
): string {
- const value = fieldNames?.value ?? 'value'
- const label = fieldNames?.label ?? 'label'
+ // 获取字段名称配置中的 'value' 字段名,如果未提供则默认为 'value'
+ const value = fieldNames?.value?? 'value'
+ // 获取字段名称配置中的 'label' 字段名,如果未提供则默认为 'label'
+ const label = fieldNames?.label?? 'label'
+ // 定义一个空对象,用于存储过滤后的数据
let filterData: { [key: string]: any } = {}
+ // 如果枚举数据是一个数组,查找与传入值匹配的枚举项
if (Array.isArray(enumData))
filterData = enumData.find((item: any) => item[value] === callValue)
- if (type == 'tag') return filterData?.tagType ? filterData.tagType : ''
- return filterData ? filterData[label] : '--'
+ // 如果类型为 'tag',返回过滤数据中的 'tagType' 字段值,如果不存在则返回空字符串
+ if (type == 'tag') return filterData?.tagType? filterData.tagType : ''
+ // 返回过滤数据中的 'label' 字段值,如果过滤数据不存在则返回 '--'
+ return filterData? filterData[label] : '--'
}
diff --git a/client/src/views/base/activity/index.vue b/client/src/views/base/activity/index.vue
index 37386f2..d7f7279 100644
--- a/client/src/views/base/activity/index.vue
+++ b/client/src/views/base/activity/index.vue
@@ -1,9 +1,18 @@
+
+
activity
-
+
-
+
diff --git a/client/src/views/base/index.vue b/client/src/views/base/index.vue
index cf9ed48..ad13d94 100644
--- a/client/src/views/base/index.vue
+++ b/client/src/views/base/index.vue
@@ -1,7 +1,22 @@
+
-
+
-
+
diff --git a/client/src/views/base/live/flat/dialog/bed.vue b/client/src/views/base/live/flat/dialog/bed.vue
index f1736ed..d3f10e0 100644
--- a/client/src/views/base/live/flat/dialog/bed.vue
+++ b/client/src/views/base/live/flat/dialog/bed.vue
@@ -1,66 +1,88 @@
+
+ v-model="dialogVisible"
+
+ :title="drawerProps.title"
+
+ destroy-on-close
>
-
-
-
-
-
+
+
+
+ :model="formData"
+ class="login-form"
+
+ ref="ruleFormRef"
+
+ :rules="rules"
+ label-width="120px"
+ >
+
-
-
-
+
+
+
+
+
+
+
+ rowData:
diff --git a/client/src/views/base/live/index.vue b/client/src/views/base/live/index.vue
index cf9ed48..52d2b92 100644
--- a/client/src/views/base/live/index.vue
+++ b/client/src/views/base/live/index.vue
@@ -1,7 +1,17 @@
+
-
+
-
+
diff --git a/client/src/views/base/live/room/index.vue b/client/src/views/base/live/room/index.vue
index 3386654..b89fae0 100644
--- a/client/src/views/base/live/room/index.vue
+++ b/client/src/views/base/live/room/index.vue
@@ -1,118 +1,137 @@
+
+
+ title="用户列表"
+ :columns="columns"
+ :requestApi="getTableList"
+ :initParam="initParam"
+ :dataCallback="dataCallback"
>
-
-
-
-
- 新增
-
+
+
+
+
+
+
+ 新增
+
+
-
-
-
- 查看
-
-
- 编辑
-
-
-
- 删除
-
-
+
+
+
+
+ 查看
+
+
+
+ 编辑
+
+
+
+ @confirm="deleteData(scope)"
+ confirm-button-type="danger"
+ >
+
+
+ 删除
+
+
+
diff --git a/client/src/views/base/live/room/roomDialog/index.vue b/client/src/views/base/live/room/roomDialog/index.vue
index e4772ff..b5d73b0 100644
--- a/client/src/views/base/live/room/roomDialog/index.vue
+++ b/client/src/views/base/live/room/roomDialog/index.vue
@@ -1,107 +1,136 @@
+
+
+ v-model="dialogVisible"
+ :title="drawerProps.title"
+ destroy-on-close
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ class="login-form"
+ ref="ruleFormRef"
+ :rules="rules"
+ label-width="120px"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/base/sale/origin/index.vue b/client/src/views/base/sale/origin/index.vue
index fae4f59..31d830f 100644
--- a/client/src/views/base/sale/origin/index.vue
+++ b/client/src/views/base/sale/origin/index.vue
@@ -1,5 +1,9 @@
+
+
-
+
+
+
- 新增
+ 新增
+
-
+
+
查看
+
编辑
+
+
删除
+
-
+
diff --git a/client/src/views/base/sale/origin/originDialog/index.vue b/client/src/views/base/sale/origin/originDialog/index.vue
index 97998e8..d97022a 100644
--- a/client/src/views/base/sale/origin/originDialog/index.vue
+++ b/client/src/views/base/sale/origin/originDialog/index.vue
@@ -1,138 +1,172 @@
+
+ :title="drawerProps.title"
+ destroy-on-close
>
-
-
-
-
-
+
+
+
+ class="login-form"
+ ref="ruleFormRef"
+ :rules="rules"
+ label-width="120px"
+ >
+
-
-
-
+
+
+
+
+
+
+
+
diff --git a/client/src/views/charge/audit/auditDialog/index.vue b/client/src/views/charge/audit/auditDialog/index.vue
index f382ae5..8c97ac7 100644
--- a/client/src/views/charge/audit/auditDialog/index.vue
+++ b/client/src/views/charge/audit/auditDialog/index.vue
@@ -1,152 +1,194 @@
+
+ :title="drawerProps.title"
+ destroy-on-close
>
-
-
-
-
-
-
-
+
+
+
+ class="login-form"
+ ref="ruleFormRef"
+ :rules="rules"
+ label-width="120px"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
diff --git a/client/src/views/charge/index.vue b/client/src/views/charge/index.vue
index cf9ed48..b6740fe 100644
--- a/client/src/views/charge/index.vue
+++ b/client/src/views/charge/index.vue
@@ -1,7 +1,27 @@
+
-
+
-
+
diff --git a/client/src/views/charge/prestore/index.vue b/client/src/views/charge/prestore/index.vue
index 07f5d10..808d3fd 100644
--- a/client/src/views/charge/prestore/index.vue
+++ b/client/src/views/charge/prestore/index.vue
@@ -1,83 +1,93 @@
+
+
+ title="用户列表"
+ :columns="columns"
+ :requestApi="getTableList"
+ :initParam="initParam"
+ :dataCallback="dataCallback"
>
-
-
-
-
-
- 账户充值
-
-
+
+
+
+
+ link
+ :icon="CreditCard"
+ @click="openDrawer('账户充值',scope)"
+ >
+ 账户充值
+
+
+
-
+
diff --git a/client/src/views/charge/prestore/prestoreDialog/index.vue b/client/src/views/charge/prestore/prestoreDialog/index.vue
index 03f28ba..7ba812b 100644
--- a/client/src/views/charge/prestore/prestoreDialog/index.vue
+++ b/client/src/views/charge/prestore/prestoreDialog/index.vue
@@ -1,133 +1,166 @@
+
+ v-model="dialogVisible"
+ :title="drawerProps.title"
+ destroy-on-close
>
-
-
-
-
-
+
+
+
+ class="login-form"
+ ref="ruleFormRef"
+ :rules="rules"
+ label-width="120px"
+ >
+
-
-
-
+
+
+
+
+
+
+
+
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeException.java b/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeException.java
index 1d25322..979cee1 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeException.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeException.java
@@ -1,22 +1,36 @@
+// 声明该类所在的包路径,通常用于组织和管理代码结构
package com.ew.gerocomium.common.config.exception;
+// 导入自定义的异常枚举类,该类可能定义了各种异常类型的代码和消息
import com.ew.gerocomium.common.constant.ExceptionEnum;
+// 导入 Lombok 库的 @Getter 注解,用于自动生成类中属性的 getter 方法
import lombok.Getter;
+// 使用 @Getter 注解,让 Lombok 自动为类中的属性生成 getter 方法
@Getter
+// 定义一个自定义的运行时异常类,继承自 RuntimeException,用于处理业务逻辑中的异常情况
public class BusinessRuntimeException extends RuntimeException {
+ // 定义一个静态常量 serialVersionUID,用于序列化版本控制,确保在反序列化时版本的一致性
private static final long serialVersionUID = -7382189364622337034L;
+ // 定义一个私有成员变量 code,用于存储异常的错误码
private final Integer code;
+ // 定义一个私有成员变量 msg,用于存储异常的错误消息
private final String msg;
+ // 定义一个构造函数,接收错误码和错误消息作为参数
public BusinessRuntimeException(Integer code, String msg) {
+ // 调用父类 RuntimeException 的构造函数,将错误消息传递给父类
super(msg);
+ // 将传入的错误码赋值给当前类的 code 成员变量
this.code = code;
+ // 将传入的错误消息赋值给当前类的 msg 成员变量
this.msg = msg;
}
+ // 定义另一个构造函数,接收一个 ExceptionEnum 类型的参数
public BusinessRuntimeException(ExceptionEnum type) {
+ // 调用上面定义的构造函数,将 ExceptionEnum 类型参数的 code 和 msg 传递进去
this(type.getCode(), type.getMsg());
}
-}
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeExceptionAdvice.java b/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeExceptionAdvice.java
index 27995d0..9f31319 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeExceptionAdvice.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/exception/BusinessRuntimeExceptionAdvice.java
@@ -1,80 +1,143 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.exception;
+// 导入自定义的代码枚举类,可能包含各种业务状态码
import com.ew.gerocomium.common.constant.CodeEnum;
+// 导入自定义的异常枚举类,包含不同类型异常的信息
import com.ew.gerocomium.common.constant.ExceptionEnum;
+// 导入自定义的响应工具类,用于构建响应结果
import com.ew.gerocomium.common.util.ResponseUtil;
+// 导入自定义的基础结果类,用于封装接口返回数据
import com.ew.gerocomium.dao.base.Result;
+// 导入 Lombok 提供的日志注解,可自动为类添加日志记录功能
import lombok.extern.slf4j.Slf4j;
+// 导入 Spring Security 框架中权限访问被拒绝的异常类
import org.springframework.security.access.AccessDeniedException;
+// 导入 Spring Security 框架中凭证错误的异常类
import org.springframework.security.authentication.BadCredentialsException;
+// 导入 Spring Security 框架中内部认证服务异常类
import org.springframework.security.authentication.InternalAuthenticationServiceException;
+// 导入 Spring Web 模块中 HTTP 请求方法不支持的异常类
import org.springframework.web.HttpRequestMethodNotSupportedException;
+// 导入 Spring Web 模块中缺少请求参数的异常类
import org.springframework.web.bind.MissingServletRequestParameterException;
+// 导入 Spring 框架的异常处理注解,用于捕获和处理控制器层抛出的异常
import org.springframework.web.bind.annotation.ExceptionHandler;
+// 导入 Spring 框架的全局异常处理注解,结合 @ExceptionHandler 捕获全局异常
import org.springframework.web.bind.annotation.RestControllerAdvice;
+// 导入 Spring Web 模块中方法参数类型不匹配的异常类
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+// 导入 Spring MVC 模块中未找到处理器的异常类
import org.springframework.web.servlet.NoHandlerFoundException;
-
+// 导入 JavaMail 中的消息传递异常类
import javax.mail.MessagingException;
+// 导入 JavaMail 中邮件发送失败的异常类
import javax.mail.SendFailedException;
+// 导入 Servlet 规范中的 HttpServletRequest 类,用于获取 HTTP 请求信息
import javax.servlet.http.HttpServletRequest;
+// 导入 Servlet 规范中的 HttpServletResponse 类,用于发送 HTTP 响应信息
import javax.servlet.http.HttpServletResponse;
+// 导入 Java 中的 Objects 工具类,提供一些对象操作方法
import java.util.Objects;
+// 使用 Lombok 的 Slf4j 注解,自动为该类添加日志记录功能
+
+// 标记为日志记录类,使用Slf4j日志框架
@Slf4j
+// 表示这是一个全局的异常处理类,用于处理控制器层抛出的异常
@RestControllerAdvice
public class BusinessRuntimeExceptionAdvice {
+ /**
+ * 异常处理方法,处理所有类型的异常
+ *
+ * @param ex 捕获到的异常对象
+ * @param response HTTP响应对象,用于返回结果
+ * @param request HTTP请求对象
+ */
@ExceptionHandler({Exception.class})
public void exceptionHandler(Exception ex, HttpServletResponse response, HttpServletRequest request) {
+ // 异常类型描述
String errorType;
+ // 异常对应的错误码
Integer errorCode;
+ // 异常对应的错误消息
String errorMsg;
+ // 判断是否为自定义的业务异常
if (ex instanceof BusinessRuntimeException) {
errorType = "业务异常";
- // AssertUtils断言抛出的自定义业务异常
+ // 获取自定义业务异常的错误码
errorCode = ((BusinessRuntimeException) ex).getCode();
+ // 获取自定义业务异常的错误消息
errorMsg = ((BusinessRuntimeException) ex).getMsg();
- } else if (ex instanceof NoHandlerFoundException) {
+ }
+ // 判断是否为请求的资源不存在的异常
+ else if (ex instanceof NoHandlerFoundException) {
errorType = "无访问资源";
+ // 获取资源不存在对应的错误码
errorCode = ExceptionEnum.NOT_EXIST.getCode();
+ // 获取资源不存在对应的错误消息
errorMsg = ExceptionEnum.NOT_EXIST.getMsg();
- } else if (ex instanceof HttpRequestMethodNotSupportedException) {
+ }
+ // 判断是否为请求方式不支持的异常
+ else if (ex instanceof HttpRequestMethodNotSupportedException) {
errorType = "请求方式错误";
+ // 获取请求方式错误对应的错误码
errorCode = ExceptionEnum.METHOD_ERROR.getCode();
+ // 获取请求方式错误对应的错误消息
errorMsg = ExceptionEnum.METHOD_ERROR.getMsg();
- // MissingServletRequestParameterException -> 未传必传参数
- // MethodArgumentTypeMismatchException -> 参数类型错误
- } else if (ex instanceof MissingServletRequestParameterException || ex instanceof MethodArgumentTypeMismatchException) {
+ }
+ // 判断是否为请求参数缺失或参数类型不匹配的异常
+ else if (ex instanceof MissingServletRequestParameterException || ex instanceof MethodArgumentTypeMismatchException) {
errorType = "请求参数异常";
+ // 获取请求参数异常对应的错误码
errorCode = ExceptionEnum.PARAM_ERROR.getCode();
+ // 获取请求参数异常对应的错误消息
errorMsg = ExceptionEnum.PARAM_ERROR.getMsg();
- // InternalAuthenticationServiceException -> 账号未注册
- // BadCredentialsException -> 密码错误
- } else if (ex instanceof InternalAuthenticationServiceException || ex instanceof BadCredentialsException) {
+ }
+ // 判断是否为认证失败的异常(账号未注册或密码错误)
+ else if (ex instanceof InternalAuthenticationServiceException || ex instanceof BadCredentialsException) {
errorType = "认证失败";
+ // 获取认证失败对应的错误码
errorCode = ExceptionEnum.CERTIFICATION_ERROR.getCode();
+ // 获取认证失败对应的错误消息
errorMsg = ExceptionEnum.CERTIFICATION_ERROR.getMsg();
- } else if (ex instanceof AccessDeniedException) {
+ }
+ // 判断是否为权限不足的异常
+ else if (ex instanceof AccessDeniedException) {
errorType = "权限不足";
+ // 获取权限不足对应的错误码
errorCode = ExceptionEnum.AUTH_ERROR.getCode();
+ // 获取权限不足对应的错误消息
errorMsg = ExceptionEnum.AUTH_ERROR.getMsg();
- } else {
+ }
+ // 其他类型的异常
+ else {
errorType = "其他异常";
+ // 获取通用错误码
errorCode = CodeEnum.ERROR.getCode();
+ // 获取异常的消息
String message = ex.getMessage();
+ // 标记是否匹配到自定义的异常消息
boolean checkMsg = false;
+ // 遍历所有自定义异常枚举
for (ExceptionEnum exceptionEnum : ExceptionEnum.values()) {
+ // 判断异常消息是否与自定义异常枚举中的消息匹配
if (Objects.equals(exceptionEnum.getMsg(), message)) {
checkMsg = true;
break;
}
}
+ // 如果匹配到自定义异常消息,则使用该消息,否则使用系统错误消息
errorMsg = checkMsg ? message : ExceptionEnum.SYS_ERROR.getMsg();
}
+ // 判断日志级别是否为INFO
if (log.isInfoEnabled()) {
+ // 记录异常类型
log.info(errorType);
+ // 打印异常堆栈信息
ex.printStackTrace();
}
+ // 使用工具类将错误结果输出到响应中
ResponseUtil.resultOut(response, Result.error(errorCode, errorMsg));
}
-}
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/file/FileTempPath.java b/server/src/main/java/com/ew/gerocomium/common/config/file/FileTempPath.java
index 3ed73b8..e54630e 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/file/FileTempPath.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/file/FileTempPath.java
@@ -1,19 +1,35 @@
+// 声明该类所在的包,通常用于组织和管理代码结构
package com.ew.gerocomium.common.config.file;
+// 导入 Hutool 工具包中的日期格式常量类
import cn.hutool.core.date.DatePattern;
+// 导入 Hutool 工具包中的日期工具类
import cn.hutool.core.date.DateUtil;
+// 导入 Hutool 工具包中的文件操作工具类
import cn.hutool.core.io.FileUtil;
+// 导入 Hutool 工具包中的字符常量类
import cn.hutool.core.text.CharPool;
+// 导入 Hutool 工具包中的字符串常量类
import cn.hutool.core.text.StrPool;
+// 导入 Hutool 工具包中的 ID 生成工具类
import cn.hutool.core.util.IdUtil;
+// 导入 Hutool 工具包中的字符串操作工具类
import cn.hutool.core.util.StrUtil;
+// 导入 Hutool 工具包中的操作系统信息类
import cn.hutool.system.OsInfo;
+// 导入自定义的常量类
import com.ew.gerocomium.common.constant.Constant;
+// 导入自定义的异常枚举类
import com.ew.gerocomium.common.constant.ExceptionEnum;
+// 导入自定义的断言工具类
import com.ew.gerocomium.common.util.AssertUtil;
+// 导入 Lombok 提供的全参构造函数注解
import lombok.AllArgsConstructor;
+// 导入 Lombok 提供的自动处理异常注解
import lombok.SneakyThrows;
+// 导入 Spring 框架的配置类注解
import org.springframework.context.annotation.Configuration;
+// 导入 Spring 框架处理文件上传的类
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@@ -22,18 +38,22 @@ import java.time.LocalDateTime;
/**
* 文件信息
*/
+// 表明这是一个 Spring 配置类
@Configuration
+// 让 Lombok 自动生成全参构造函数
@AllArgsConstructor
public class FileTempPath {
+ // 注入文件上传配置属性类的实例
private final FileUploadConfigPropertity fileUploadConfigPropertity;
/**
- * 获取module存储路径
+ * 获取 module 存储路径
*
* @param module 模块名称
* @return 根路径/module/当天日期/
*/
public String uploadPath(String module) {
+ // 拼接上传路径,格式为根路径 + 模块名 + 当天日期
return getRootPath() + File.separator
+ module + File.separator
+ DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN)
@@ -46,6 +66,7 @@ public class FileTempPath {
* @return 根路径/download/
*/
public String downloadPath() {
+ // 拼接下载路径,格式为根路径 + /download/
return getRootPath() + File.separator + Constant.STR_DOWNLOAD + File.separator;
}
@@ -55,6 +76,7 @@ public class FileTempPath {
* @return 根路径/upload/img/
*/
public String imgSrcPath() {
+ // 拼接图片存储路径,格式为根路径 + /upload/img/
return getRootPath() + File.separator + Constant.STR_UPLOAD_IMG + File.separator;
}
@@ -64,6 +86,7 @@ public class FileTempPath {
* @return 根路径/upload/video/
*/
public String videoSrcPath() {
+ // 拼接视频存储路径,格式为根路径 + /upload/video/
return getRootPath() + File.separator + Constant.STR_UPLOAD_VIDEO + File.separator;
}
@@ -73,6 +96,7 @@ public class FileTempPath {
* @return 根路径/upload/file/
*/
public String filePath() {
+ // 拼接附件存储路径,格式为根路径 + /upload/file/
return getRootPath() + File.separator + Constant.STR_UPLOAD_FILE + File.separator;
}
@@ -82,12 +106,21 @@ public class FileTempPath {
* @return 根路径
*/
public String getRootPath() {
+ // 创建操作系统信息对象
OsInfo osInfo = new OsInfo();
+ // 判断操作系统是否为 Windows
if (osInfo.isWindows()) {
+ // 如果是 Windows 系统,返回 Windows 对应的根路径
return fileUploadConfigPropertity.getWindows();
- } else if (osInfo.isMacOsX()) {
+ }
+ // 判断操作系统是否为 Mac OS X
+ else if (osInfo.isMacOsX()) {
+ // 如果是 Mac OS X 系统,返回 Mac OS X 对应的根路径
return fileUploadConfigPropertity.getMacos();
- } else {
+ }
+ // 其他操作系统,默认为 Linux
+ else {
+ // 返回 Linux 对应的根路径
return fileUploadConfigPropertity.getLinux();
}
}
@@ -95,63 +128,70 @@ public class FileTempPath {
/**
* 文件上传 保存
*
- * @param file
- * @param module
- * @param suff
- * @return
+ * @param file 上传的文件
+ * @param module 模块名称
+ * @param suff 允许的文件后缀数组
+ * @return 保存后的文件对象
*/
@SneakyThrows
public File saveFile(MultipartFile file, String module, String[] suff) {
+ // 检查文件是否为空或文件名是否为空
if (file == null || StrUtil.isBlank(file.getOriginalFilename())) {
return null;
}
+ // 获取文件后缀,格式为 .xxx
String suffix = StrPool.DOT + FileUtil.getSuffix(file.getOriginalFilename());
- // 文件后缀校验
+ // 校验文件后缀是否不在允许的后缀数组中,如果不在则抛出异常
AssertUtil.isTrue(!StrUtil.equalsAnyIgnoreCase(suffix, suff), ExceptionEnum.UPLOAD_SUFFIX_ERROR);
- // 文件转存
- // 模块路径/雪花算法ID_上传文件名称
+ // 生成新的文件名,格式为雪花算法 ID + _ + 原文件名
String fileName = IdUtil.getSnowflakeNextIdStr() + "_" + file.getOriginalFilename();
+ // 创建文件对象,路径为模块上传路径 + 新文件名
File files = FileUtil.file(uploadPath(module) + fileName);
+ // 创建文件的父目录
FileUtil.mkParentDirs(files);
-
+ // 将上传的文件转存到指定位置
file.transferTo(files);
return files;
}
-
/**
* 文件上传 备查信息
*
* @param file http MultipartFile 文件流
* @param suff 后缀 包含此类型的后缀不允许上传 如:.exe .sh
- * @return
+ * @return 保存后的文件对象
*/
@SneakyThrows
public File saveInformationFile(MultipartFile file, String[] suff) {
+ // 调用 saveFile 方法,指定模块名为 "information"
return saveFile(file, "information", suff);
}
@SneakyThrows
public File saveCommFile(MultipartFile file, String module) {
+ // 调用 saveFile 方法,不限制文件后缀
return saveFile(file, module, null);
}
/**
- * 下载路径处理 前提是放开uri
+ * 下载路径处理 前提是放开 uri
*
- * @param file
- * @return
+ * @param file 要下载的文件
+ * @return 处理后的下载 URL
*/
public String downloadUrl(File file) {
+ // 获取文件的绝对路径
String filePath = file.getAbsolutePath();
+ // 替换根路径为空字符串,并将反斜杠替换为正斜杠
String downloadUrl = filePath
.replace(getRootPath(), "")
.replace(StrPool.BACKSLASH, StrPool.SLASH);
+ // 在路径前添加斜杠
return CharPool.SLASH + downloadUrl;
}
public String downloadUrl(String path) {
+ // 调用 downloadUrl 方法,将文件路径转换为文件对象
return downloadUrl(FileUtil.file(path));
}
-
}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/file/FileUploadConfigPropertity.java b/server/src/main/java/com/ew/gerocomium/common/config/file/FileUploadConfigPropertity.java
index 61c9568..9eb680d 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/file/FileUploadConfigPropertity.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/file/FileUploadConfigPropertity.java
@@ -1,19 +1,32 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.file;
+// 导入 Lombok 库的 @Data 注解,该注解会自动生成 getter、setter、toString、equals 和 hashCode 等方法
import lombok.Data;
+// 导入 Spring Boot 提供的 @ConfigurationProperties 注解,用于将配置文件中的属性映射到 Java 类中
import org.springframework.boot.context.properties.ConfigurationProperties;
+// 导入 Spring 框架的 @Configuration 注解,表明这是一个配置类,会被 Spring 容器管理
import org.springframework.context.annotation.Configuration;
/**
* 文件上传路径配置
*/
+// 使用 Lombok 的 @Data 注解,自动生成类的 getter、setter、toString 等方法
@Data
+// 使用 Spring 的 @Configuration 注解,将该类标记为配置类
@Configuration
+// 使用 @ConfigurationProperties 注解,指定从配置文件中读取以 "filesave" 为前缀的属性
@ConfigurationProperties(prefix = "filesave")
+// 定义一个配置属性类,用于存储文件上传路径的相关配置
public class FileUploadConfigPropertity {
+ // 定义一个字符串类型的属性,用于存储 Windows 系统下的文件上传路径
private String windows;
+ // 定义一个字符串类型的属性,用于存储 Mac OS 系统下的文件上传路径
private String macos;
+ // 定义一个字符串类型的属性,用于存储 Linux 系统下的文件上传路径
private String linux;
+ // 定义一个字符串类型的属性,用于存储上传文件的头部路径配置
private String uploadHead;
+ // 定义一个字符串类型的属性,用于存储本地文件的头部路径配置
private String localHead;
-}
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/mybatisplus/MetaObjectHandlerImpl.java b/server/src/main/java/com/ew/gerocomium/common/config/mybatisplus/MetaObjectHandlerImpl.java
index f211a64..95e2bbf 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/mybatisplus/MetaObjectHandlerImpl.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/mybatisplus/MetaObjectHandlerImpl.java
@@ -1,86 +1,119 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.mybatisplus;
+// 导入 MyBatis-Plus 核心的元对象处理器接口
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+// 导入自定义的权限验证处理类
import com.ew.gerocomium.common.config.security.handler.AuthorityAssert;
+// 导入自定义的登录用户信息视图对象类
import com.ew.gerocomium.dao.vo.LoginUserVo;
+// 导入 Lombok 提供的日志注解,可自动为类添加日志记录功能
import lombok.extern.slf4j.Slf4j;
+// 导入 MyBatis 的元对象类,用于操作对象属性
import org.apache.ibatis.reflection.MetaObject;
+// 导入 Spring 框架的组件注解,将该类标记为 Spring 组件,以便 Spring 自动扫描和管理
import org.springframework.stereotype.Component;
+// 导入 Java 的资源注入注解
import javax.annotation.Resource;
-import java.util.Arrays;
+// 导入 Java 的日期类,用于表示时间
import java.util.Date;
+// 导入 Java 的数组工具类,用于处理数组操作
+import java.util.Arrays;
+// 导入 Java 的列表接口,用于存储元素
import java.util.List;
+// 导入 Java 的流处理工具类,用于集合元素的转换和处理
import java.util.stream.Collectors;
/**
* mybatis对象处理器
**/
+// 使用 Lombok 的 Slf4j 注解,自动为该类添加日志记录功能
@Slf4j
+// 使用 Spring 的 Component 注解,将该类标记为组件,让 Spring 框架管理
@Component
+// 定义一个元对象处理器实现类,实现 MyBatis-Plus 的 MetaObjectHandler 接口
public class MetaObjectHandlerImpl implements MetaObjectHandler {
+
+ // 使用 Spring 的 Resource 注解注入 AuthorityAssert 类的实例
@Resource
private AuthorityAssert authorityAssert;
+ // 定义常量,表示创建记录的用户 ID 字段名
private static final String CREATEID = "createId";
+ // 定义常量,表示创建记录的时间字段名
private static final String CREATETIME = "createTime";
+ // 定义常量,表示更新记录的用户 ID 字段名
private static final String UPDATEID = "updateId";
+ // 定义常量,表示更新记录的时间字段名
private static final String UPDATETIME = "updateTime";
/**
* 自动填充创建时间修改时间
*
- * @param metaObject
+ * @param metaObject 元对象,包含要操作的对象信息
*/
@Override
public void insertFill(MetaObject metaObject) {
- // 获取登录信息
+ // 调用 authorityAssert 的方法获取当前登录用户的信息
LoginUserVo loginUserInfo = authorityAssert.getLoginUserInfo();
- //获取所有的字段名称
+ // 获取元对象中所有可获取的字段名称,并存储在列表中
List list = Arrays.stream(metaObject.getGetterNames()).collect(Collectors.toList());
+ // 判断列表中是否包含创建用户 ID 字段
if (list.contains(CREATEID)) {
- // 有登录信息
+ // 如果登录用户信息不为空
if (loginUserInfo != null) {
+ // 设置创建用户 ID 字段的值为当前登录用户的 ID
this.setFieldValByName(CREATEID, loginUserInfo.getId(), metaObject);
- // 无登录信息
} else {
+ // 若登录用户信息为空,设置创建用户 ID 字段的值为默认值 1L
this.setFieldValByName(CREATEID, 1L, metaObject);
}
}
+ // 判断列表中是否包含创建时间字段
if (list.contains(CREATETIME)) {
+ // 设置创建时间字段的值为当前时间
this.setFieldValByName(CREATETIME, new Date(), metaObject);
}
+ // 判断列表中是否包含更新用户 ID 字段
if (list.contains(UPDATEID)) {
- // 有登录信息
+ // 如果登录用户信息不为空
if (loginUserInfo != null) {
+ // 设置更新用户 ID 字段的值为当前登录用户的 ID
this.setFieldValByName(UPDATEID, loginUserInfo.getId(), metaObject);
- // 无登录信息
} else {
+ // 若登录用户信息为空,设置更新用户 ID 字段的值为默认值 1L
this.setFieldValByName(UPDATEID, 1L, metaObject);
}
}
+ // 判断列表中是否包含更新时间字段
if (list.contains(UPDATETIME)) {
+ // 设置更新时间字段的值为当前时间
this.setFieldValByName(UPDATETIME, new Date(), metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
- // 获取登录信息
+ // 调用 authorityAssert 的方法获取当前登录用户的信息
LoginUserVo loginUserInfo = authorityAssert.getLoginUserInfo();
- //获取所有的字段名称
+ // 获取元对象中所有可获取的字段名称,并存储在列表中
List list = Arrays.stream(metaObject.getGetterNames()).collect(Collectors.toList());
+ // 判断列表中是否包含更新用户 ID 字段
if (list.contains(UPDATEID)) {
- // 有登录信息
+ // 如果登录用户信息不为空
if (loginUserInfo != null) {
+ // 设置更新用户 ID 字段的值为当前登录用户的 ID
this.setFieldValByName(UPDATEID, loginUserInfo.getId(), metaObject);
- // 无登录信息
} else {
+ // 若登录用户信息为空,设置更新用户 ID 字段的值为默认值 1L
this.setFieldValByName(UPDATEID, 1L, metaObject);
}
}
+ // 判断列表中是否包含更新时间字段
if (list.contains(UPDATETIME)) {
+ // 设置更新时间字段的值为当前时间
this.setFieldValByName(UPDATETIME, new Date(), metaObject);
}
}
-}
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzConfig.java b/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzConfig.java
index eb9dc75..b2191f9 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzConfig.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzConfig.java
@@ -1,33 +1,57 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.quartz;
-import org.quartz.*;
+// 导入 Quartz 框架的 JobDetail 接口,用于定义作业的详细信息
+import org.quartz.JobDetail;
+// 导入 Quartz 框架的 Trigger 接口,用于定义作业的触发条件
+import org.quartz.Trigger;
+// 导入 Quartz 框架的 JobBuilder 类,用于构建 JobDetail 对象
+import org.quartz.JobBuilder;
+// 导入 Quartz 框架的 TriggerBuilder 类,用于构建 Trigger 对象
+import org.quartz.TriggerBuilder;
+// 导入 Quartz 框架的 CronScheduleBuilder 类,用于构建基于 Cron 表达式的调度器
+import org.quartz.CronScheduleBuilder;
+// 导入 Quartz 框架的 JobDataMap 类,用于存储作业的数据
+import org.quartz.JobDataMap;
+// 导入 Spring 框架的配置类注解,表明这是一个配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+// 导入 Spring 框架中 Quartz 作业的基类
import org.springframework.scheduling.quartz.QuartzJobBean;
+// 使用 Spring 的 Configuration 注解,将该类标记为配置类
@Configuration
-public class QuartzConfig {
- // 预定过期
- @Bean
- public JobDetail reserveExpireJobDetail() {
- return newJobDetail(QuartzJob.ReserveExpireJob.class, "reserveExpireJobDetail");
- }
+ public class QuartzConfig {
- @Bean
- public Trigger reserveExpireTrigger() {
- return newTrigger(reserveExpireJobDetail(), "reserveExpireJobTrigger", "0 0 0 ? * *");
- }
+ // 定义一个名为 reserveExpireJobDetail 的 Bean,用于配置预定过期的作业详情
+ @Bean
+ public JobDetail reserveExpireJobDetail() {
+ // 调用自定义的 newJobDetail 方法创建并返回一个 JobDetail 对象
+ return newJobDetail(QuartzJob.ReserveExpireJob.class, "reserveExpireJobDetail");
+ }
- // 合同到期
- @Bean
- public JobDetail contractExpireJobDetail() {
- return newJobDetail(QuartzJob.ContractExpireJob.class, "contractExpireJobDetail");
- }
+ // 定义一个名为 reserveExpireTrigger 的 Bean,用于配置预定过期作业的触发器
+ @Bean
+ public Trigger reserveExpireTrigger() {
+ // 调用自定义的 newTrigger 方法创建并返回一个 Trigger 对象,关联到 reserveExpireJobDetail 作业详情
+ return newTrigger(reserveExpireJobDetail(), "reserveExpireJobTrigger", "0 0 0 ? * *");
+ }
- @Bean
- public Trigger contractExpireTrigger() {
- return newTrigger(contractExpireJobDetail(), "contractExpireJobTrigger", "0 0 8 ? * *");
- }
+ // 定义一个名为 contractExpireJobDetail 的 Bean,用于配置合同到期的作业详情
+ @Bean
+ public JobDetail contractExpireJobDetail() {
+ // 调用自定义的 newJobDetail 方法创建并返回一个 JobDetail 对象
+ return newJobDetail(QuartzJob.ContractExpireJob.class, "contractExpireJobDetail");
+ }
+
+ // 定义一个名为 contractExpireTrigger 的 Bean,用于配置合同到期作业的触发器
+ @Bean
+ public Trigger contractExpireTrigger() {
+ // 调用自定义的 newTrigger 方法创建并返回一个 Trigger 对象,关联到 contractExpireJobDetail 作业详情
+ return newTrigger(contractExpireJobDetail(), "contractExpireJobTrigger", "0 0 8 ? * *");
+ }
+
+ // 通用的作业详情构建方法,参数为作业类和作业详情名称
// 任务封装
private JobDetail newJobDetail(Class extends QuartzJobBean> jobDetailClass, String jobDetailName) {
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzJob.java b/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzJob.java
index 8435b7c..aa1308c 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzJob.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/quartz/QuartzJob.java
@@ -1,37 +1,54 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.quartz;
+// 导入自定义的合同服务接口
import com.ew.gerocomium.service.ContractService;
+// 导入自定义的预定服务接口
import com.ew.gerocomium.service.ReserveService;
+// 导入 Lombok 提供的注解,用于简化异常处理,自动抛出异常
import lombok.SneakyThrows;
+// 导入 Quartz 框架中作业的基类,Spring 整合 Quartz 时使用的作业类需要继承该类
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
+// 导入 Spring 框架的服务注解,将该类标记为服务层组件
import org.springframework.stereotype.Service;
+// 导入 Spring 的资源注入注解
import javax.annotation.Resource;
+// 使用 Spring 的 Service 注解,将该类标记为服务层组件,让 Spring 框架管理
@Service
public class QuartzJob {
+
+ // 使用 Spring 的 Resource 注解注入 ReserveService 接口的实现类实例
@Resource
private ReserveService reserveService;
+ // 使用 Spring 的 Resource 注解注入 ContractService 接口的实现类实例
@Resource
private ContractService contractService;
- // 床位预定过期任务
+ // 定义一个内部类,表示床位预定过期任务,继承自 QuartzJobBean
public class ReserveExpireJob extends QuartzJobBean {
+ // 使用 Lombok 的 @SneakyThrows 注解,自动处理方法内可能抛出的异常
@SneakyThrows
@Override
+ // 重写 QuartzJobBean 的 executeInternal 方法,该方法在作业执行时被调用
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
+ // 调用 ReserveService 中的 reserveExpireJob 方法,执行床位预定过期的业务逻辑
reserveService.reserveExpireJob();
}
}
- // 老人合同到期任务
+ // 定义一个内部类,表示老人合同到期任务,继承自 QuartzJobBean
public class ContractExpireJob extends QuartzJobBean {
+ // 使用 Lombok 的 @SneakyThrows 注解,自动处理方法内可能抛出的异常
@SneakyThrows
@Override
+ // 重写 QuartzJobBean 的 executeInternal 方法,该方法在作业执行时被调用
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
+ // 调用 ContractService 中的 contractExpireJob 方法,执行老人合同到期的业务逻辑
contractService.contractExpireJob();
}
}
-}
+}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/redis/FastJsonRedisSerializer.java b/server/src/main/java/com/ew/gerocomium/common/config/redis/FastJsonRedisSerializer.java
index 5b83078..0f2718a 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/redis/FastJsonRedisSerializer.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/redis/FastJsonRedisSerializer.java
@@ -1,53 +1,93 @@
+// 声明该类所在的包,用于组织和管理代码结构
package com.ew.gerocomium.common.config.redis;
+// 导入阿里巴巴的 FastJSON 库,用于 JSON 处理
import com.alibaba.fastjson.JSON;
+// 导入 FastJSON 库中用于设置序列化特性的类
import com.alibaba.fastjson.serializer.SerializerFeature;
+// 导入 Jackson 库中用于处理 Java 类型的类,在反序列化等操作中会用到
import com.fasterxml.jackson.databind.JavaType;
+// 导入 Jackson 库中用于创建类型工厂的类,通过该工厂创建 JavaType 等类型对象
import com.fasterxml.jackson.databind.type.TypeFactory;
+// 导入 Spring Data Redis 中用于定义 Redis 序列化器的接口
import org.springframework.data.redis.serializer.RedisSerializer;
+// 导入 Spring Data Redis 中序列化异常类,用于处理序列化过程中可能出现的异常情况
import org.springframework.data.redis.serializer.SerializationException;
+// 导入 FastJSON 库中用于解析配置的类
import com.alibaba.fastjson.parser.ParserConfig;
+// 导入 Java 标准库中用于处理字符集的类
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
+ * 该类实现了RedisSerializer接口,用于将Java对象序列化为字节数组,以及将字节数组反序列化为Java对象
*/
public class FastJsonRedisSerializer implements RedisSerializer {
+ // 默认字符集,使用UTF-8编码
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+ // 泛型T对应的具体类的Class对象
private Class clazz;
+ // 静态代码块,在类加载时执行
+ // 开启FastJson的自动类型支持,这样在序列化和反序列化时可以保留对象的类型信息
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
+ /**
+ * 构造函数,用于初始化泛型T对应的具体类的Class对象
+ * @param clazz 泛型T对应的具体类的Class对象
+ */
public FastJsonRedisSerializer(Class clazz) {
super();
this.clazz = clazz;
}
+ /**
+ * 序列化方法,将Java对象序列化为字节数组
+ * @param t 要序列化的Java对象
+ * @return 序列化后的字节数组
+ * @throws SerializationException 如果序列化过程中出现异常
+ */
@Override
public byte[] serialize(T t) throws SerializationException {
+ // 如果要序列化的对象为null,则返回一个空的字节数组
if (t == null) {
return new byte[0];
}
+ // 使用FastJson将对象转换为JSON字符串,并写入类名信息
+ // 然后将JSON字符串按照默认字符集编码为字节数组
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
+ /**
+ * 反序列化方法,将字节数组反序列化为Java对象
+ * @param bytes 要反序列化的字节数组
+ * @return 反序列化后的Java对象
+ * @throws SerializationException 如果反序列化过程中出现异常
+ */
@Override
public T deserialize(byte[] bytes) throws SerializationException {
+ // 如果字节数组为null或者长度为0,则返回null
if (bytes == null || bytes.length <= 0) {
return null;
}
+ // 将字节数组按照默认字符集解码为字符串
String str = new String(bytes, DEFAULT_CHARSET);
-
+ // 使用FastJson将JSON字符串反序列化为指定类型的对象
return JSON.parseObject(str, clazz);
}
-
+ /**
+ * 获取指定类的JavaType对象
+ * @param clazz 要获取JavaType的类
+ * @return 对应的JavaType对象
+ */
protected JavaType getJavaType(Class> clazz) {
+ // 使用Jackson的TypeFactory构建指定类的JavaType对象
return TypeFactory.defaultInstance().constructType(clazz);
}
}
\ No newline at end of file
diff --git a/server/src/main/java/com/ew/gerocomium/common/config/redis/RedisConfig.java b/server/src/main/java/com/ew/gerocomium/common/config/redis/RedisConfig.java
index 78ff5c3..38d8b7a 100644
--- a/server/src/main/java/com/ew/gerocomium/common/config/redis/RedisConfig.java
+++ b/server/src/main/java/com/ew/gerocomium/common/config/redis/RedisConfig.java
@@ -1,30 +1,53 @@
+// 声明该类所在的包,包名为 com.ew.gerocomium.common.config.redis
package com.ew.gerocomium.common.config.redis;
+// 导入 Spring 框架中用于声明 Bean 的注解
import org.springframework.context.annotation.Bean;
+// 导入 Spring 框架中用于声明配置类的注解
import org.springframework.context.annotation.Configuration;
+// 导入 Spring Data Redis 中用于表示 Redis 连接工厂的类
import org.springframework.data.redis.connection.RedisConnectionFactory;
+// 导入 Spring Data Redis 中用于操作 Redis 的核心类
import org.springframework.data.redis.core.RedisTemplate;
+// 导入 Spring Data Redis 中用于将字符串进行序列化的类
import org.springframework.data.redis.serializer.StringRedisSerializer;
+// 使用 @Configuration 注解将该类标记为 Spring 的配置类,用于定义 Bean
+
+// 此注解表明该类是一个配置类,Spring会对其进行扫描并处理其中的配置信息
@Configuration
public class RedisConfig {
+ /**
+ * 创建一个RedisTemplate的Bean,用于与Redis进行交互
+ * @param connectionFactory Redis连接工厂,用于创建Redis连接
+ * @return 配置好的RedisTemplate实例
+ */
@Bean
+ // 抑制未经检查的类型转换和原始类型使用的警告
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate