refactor(projects): 动态路由权限完善

This commit is contained in:
Soybean 2022-04-29 02:00:51 +08:00
parent 401f0c748d
commit 55ddc9cab0
36 changed files with 406 additions and 717 deletions

View File

@ -1,6 +1,6 @@
VITE_VISUALIZER=true
VITE_VISUALIZER=false
VITE_COMPRESS=true
VITE_COMPRESS=false
# gzip | brotliCompress | deflate | deflateRaw
VITE_COMPRESS_TYPE=gzip

View File

@ -55,12 +55,11 @@ module.exports = {
group: 'external',
position: 'before'
},
// ui framework, such as "naive-ui"
// {
// pattern: 'naive-ui',
// group: 'external',
// position: 'before'
// },
{
pattern: 'naive-ui',
group: 'external',
position: 'before'
},
{
pattern: '@/config',
group: 'internal',
@ -142,13 +141,7 @@ module.exports = {
position: 'before'
}
],
pathGroupsExcludedImportTypes: [
'vue',
'vue-router',
'vuex',
'pinia'
// 'naive-ui'
]
pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'vuex', 'pinia', 'naive-ui']
}
],
'import/no-unresolved': 'off',
@ -171,17 +164,7 @@ module.exports = {
ignores: ['index']
}
],
'@typescript-eslint/ban-types': [
'error',
{
types: {
'{}': {
message: 'Use object instead',
fixWith: 'object'
}
}
}
],
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-empty-interface': [
'error',
{
@ -192,17 +175,12 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true, varsIgnorePattern: '^_' }],
'@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }]
'@typescript-eslint/no-use-before-define': ['warn', { classes: true, functions: false, typedefs: false }]
},
overrides: [
{
files: ['*.vue'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
},
rules: {
'no-unused-vars': 'off',
'no-undef': 'off'
}
},

View File

@ -33,7 +33,6 @@
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"javascript.updateImportsOnFileMove.enabled": "always",
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
@ -72,5 +71,6 @@
"business": "core",
"request": "api",
"adapter": "middleware"
}
},
"unocss.root": "src"
}

View File

@ -1,9 +1,10 @@
import type { MockMethod } from 'vite-plugin-mock';
import { userModel } from '../model';
const token: ApiAuth.Token = {
token: '__TEMP_TOKEN__',
refreshToken: '__TEMP_REFRESH_TOKEN__'
};
/** 参数错误的状态码 */
const ERROR_PARAM_CODE = 10000;
const ERROR_PARAM_MSG = '参数校验失败!';
const apis: MockMethod[] = [
// 获取验证码
@ -18,73 +19,107 @@ const apis: MockMethod[] = [
};
}
},
// 密码登录
// 用户+密码 登录
{
url: '/mock/loginByPwd',
url: '/mock/login',
method: 'post',
response: (): Service.MockServiceResult<ApiAuth.Token> => {
return {
code: 200,
message: 'ok',
data: token
};
}
},
// 验证码登录
{
url: '/mock/loginByCode',
method: 'post',
response: (): Service.MockServiceResult<ApiAuth.Token> => {
return {
code: 200,
message: 'ok',
data: token
};
}
},
// 获取用户信息(请求头携带token)
{
url: '/mock/getUserInfo',
method: 'get',
response: (): Service.MockServiceResult<ApiAuth.UserInfo> => {
return {
code: 200,
message: 'ok',
data: {
userId: '0',
userName: 'Soybean',
userPhone: '15170283876',
userRole: 'super'
}
};
}
},
{
url: '/mock/testToken',
method: 'post',
response: (option: Service.MockOption): Service.MockServiceResult<true | null> => {
if (option.headers?.authorization !== token.token) {
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
const { userName = undefined, password = undefined } = options.body;
if (!userName || !password) {
return {
code: 66666,
message: 'token 失效',
code: ERROR_PARAM_CODE,
message: ERROR_PARAM_MSG,
data: null
};
}
const findItem = userModel.find(item => item.userName === userName && item.password === password);
if (findItem) {
return {
code: 200,
message: 'ok',
data: {
token: findItem.token,
refreshToken: findItem.refreshToken
}
};
}
return {
code: 200,
message: 'ok',
data: true
code: 1000,
message: '用户名或密码错误!',
data: null
};
}
},
// 获取用户信息(请求头携带token, 根据token获取用户信息)
{
url: '/mock/getUserInfo',
method: 'get',
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.UserInfo | null> => {
// 这里的mock插件得到的字段是authorization, 前端传递的是Authorization字段
const { authorization = '' } = options.headers;
const REFRESH_TOKEN_CODE = 66666;
if (!authorization) {
return {
code: REFRESH_TOKEN_CODE,
message: '用户已失效或不存在!',
data: null
};
}
const userInfo: Auth.UserInfo = {
userId: '',
userName: '',
userRole: 'user'
};
const isInUser = userModel.some(item => {
const flag = item.token === authorization;
if (flag) {
const { userId: itemUserId, userName, userRole } = item;
Object.assign(userInfo, { userId: itemUserId, userName, userRole });
}
return flag;
});
if (isInUser) {
return {
code: 200,
message: 'ok',
data: userInfo
};
}
return {
code: REFRESH_TOKEN_CODE,
message: '用户信息异常!',
data: null
};
}
},
{
url: '/mock/updateToken',
method: 'post',
response: (): Service.MockServiceResult<ApiAuth.Token> => {
response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
const { refreshToken = '' } = options.body;
const findItem = userModel.find(item => item.refreshToken === refreshToken);
if (findItem) {
return {
code: 200,
message: 'ok',
data: {
token: findItem.token,
refreshToken: findItem.refreshToken
}
};
}
return {
code: 200,
message: 'ok',
data: token
code: 3000,
message: '用户已失效或不存在!',
data: null
};
}
}

View File

@ -1,403 +1,26 @@
import type { MockMethod } from 'vite-plugin-mock';
import { filterAuthRoutesByUserPermission } from '../utils';
const routes: AuthRoute.Route[] = [
{
name: 'dashboard',
path: '/dashboard',
component: 'basic',
children: [
{
name: 'dashboard_analysis',
path: '/dashboard/analysis',
component: 'self',
meta: {
title: '分析页',
requiresAuth: true,
icon: 'icon-park-outline:analysis'
}
},
{
name: 'dashboard_workbench',
path: '/dashboard/workbench',
component: 'self',
meta: {
title: '工作台',
requiresAuth: true,
permissions: ['super', 'admin'],
icon: 'icon-park-outline:workbench'
}
}
],
meta: {
title: '仪表盘',
icon: 'carbon:dashboard',
order: 1
}
},
{
name: 'document',
path: '/document',
component: 'basic',
children: [
{
name: 'document_vue',
path: '/document/vue',
component: 'self',
meta: {
title: 'vue文档',
requiresAuth: true,
icon: 'mdi:vuejs'
}
},
{
name: 'document_vue-new',
path: '/document/vue-new',
component: 'self',
meta: {
title: 'vue文档(新版)',
requiresAuth: true,
icon: 'mdi:vuejs'
}
},
{
name: 'document_vite',
path: '/document/vite',
component: 'self',
meta: {
title: 'vite文档',
requiresAuth: true,
icon: 'simple-icons:vite'
}
},
{
name: 'document_project',
path: '/document/project',
meta: {
title: '项目文档(外链)',
requiresAuth: true,
icon: 'mdi:file-link-outline',
href: 'https://docs.soybean.pro/'
}
}
],
meta: {
title: '文档',
icon: 'carbon:document',
order: 2
}
},
{
name: 'component',
path: '/component',
component: 'basic',
children: [
{
name: 'component_button',
path: '/component/button',
component: 'self',
meta: {
title: '按钮',
requiresAuth: true,
icon: 'ic:baseline-radio-button-checked'
}
},
{
name: 'component_card',
path: '/component/card',
component: 'self',
meta: {
title: '卡片',
requiresAuth: true,
icon: 'mdi:card-outline'
}
},
{
name: 'component_table',
path: '/component/table',
component: 'self',
meta: {
title: '表格',
requiresAuth: true,
icon: 'mdi:table-large'
}
}
],
meta: {
title: '组件示例',
icon: 'fluent:app-store-24-regular',
order: 3
}
},
{
name: 'plugin',
path: '/plugin',
component: 'basic',
children: [
{
name: 'plugin_map',
path: '/plugin/map',
component: 'self',
meta: {
title: '地图',
requiresAuth: true,
icon: 'mdi:map'
}
},
{
name: 'plugin_video',
path: '/plugin/video',
component: 'self',
meta: {
title: '视频',
requiresAuth: true,
icon: 'mdi:video'
}
},
{
name: 'plugin_editor',
path: '/plugin/editor',
component: 'multi',
children: [
{
name: 'plugin_editor_quill',
path: '/plugin/editor/quill',
component: 'self',
meta: {
title: '富文本编辑器',
requiresAuth: true,
icon: 'mdi:file-document-edit-outline'
}
},
{
name: 'plugin_editor_markdown',
path: '/plugin/editor/markdown',
component: 'self',
meta: {
title: 'markdown编辑器',
requiresAuth: true,
icon: 'ri:markdown-line'
}
}
],
meta: {
title: '编辑器',
icon: 'icon-park-outline:editor'
}
},
{
name: 'plugin_swiper',
path: '/plugin/swiper',
component: 'self',
meta: {
title: 'Swiper插件',
requiresAuth: true,
icon: 'simple-icons:swiper'
}
},
{
name: 'plugin_copy',
path: '/plugin/copy',
component: 'self',
meta: {
title: '剪贴板',
requiresAuth: true,
icon: 'mdi:clipboard-outline'
}
},
{
name: 'plugin_icon',
path: '/plugin/icon',
component: 'self',
meta: {
title: '图标',
requiresAuth: true,
icon: 'ic:baseline-insert-emoticon'
}
},
{
name: 'plugin_print',
path: '/plugin/print',
component: 'self',
meta: {
title: '打印',
requiresAuth: true,
icon: 'ic:baseline-local-printshop'
}
}
],
meta: {
title: '插件示例',
icon: 'clarity:plugin-line',
order: 4
}
},
{
name: 'auth-demo',
path: '/auth-demo',
component: 'basic',
children: [
{
name: 'auth-demo_permission',
path: '/auth-demo/permission',
component: 'self',
meta: {
title: '权限切换',
requiresAuth: true,
icon: 'ic:round-construction'
}
},
{
name: 'auth-demo_super',
path: '/auth-demo/super',
component: 'self',
meta: {
title: '超级管理员可见',
requiresAuth: true,
permissions: ['super'],
icon: 'ic:round-supervisor-account'
}
}
],
meta: {
title: '权限示例',
icon: 'ic:baseline-security',
order: 5
}
},
{
name: 'exception',
path: '/exception',
component: 'basic',
children: [
{
name: 'exception_403',
path: '/exception/403',
component: 'self',
meta: {
title: '异常页403',
requiresAuth: true,
icon: 'ic:baseline-block'
}
},
{
name: 'exception_404',
path: '/exception/404',
component: 'self',
meta: {
title: '异常页404',
requiresAuth: true,
icon: 'ic:baseline-web-asset-off'
}
},
{
name: 'exception_500',
path: '/exception/500',
component: 'self',
meta: {
title: '异常页500',
requiresAuth: true,
icon: 'ic:baseline-wifi-off'
}
}
],
meta: {
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 6
}
},
{
name: 'multi-menu',
path: '/multi-menu',
component: 'basic',
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
component: 'multi',
children: [
{
name: 'multi-menu_first_second',
path: '/multi-menu/first/second',
component: 'self',
meta: {
title: '二级菜单',
requiresAuth: true,
icon: 'ic:outline-menu'
}
},
{
name: 'multi-menu_first_second-new',
path: '/multi-menu/first/second-new',
component: 'multi',
children: [
{
name: 'multi-menu_first_second-new_third',
path: '/multi-menu/first/second-new/third',
component: 'self',
meta: {
title: '三级菜单',
requiresAuth: true,
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '二级菜单(有子菜单)',
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '一级菜单',
icon: 'ic:outline-menu'
}
}
],
meta: {
title: '多级菜单',
icon: 'carbon:menu',
order: 7
}
},
{
name: 'about',
path: '/about',
component: 'self',
meta: {
title: '关于',
requiresAuth: true,
singleLayout: 'basic',
permissions: ['super', 'admin', 'user'],
icon: 'fluent:book-information-24-regular',
order: 8
}
}
];
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
data.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
const filters = filterAuthRoutesByUserPermission(data, 'admin');
return {
routes: filters,
home: routeHomeName
};
}
import { userModel, routeModel } from '../model';
const apis: MockMethod[] = [
{
url: '/mock/getUserRoutes',
method: 'post',
response: (): Service.MockServiceResult => {
response: (options: Service.MockOption): Service.MockServiceResult => {
const { userId = undefined } = options.body;
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
const filterRoutes = routeModel[role];
return {
code: 200,
message: 'ok',
data: dataMiddleware(routes)
data: {
routes: filterRoutes,
home: routeHomeName
}
};
}
}

40
mock/model/auth.ts Normal file
View File

@ -0,0 +1,40 @@
interface UserModel extends Auth.UserInfo {
token: string;
refreshToken: string;
password: string;
}
export const userModel: UserModel[] = [
{
token: '__TOKEN_SOYBEAN__',
refreshToken: '__REFRESH_TOKEN_SOYBEAN__',
userId: '0',
userName: 'Soybean',
userRole: 'super',
password: 'soybean123'
},
{
token: '__TOKEN_SUPER__',
refreshToken: '__REFRESH_TOKEN_SUPER__',
userId: '1',
userName: 'Super',
userRole: 'super',
password: 'super123'
},
{
token: '__TOKEN_ADMIN__',
refreshToken: '__REFRESH_TOKEN_ADMIN__',
userId: '2',
userName: 'Admin',
userRole: 'admin',
password: 'admin123'
},
{
token: '__TOKEN_USER01__',
refreshToken: '__REFRESH_TOKEN_USER01__',
userId: '3',
userName: 'User01',
userRole: 'user',
password: 'user01123'
}
];

2
mock/model/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './auth';
export * from './route';

View File

@ -1,4 +1,4 @@
const routes: Record<Auth.RoleType, AuthRoute.Route[]> = {
export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
super: [
{
name: 'dashboard',
@ -844,5 +844,3 @@ const routes: Record<Auth.RoleType, AuthRoute.Route[]> = {
}
]
};
export default routes;

View File

@ -1,35 +0,0 @@
interface ModelUser {
userId: string;
userName: string;
password: string;
role: Auth.RoleType;
}
const users: ModelUser[] = [
{
userId: '0',
userName: 'Soybean',
password: 'soybean',
role: 'super'
},
{
userId: '1',
userName: 'Super',
password: 'super',
role: 'super'
},
{
userId: '2',
userName: 'Admin',
password: 'admin',
role: 'admin'
},
{
userId: '3',
userName: 'User01',
password: 'user01',
role: 'user'
}
];
export default users;

View File

@ -1,24 +0,0 @@
/**
*
* @param routes -
* @param permission -
*/
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
return routes.map(route => filterAuthRouteByUserPermission(route, permission)).flat(1);
}
/**
*
* @param route -
* @param permission -
*/
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
const hasPermission =
!route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
if (route.children) {
const filterChildren = route.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
Object.assign(route, { children: filterChildren });
}
return hasPermission ? [route] : [];
}

View File

@ -52,8 +52,8 @@
},
"devDependencies": {
"@amap/amap-jsapi-types": "^0.0.8",
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@commitlint/cli": "^16.2.4",
"@commitlint/config-conventional": "^16.2.4",
"@iconify/json": "^2.1.33",
"@iconify/vue": "^3.2.1",
"@types/bmapgl": "^0.0.5",
@ -86,7 +86,7 @@
"rollup-plugin-visualizer": "^5.6.0",
"sass": "^1.51.0",
"typescript": "^4.6.3",
"unocss": "^0.31.17",
"unocss": "^0.32.1",
"unplugin-icons": "^0.14.1",
"unplugin-vue-components": "0.19.3",
"unplugin-vue-define-options": "^0.6.1",

View File

@ -4,8 +4,8 @@ specifiers:
'@amap/amap-jsapi-types': ^0.0.8
'@antv/g2plot': ^2.4.16
'@better-scroll/core': ^2.4.2
'@commitlint/cli': ^16.2.3
'@commitlint/config-conventional': ^16.2.1
'@commitlint/cli': ^16.2.4
'@commitlint/config-conventional': ^16.2.4
'@iconify/json': ^2.1.33
'@iconify/vue': ^3.2.1
'@soybeanjs/vue-admin-layout': ^1.0.3
@ -54,7 +54,7 @@ specifiers:
swiper: ^8.1.4
typescript: ^4.6.3
ua-parser-js: ^1.0.2
unocss: ^0.31.17
unocss: ^0.32.1
unplugin-icons: ^0.14.1
unplugin-vue-components: 0.19.3
unplugin-vue-define-options: ^0.6.1
@ -97,8 +97,8 @@ dependencies:
devDependencies:
'@amap/amap-jsapi-types': 0.0.8
'@commitlint/cli': 16.2.3
'@commitlint/config-conventional': 16.2.1
'@commitlint/cli': 16.2.4
'@commitlint/config-conventional': 16.2.4
'@iconify/json': 2.1.33
'@iconify/vue': 3.2.1_vue@3.2.33
'@types/bmapgl': 0.0.5
@ -131,7 +131,7 @@ devDependencies:
rollup-plugin-visualizer: 5.6.0
sass: 1.51.0
typescript: 4.6.3
unocss: 0.31.17_vite@2.9.6
unocss: 0.32.1_vite@2.9.6
unplugin-icons: 0.14.1_vite@2.9.6
unplugin-vue-components: 0.19.3_vite@2.9.6+vue@3.2.33
unplugin-vue-define-options: 0.6.1_vite@2.9.6+vue@3.2.33
@ -649,14 +649,14 @@ packages:
resolution: {integrity: sha512-Gy/Jfbpu+hq0u+PcjkTqyXGqAf+0dexTzEZ5IDXEVwJVLmd3cx8A73oTcAZ8QZgk4wSHvlMjXecSaptkhnNPEw==}
dev: false
/@commitlint/cli/16.2.3:
resolution: {integrity: sha512-VsJBQLvhhlOgEfxs/Z5liYuK0dXqLE5hz1VJzLBxiOxG31kL/X5Q4OvK292BmO7IGZcm1yJE3XQPWSiFaEHbWA==}
/@commitlint/cli/16.2.4:
resolution: {integrity: sha512-rbvqvz9JI+uiKxV2nH65BtSU01fsADd3bxe9fWtO3rM0c+CI/H9FfzKkDLvSRmXjvk1G2/wXlCGeqO9IBT4X9g==}
engines: {node: '>=v12'}
hasBin: true
dependencies:
'@commitlint/format': 16.2.1
'@commitlint/lint': 16.2.1
'@commitlint/load': 16.2.3
'@commitlint/lint': 16.2.4
'@commitlint/load': 16.2.4
'@commitlint/read': 16.2.1
'@commitlint/types': 16.2.1
lodash: 4.17.21
@ -668,8 +668,8 @@ packages:
- '@swc/wasm'
dev: true
/@commitlint/config-conventional/16.2.1:
resolution: {integrity: sha512-cP9gArx7gnaj4IqmtCIcHdRjTYdRUi6lmGE+lOzGGjGe45qGOS8nyQQNvkNy2Ey2VqoSWuXXkD8zCUh6EHf1Ww==}
/@commitlint/config-conventional/16.2.4:
resolution: {integrity: sha512-av2UQJa3CuE5P0dzxj/o/B9XVALqYzEViHrMXtDrW9iuflrqCStWBAioijppj9URyz6ONpohJKAtSdgAOE0gkA==}
engines: {node: '>=v12'}
dependencies:
conventional-changelog-conventionalcommits: 4.6.3
@ -704,21 +704,21 @@ packages:
chalk: 4.1.2
dev: true
/@commitlint/is-ignored/16.2.1:
resolution: {integrity: sha512-exl8HRzTIfb1YvDJp2b2HU5z1BT+9tmgxR2XF0YEzkMiCIuEKh+XLeocPr1VcvAKXv3Cmv5X/OfNRp+i+/HIhQ==}
/@commitlint/is-ignored/16.2.4:
resolution: {integrity: sha512-Lxdq9aOAYCOOOjKi58ulbwK/oBiiKz+7Sq0+/SpFIEFwhHkIVugvDvWjh2VRBXmRC/x5lNcjDcYEwS/uYUvlYQ==}
engines: {node: '>=v12'}
dependencies:
'@commitlint/types': 16.2.1
semver: 7.3.5
semver: 7.3.7
dev: true
/@commitlint/lint/16.2.1:
resolution: {integrity: sha512-fNINQ3X2ZqsCkNB3Z0Z8ElmhewqrS3gy2wgBTx97BkcjOWiyPAGwDJ752hwrsUnWAVBRztgw826n37xPzxsOgg==}
/@commitlint/lint/16.2.4:
resolution: {integrity: sha512-AUDuwOxb2eGqsXbTMON3imUGkc1jRdtXrbbohiLSCSk3jFVXgJLTMaEcr39pR00N8nE9uZ+V2sYaiILByZVmxQ==}
engines: {node: '>=v12'}
dependencies:
'@commitlint/is-ignored': 16.2.1
'@commitlint/is-ignored': 16.2.4
'@commitlint/parse': 16.2.1
'@commitlint/rules': 16.2.1
'@commitlint/rules': 16.2.4
'@commitlint/types': 16.2.1
dev: true
@ -741,6 +741,27 @@ packages:
- '@swc/core'
- '@swc/wasm'
dev: true
optional: true
/@commitlint/load/16.2.4:
resolution: {integrity: sha512-HjANm3/29ROV+zt4yfaY/K6gpr9Dbzgtlp0kSwZGW0poDXlD/yqVYgPQ6JolJzZii5FUz5R4yVLC15hVL/w60w==}
engines: {node: '>=v12'}
dependencies:
'@commitlint/config-validator': 16.2.1
'@commitlint/execute-rule': 16.2.1
'@commitlint/resolve-extends': 16.2.1
'@commitlint/types': 16.2.1
'@types/node': 17.0.29
chalk: 4.1.2
cosmiconfig: 7.0.1
cosmiconfig-typescript-loader: 1.0.9_5281fe59fc32158e106b8b5e2bebb315
lodash: 4.17.21
resolve-from: 5.0.0
typescript: 4.6.3
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
dev: true
/@commitlint/message/16.2.1:
resolution: {integrity: sha512-2eWX/47rftViYg7a3axYDdrgwKv32mxbycBJT6OQY/MJM7SUfYNYYvbMFOQFaA4xIVZt7t2Alyqslbl6blVwWw==}
@ -778,8 +799,8 @@ packages:
resolve-global: 1.0.0
dev: true
/@commitlint/rules/16.2.1:
resolution: {integrity: sha512-ZFezJXQaBBso+BOTre/+1dGCuCzlWVaeLiVRGypI53qVgPMzQqZhkCcrxBFeqB87qeyzr4A4EoG++IvITwwpIw==}
/@commitlint/rules/16.2.4:
resolution: {integrity: sha512-rK5rNBIN2ZQNQK+I6trRPK3dWa0MtaTN4xnwOma1qxa4d5wQMQJtScwTZjTJeallFxhOgbNOgr48AMHkdounVg==}
engines: {node: '>=v12'}
dependencies:
'@commitlint/ensure': 16.2.1
@ -1238,14 +1259,14 @@ packages:
eslint-visitor-keys: 3.3.0
dev: true
/@unocss/cli/0.31.17:
resolution: {integrity: sha512-at/gAnZBrutoTuee7dujhN/TRSCWAq+1QYm2ekfNktt4dgA5xVzvFQo63GEUvnM4tE51SQkIbznf44BxNbuEUQ==}
/@unocss/cli/0.32.1:
resolution: {integrity: sha512-KrO0SCiyss9fYl+VM4yNdYUqqMGpNQdOjPXErr12eNLnY68WiM7yEPZrcQL3QZO2tza7CObkivMkuCC1PMITbQ==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@unocss/config': 0.31.17
'@unocss/core': 0.31.17
'@unocss/preset-uno': 0.31.17
'@unocss/config': 0.32.1
'@unocss/core': 0.32.1
'@unocss/preset-uno': 0.32.1
cac: 6.7.12
chokidar: 3.5.3
colorette: 2.0.16
@ -1255,107 +1276,112 @@ packages:
perfect-debounce: 0.1.3
dev: true
/@unocss/config/0.31.17:
resolution: {integrity: sha512-Ggj2yCOeLxRfXd2Qt4ajWXJXQilZc/ozBFZM0vU77iFFq/yBlCWH6rLlhkYqGvSQT+K5SfaKluncskSG2090YA==}
/@unocss/config/0.32.1:
resolution: {integrity: sha512-BuS22lneXnuC6KkZUOYLVPdWnYKUx5reYzFOayUp0FmMcG2uMuMWZGGBx10JV/hWSWq3FKkCT8bNM0QcNZXysw==}
engines: {node: '>=14'}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
unconfig: 0.3.3
dev: true
/@unocss/core/0.31.17:
resolution: {integrity: sha512-DJ3Uk2ePVXvV1qQmgoLK44aqB6f0s+naOEvouI97nzVXDZgxDQPBxIPB/L4vvE4U+gQxEiHwwE3gJ75iPqVzXw==}
/@unocss/core/0.32.1:
resolution: {integrity: sha512-VlLF00eRvP8lsEspPWTju6kgl67R3POYl1HKZP0IPknbm9o6dyEwsKkG9VAHHvO4lDiQMI5O3ahI+DRvN0zmfg==}
dev: true
/@unocss/inspector/0.31.17:
resolution: {integrity: sha512-eBRSBtiSvIk5mVJnNyN6Ag9ykBmQi2xROvcNS5sa11SHMkwWEaj1/8kBxyYFuOvl6ysEtdC7eSzv0tMRjXuuTw==}
/@unocss/inspector/0.32.1:
resolution: {integrity: sha512-BaKKYg9llm1ZyPpvHfffBNJmJ2DqageFUQjd2uRFZCGjYzqiXF7wCSGFccYAwbS7vs+DhAqDuoAAS2F8n/Hbew==}
dependencies:
gzip-size: 6.0.0
sirv: 2.0.2
dev: true
/@unocss/preset-attributify/0.31.17:
resolution: {integrity: sha512-Kar6K6oF7Zp/qXbWq1g+RwphOKCHiU3kWhulgbwy/HbdhSXsR0EE8zAHIgEga25q72Mm0hxBlowPtbjvX107rQ==}
/@unocss/preset-attributify/0.32.1:
resolution: {integrity: sha512-rqjR/RDePVEoqEJMtnQ7uGE1qTHlzf07nQqVPBwebadOhbype4YguoC1Y7Dgiqc78tEiGj88OMoO1vBXMkw3SQ==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
dev: true
/@unocss/preset-icons/0.31.17:
resolution: {integrity: sha512-cdS/BuL15NOKWIBxTW8e3xnr26MmC1dOtOtPLljLyMhdnEWOwHQA2PB9YtUZIEvrr2zTyMv/9aPQlBQ78973kg==}
/@unocss/preset-icons/0.32.1:
resolution: {integrity: sha512-S31Qlqbr7NBduQR/jdirYHecyM8DNT9TpXjqvxX2ssKMB1Ie1zpAWrtwM5Y0JaprjKa0mmL1KZ0wWGTHJ+LypA==}
dependencies:
'@iconify/utils': 1.0.32
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
transitivePeerDependencies:
- supports-color
dev: true
/@unocss/preset-mini/0.31.17:
resolution: {integrity: sha512-gVgMTOKLt3O1ym348QIBmR5sS9W0Ozkk5xelhH6e0VXcpg0dXDPDrl4hFErMy4x6IB86yyJG6Dz5JhcwQB13Ig==}
/@unocss/preset-mini/0.32.1:
resolution: {integrity: sha512-9cq1yfJLKmgim8lr/CmfpGTEVOCdg5W1F5MbutRtRijYrsSsETCGLLCfdq4QhV4o84QE8kAM6iLoUm0j+Gn8Sg==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
dev: true
/@unocss/preset-typography/0.31.17:
resolution: {integrity: sha512-zFFZeGdcXxpjgLG1o1zUQNFCdBqyGoFfUa0Zj0SeIYs11a4Y3ra701jHr1F4X4mhYzIyCUEfGC9X852o0iPa+A==}
/@unocss/preset-typography/0.32.1:
resolution: {integrity: sha512-+bekG07fiOjSzzyaJ2LmaGbJJab9XNyyC18TOYPCtryChO3kr2AmJyqows2II2+uqcGlexe6rsjeNteHy58cNg==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
dev: true
/@unocss/preset-uno/0.31.17:
resolution: {integrity: sha512-zSajrrPQlPXBr+egbQ00Nvku9YrqFh3pWByVSx/4XPpZ1oSSjOqMAfGcdDPlmOWi++G6FLU28sglc3JB7jJEZA==}
/@unocss/preset-uno/0.32.1:
resolution: {integrity: sha512-SHLusZNl2d1mfqyu425xCUkShLOrqYANxXVvb/HOhDXtWDtf8dglE3afbYCcklTb1YDZ08M6k4AsSgZjsv23ng==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/preset-mini': 0.31.17
'@unocss/preset-wind': 0.31.17
'@unocss/core': 0.32.1
'@unocss/preset-mini': 0.32.1
'@unocss/preset-wind': 0.32.1
dev: true
/@unocss/preset-web-fonts/0.31.17:
resolution: {integrity: sha512-4FVNYMBN70j8xNOTxnUG6XeEJ/1WoJ1shQ72UhXDMaH4ZgCORvmAYdjl4opjEEB4RoXg5yJ1N1W6B3O/bsupbQ==}
/@unocss/preset-web-fonts/0.32.1:
resolution: {integrity: sha512-cPIcrkRZKejGNwF/ZzrhGW8gLQpgAdr1EIhSNPsJG77kpWn7fnsA+VUqa//2shKYhx0hezS3Tbq47hDK5qc4gQ==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
axios: 0.26.1
transitivePeerDependencies:
- debug
dev: true
/@unocss/preset-wind/0.31.17:
resolution: {integrity: sha512-GYVxPA66BovfXO9IAbSlE5yuBTO3ko7ChJS1Oisy3Y0+JBNXJsqzKlyLRLeKOSK76o2b/D0wRO8xEqirh76GQA==}
/@unocss/preset-wind/0.32.1:
resolution: {integrity: sha512-StgSnYQ4w7sUYqW1GURCICQ42qL83SQ1H5KZbSIJVnv2VVgo77m6dt96TmTmvizWJWH4Ga597YFhjXP4WUv6mw==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/preset-mini': 0.31.17
'@unocss/core': 0.32.1
'@unocss/preset-mini': 0.32.1
dev: true
/@unocss/reset/0.31.17:
resolution: {integrity: sha512-g3+bqtM6LetSEJ5NYhi2P4vdP8yVLUQLbNZUdMtggcmHXTY08ISWaJKWmnHptrO13rtRoQ+l9gFc4Y7kRpD7NA==}
dev: false
/@unocss/scope/0.31.17:
resolution: {integrity: sha512-X6V62OKexnhePLuVj9FtrpAJYUCpIedIieogvl6gHDZMnTnJPNaW9jJ7/e6r21F3u9IrqOzlikgCicFSm4J/TA==}
/@unocss/reset/0.32.1:
resolution: {integrity: sha512-tC2gCYY2isEDfBo1LWfcPAnxuM3my29fWtFUQRGvViZVTwYFjVIGD8BgIfiioifGumLunRW78qN7LT22fXoxgg==}
dev: true
/@unocss/transformer-directives/0.31.17:
resolution: {integrity: sha512-1FF6PQybr2eFVp1mlz+OeTDAIWTuJw61EJneFWlsnWk2PgqBlX25SIuQjTWhXfjWihL3n8F2wHrX8i0vcG39bg==}
/@unocss/scope/0.32.1:
resolution: {integrity: sha512-6Xsas4Fm797IIONNXP7Y7JLL7B4NTjk/BeFX7j++CS14SfGzF0wTTkyaHxE9ETecOiouUABwoiPQRzHNAkrNIA==}
dev: true
/@unocss/transformer-directives/0.32.1:
resolution: {integrity: sha512-F4rPaNGaKbRGKksHuzAfSrsIaMZLpvqRhlk1xpwN5AGHClhK+Ak0z9j+N9Yy4rUuRx5OeOCuOj9Kcvq8cL8JnA==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
css-tree: 2.1.0
dev: true
/@unocss/transformer-variant-group/0.31.17:
resolution: {integrity: sha512-q1L7jckHicv2GwdKp7KGhufHeH5sGhJeRv1EGVZkb7KFKt9AROH9X9LDzE6Xr0jWgywrCIyTUIBdZwb2aKrjeg==}
/@unocss/transformer-variant-group/0.32.1:
resolution: {integrity: sha512-SrT5nWvNuuGFlMSGYgP7+KemYaBSUlP7i6v5QqzbjX9bcMz8JicT5Rwvy1Z1GOdtRGCy7e5aB6LW3Mxpd/pQhw==}
dependencies:
'@unocss/core': 0.31.17
'@unocss/core': 0.32.1
dev: true
/@unocss/vite/0.31.17_vite@2.9.6:
resolution: {integrity: sha512-+NH/In8zqBXbTWfpiu8u/7jkwBJdaq2lM/ErXzd0q07w8Jv0FmKRWxBGml168uDA6dHHoJRcGO1AvzOYxsv9+A==}
/@unocss/vite/0.32.1_vite@2.9.6:
resolution: {integrity: sha512-XLbBodjSfgoyTRUGL5XY8Fw+pW7GOZ1WrT1C+QfVJt8+BgY6wIdVhaERbfgOdAw/trzM0PYwhhbMHZp0SyJE5Q==}
peerDependencies:
vite: ^2.9.0
dependencies:
'@rollup/pluginutils': 4.2.1
'@unocss/config': 0.31.17
'@unocss/core': 0.31.17
'@unocss/inspector': 0.31.17
'@unocss/scope': 0.31.17
'@unocss/transformer-directives': 0.31.17
'@unocss/config': 0.32.1
'@unocss/core': 0.32.1
'@unocss/inspector': 0.32.1
'@unocss/scope': 0.32.1
'@unocss/transformer-directives': 0.32.1
magic-string: 0.26.1
vite: 2.9.6_sass@1.51.0
dev: true
@ -2293,7 +2319,7 @@ packages:
longest: 2.0.1
word-wrap: 1.2.3
optionalDependencies:
'@commitlint/load': 16.2.3
'@commitlint/load': 16.2.4
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
@ -5177,14 +5203,6 @@ packages:
hasBin: true
dev: true
/semver/7.3.5:
resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/semver/7.3.7:
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
engines: {node: '>=10'}
@ -5793,23 +5811,23 @@ packages:
engines: {node: '>= 10.0.0'}
dev: true
/unocss/0.31.17_vite@2.9.6:
resolution: {integrity: sha512-JJsxXfHfRRDvimDSgTTIpDPpYsVcp/jMxj+I/WsDIFQjBKhB4dq0VyjKl5dXlicgMTJfy2wrj/zBGMPl9W6/qA==}
/unocss/0.32.1_vite@2.9.6:
resolution: {integrity: sha512-Flp6FHOLznE7nTOSTSNPiyL3dd9V8TvhXPnReEwRo8yZxXOIg/+9cVzvF1nwGCIbfDYbjNK+eNKmtwxVmB78og==}
engines: {node: '>=14'}
dependencies:
'@unocss/cli': 0.31.17
'@unocss/core': 0.31.17
'@unocss/preset-attributify': 0.31.17
'@unocss/preset-icons': 0.31.17
'@unocss/preset-mini': 0.31.17
'@unocss/preset-typography': 0.31.17
'@unocss/preset-uno': 0.31.17
'@unocss/preset-web-fonts': 0.31.17
'@unocss/preset-wind': 0.31.17
'@unocss/reset': 0.31.17
'@unocss/transformer-directives': 0.31.17
'@unocss/transformer-variant-group': 0.31.17
'@unocss/vite': 0.31.17_vite@2.9.6
'@unocss/cli': 0.32.1
'@unocss/core': 0.32.1
'@unocss/preset-attributify': 0.32.1
'@unocss/preset-icons': 0.32.1
'@unocss/preset-mini': 0.32.1
'@unocss/preset-typography': 0.32.1
'@unocss/preset-uno': 0.32.1
'@unocss/preset-web-fonts': 0.32.1
'@unocss/preset-wind': 0.32.1
'@unocss/reset': 0.32.1
'@unocss/transformer-directives': 0.32.1
'@unocss/transformer-variant-group': 0.32.1
'@unocss/vite': 0.32.1_vite@2.9.6
transitivePeerDependencies:
- debug
- supports-color

View File

@ -5,9 +5,9 @@ export const REGEXP_PHONE =
/** 邮箱正则 */
export const REGEXP_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
/** 密码正则(密码为8-18位数字/字符/符号的组合) */
/** 密码正则(密码为6-18位数字/字符/符号的组合) */
export const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){6,18}$/;
/** 6位数字验证码正则 */
export const REGEXP_CODE_SIX = /^\d{6}$/;

View File

@ -50,7 +50,7 @@ const activeComponent = computed(() => (isChromeMode.value ? ChromeTab : ButtonT
const tabRef = ref<HTMLElement>();
async function getActiveTabClientX() {
await nextTick();
if (tabRef.value) {
if (tabRef.value && tabRef.value.children.length && tabRef.value.children[tab.activeTabIndex]) {
const activeTabElement = tabRef.value.children[tab.activeTabIndex];
const { x, width } = activeTabElement.getBoundingClientRect();
const clientX = x + width / 2;

View File

@ -1,4 +1,6 @@
export function adapterOfDataWithAdapter(res: Service.RequestResult<ApiDemo.DataWithAdapter>): Demo.DataWithAdapter {
export function adapterOfFetchDataWithAdapter(
res: Service.RequestResult<ApiDemo.DataWithAdapter>
): Demo.DataWithAdapter {
const { dataId, dataName } = res.data!;
const result: Demo.DataWithAdapter = {

View File

@ -11,15 +11,11 @@ export function fetchSmsCode(phone: string) {
/**
*
* @param phone -
* @param pwdOrCode -
* @param type - 登录方式: pwd - ; sms -
* @param userName -
* @param password -
*/
export function fetchLogin(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
if (type === 'pwd') {
return mockRequest.post<ApiAuth.Token>('/loginByPwd', { phone, pwd: pwdOrCode });
}
return mockRequest.post<ApiAuth.Token>('/loginByCode', { phone, code: pwdOrCode });
export function fetchLogin(userName: string, password: string) {
return mockRequest.post<ApiAuth.Token>('/login', { userName, password });
}
/** 获取用户信息 */

View File

@ -1,9 +1,14 @@
import { adapterOfServiceResult } from '@/utils';
import { mockRequest } from '../request';
import { adapterOfDataWithAdapter } from '../adapter';
import { serviceAdapter } from '@/utils';
import { request, mockRequest } from '../request';
import { adapterOfFetchDataWithAdapter } from '../adapter';
/** 带有适配器的请求(将请求结果进行数据处理) */
export async function fetchDataWithAdapter() {
const res = await mockRequest.post<ApiDemo.DataWithAdapter>('/apiDemoWithAdapter');
return adapterOfServiceResult(adapterOfDataWithAdapter, res);
return serviceAdapter(adapterOfFetchDataWithAdapter, res);
}
/** 测试代理后的请求 */
export function testRequestWithProxy() {
return request.get('/test-proxy'); // 确保.env-config的url和当前地址组成的 `${url}/test-proxy` 是有效的后端接口
}

View File

@ -1,9 +1,11 @@
import { unref } from 'vue';
import { unref, nextTick } from 'vue';
import { defineStore } from 'pinia';
import { router as globalRouter } from '@/router';
import { useRouterPush } from '@/composables';
import { fetchLogin, fetchUserInfo } from '@/service';
import { getUserInfo, getToken, setUserInfo, setToken, setRefreshToken, clearAuthStorage } from '@/utils';
import { useTabStore } from '../tab';
import { useRouteStore } from '../route';
interface AuthState {
/** 用户信息 */
@ -30,6 +32,8 @@ export const useAuthStore = defineStore('auth-store', {
/** 重置auth状态 */
resetAuthStore() {
const { toLogin } = useRouterPush(false);
const { resetTabStore } = useTabStore();
const { resetRouteStore } = useRouteStore();
const route = unref(globalRouter.currentRoute);
clearAuthStorage();
@ -38,6 +42,11 @@ export const useAuthStore = defineStore('auth-store', {
if (route.meta.requiresAuth) {
toLogin();
}
nextTick(() => {
resetTabStore();
resetRouteStore();
});
},
/**
* token进行登录
@ -46,7 +55,7 @@ export const useAuthStore = defineStore('auth-store', {
async loginByToken(backendToken: ApiAuth.Token) {
const { toLoginRedirect } = useRouterPush(false);
// 先把token存储到缓存中
// 先把token存储到缓存中(后面接口的请求头需要token)
const { token, refreshToken } = backendToken;
setToken(token);
setRefreshToken(refreshToken);
@ -76,13 +85,12 @@ export const useAuthStore = defineStore('auth-store', {
},
/**
*
* @param phone -
* @param pwdOrCode -
* @param type - 登录方式: pwd - ; sms -
* @param userName -
* @param password -
*/
async login(phone: string, pwdOrCode: string, type: 'pwd' | 'sms') {
async login(userName: string, password: string) {
this.loginLoading = true;
const { data } = await fetchLogin(phone, pwdOrCode, type);
const { data } = await fetchLogin(userName, password);
if (data) {
await this.loginByToken(data);
}

View File

@ -43,6 +43,9 @@ export const useRouteStore = defineStore('route-store', {
cacheRoutes: []
}),
actions: {
resetRouteStore() {
this.$reset();
},
/**
*
* @param routes -
@ -53,6 +56,7 @@ export const useRouteStore = defineStore('route-store', {
this.searchMenus = transformAuthRoutesToSearchMenus(routes);
const vueRoutes = transformAuthRoutesToVueRoutes(routes);
vueRoutes.forEach(route => {
router.addRoute(route);
});

View File

@ -1,7 +1,7 @@
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router';
import { defineStore } from 'pinia';
import { useRouterPush } from '@/composables';
import { getTabRoutes } from '@/utils';
import { getTabRoutes, clearTabRoutes } from '@/utils';
import { useThemeStore } from '../theme';
import { getTabRouteByVueRoute, isInTabRoutes, getIndexInTabRoutes } from './helpers';
@ -38,6 +38,11 @@ export const useTabStore = defineStore('tab-store', {
}
},
actions: {
/** 重置Tab状态 */
resetTabStore() {
clearTabRoutes();
this.$reset();
},
/**
*
* @param path - path

View File

@ -71,18 +71,3 @@ export function getNaiveThemeOverrides(colors: Record<ColorType, string>): Globa
}
};
}
/** windicss 暗黑模式 */
export function handleWindicssDarkMode() {
const DARK_CLASS = 'dark';
function addDarkClass() {
document.documentElement.classList.add(DARK_CLASS);
}
function removeDarkClass() {
document.documentElement.classList.remove(DARK_CLASS);
}
return {
addDarkClass,
removeDarkClass
};
}

View File

@ -11,7 +11,7 @@ export default function subscribeThemeStore() {
const theme = useThemeStore();
const osTheme = useOsTheme();
const { width } = useElementSize(document.documentElement);
const { addDarkClass, removeDarkClass } = handleWindicssDarkMode();
const { addDarkClass, removeDarkClass } = handleCssDarkMode();
// 监听主题颜色
const stopThemeColor = watch(
@ -76,8 +76,8 @@ export default function subscribeThemeStore() {
});
}
/** windicss 暗黑模式 */
function handleWindicssDarkMode() {
/** css 暗黑模式 */
function handleCssDarkMode() {
const DARK_CLASS = 'dark';
function addDarkClass() {
document.documentElement.classList.add(DARK_CLASS);

View File

@ -15,8 +15,6 @@ declare namespace Auth {
userId: string;
/** 用户名 */
userName: string;
/** 用户手机号 */
userPhone: string;
/** 用户角色类型 */
userRole: RoleType;
}

View File

@ -8,9 +8,9 @@ type EnvType = 'dev' | 'test' | 'prod';
/** env环境配置 */
interface EnvConfig {
/** 请求地址 */
/** 后端的请求地址 */
url: string;
/** 代理地址 */
/** 代理标识, 用于拦截地址转发代理(如:"/api",这个和后端路径有无 "/api" 路径没有任何关系) */
proxy: string;
}

View File

@ -36,7 +36,6 @@ export function getUserInfo() {
const emptyInfo: Auth.UserInfo = {
userId: '',
userName: '',
userPhone: '',
userRole: 'user'
};
const userInfo: Auth.UserInfo = getLocal<Auth.UserInfo>(EnumStorageKey['user-info']) || emptyInfo;

View File

@ -22,7 +22,7 @@ export const formRules: CustomFormRules = {
],
pwd: [
{ required: true, message: '请输入密码' },
{ pattern: REGEXP_PWD, message: '密码为8-18位数字/字符/符号至少2种组合', trigger: 'input' }
{ pattern: REGEXP_PWD, message: '密码为6-18位数字/字符/符号至少2种组合', trigger: 'input' }
],
code: [
{ required: true, message: '请输入验证码' },

View File

@ -21,7 +21,7 @@ type Adapter<T = any> = (...args: Service.RequestResult[]) => T;
* @param adapter -
* @param args -
*/
export function adapterOfServiceResult<T extends Adapter>(adapter: T, ...args: TypeUtil.GetFunArgs<T>) {
export function serviceAdapter<T extends Adapter>(adapter: T, ...args: TypeUtil.GetFunArgs<T>) {
let result: Service.RequestResult | undefined;
const hasError = args.some(item => {

View File

@ -1,6 +1,6 @@
<template>
<n-card title="关于" :bordered="false" size="large" class="rounded-16px shadow-sm">
<p class="leading-24px">
<p class="leading-24px text-primary">
Soybean Admin 是一个基于 Vue3ViteNaive UITypeScript
的中后台解决方案它使用了最新的前端技术栈并提炼了典型的业务模型页面包括二次封装组件动态菜单权限校验粒子化权限控制等功能它可以帮助你快速搭建企业级中后台项目相信不管是从新技术使用还是其他方面都能帮助到你
</p>

View File

@ -106,11 +106,11 @@ const technology: Technology[] = [
},
{
id: 4,
name: 'WindiCSS',
name: 'UnoCSS',
description: '下一代实用优先的CSS框架',
author: 'Windicss',
site: 'https://windicss.org/',
icon: 'file-icons:windi',
author: 'Anthony Fu',
site: 'https://uno.antfu.me/?s=',
icon: 'logos:unocss',
iconColor: '#48b0f1'
},
{

View File

@ -50,9 +50,9 @@ function handleSubmit(e: MouseEvent) {
formRef.value.validate(errors => {
if (!errors) {
window.$message?.success('验证成功');
window.$message?.success('验证成功!');
} else {
window.$message?.error('验证失败');
window.$message?.error('验证失败!');
}
});
}

View File

@ -43,7 +43,6 @@ import { useSmsCode } from '@/hooks';
import { formRules, getImgCodeRule } from '@/utils';
const auth = useAuthStore();
const { login } = useAuthStore();
const { toLoginModule } = useRouterPush();
const { label, isCounting, loading: smsLoading, getSmsCode } = useSmsCode();
@ -70,8 +69,9 @@ function handleSubmit(e: MouseEvent) {
formRef.value.validate(errors => {
if (!errors) {
const { phone, code } = model;
login(phone, code, 'sms');
window.$message?.success('验证通过!');
} else {
window.$message?.error('验证失败');
}
});
}

View File

@ -0,0 +1,46 @@
<template>
<n-space :vertical="true">
<n-divider class="!mb-0 text-14px text-[#666]">其他账户登录</n-divider>
<n-space justify="center">
<n-button
v-for="item in accounts"
:key="item.userName"
type="primary"
@click="login(item.userName, item.password)"
>
{{ item.label }}
</n-button>
</n-space>
</n-space>
</template>
<script lang="ts" setup>
interface Emits {
(e: 'login', param: { userName: string; password: string }): void;
}
const emit = defineEmits<Emits>();
const accounts = [
{
label: '超级管理员',
userName: 'Super',
password: 'super123'
},
{
label: '管理员',
userName: 'Admin',
password: 'admin123'
},
{
label: '普通用户',
userName: 'User01',
password: 'user01123'
}
];
function login(userName: string, password: string) {
emit('login', { userName, password });
}
</script>
<style scoped></style>

View File

@ -1,3 +1,4 @@
import OtherLogin from './OtherLogin.vue';
import OtherAccount from './OtherAccount.vue';
export { OtherLogin };
export { OtherLogin, OtherAccount };

View File

@ -1,10 +1,10 @@
<template>
<n-form ref="formRef" :model="model" :rules="rules" size="large" :show-label="false">
<n-form-item path="phone">
<n-input v-model:value="model.phone" placeholder="请输入手机号码" />
<n-form-item path="userName">
<n-input v-model:value="model.userName" placeholder="请输入用户名" />
</n-form-item>
<n-form-item path="pwd">
<n-input v-model:value="model.pwd" type="password" show-password-on="click" placeholder="请输入密码" />
<n-form-item path="password">
<n-input v-model:value="model.password" type="password" show-password-on="click" placeholder="请输入密码" />
</n-form-item>
<n-space :vertical="true" :size="24">
<div class="flex-y-center justify-between">
@ -31,7 +31,7 @@
</n-button>
</div>
</n-space>
<other-login />
<other-account @login="handleLoginOtherAccount" />
</n-form>
</template>
@ -42,7 +42,7 @@ import { EnumLoginModule } from '@/enum';
import { useAuthStore } from '@/store';
import { useRouterPush } from '@/composables';
import { formRules } from '@/utils';
import { OtherLogin } from './components';
import { OtherAccount } from './components';
const auth = useAuthStore();
const { login } = useAuthStore();
@ -50,12 +50,11 @@ const { toLoginModule } = useRouterPush();
const formRef = ref<(HTMLElement & FormInst) | null>(null);
const model = reactive({
phone: '15170283876',
pwd: 'abc123456'
userName: 'Soybean',
password: 'soybean123'
});
const rules: FormRules = {
phone: formRules.phone,
pwd: formRules.pwd
password: formRules.pwd
};
const rememberMe = ref(false);
@ -65,10 +64,15 @@ function handleSubmit(e: MouseEvent) {
formRef.value.validate(errors => {
if (!errors) {
const { phone, pwd } = model;
login(phone, pwd, 'pwd');
const { userName, password } = model;
login(userName, password);
}
});
}
function handleLoginOtherAccount(param: { userName: string; password: string }) {
const { userName, password } = param;
login(userName, password);
}
</script>
<style scoped></style>

View File

@ -63,9 +63,9 @@ function handleSubmit(e: MouseEvent) {
formRef.value.validate(errors => {
if (!errors) {
if (!agreement.value) return;
window.$message?.success('验证成功');
window.$message?.success('验证成功!');
} else {
window.$message?.error('验证失败');
window.$message?.error('验证失败!');
}
});
}

View File

@ -1,6 +1,7 @@
import { defineConfig, presetMini } from 'unocss';
export default defineConfig({
exclude: ['node_modules', '.git', './stats.html'],
presets: [presetMini({ dark: 'class' })],
shortcuts: {
'wh-full': 'w-full h-full',