This commit is contained in:
zxx 2022-10-11 11:06:24 +08:00
parent e121ad5143
commit cb187d0b74
30 changed files with 967 additions and 457 deletions

2
.env
View File

@ -7,4 +7,4 @@ VUE_APP_USER_KEY=admin.user
VUE_APP_SETTING_KEY=admin.setting
VUE_APP_TBAS_KEY=admin.tabs
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles
VUE_APP_API_BASE_URL=http://api.iczer.com
VUE_APP_API_BASE_URL=http://192.168.31.90:19000

View File

@ -1 +1 @@
VUE_APP_API_BASE_URL=http://dev.iczer.com
VUE_APP_API_BASE_URL=http://192.168.31.90:19000

82
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,82 @@
stages: # List of stages for jobs, and their order of execution
- build
- deploy-dev
# - deploy-test
- deploy-prod
variables:
REGISTRY_GITLAB: registry.gitlab.com/seasoul/mes-antd-admin
BUILD_NUMBER: ${CI_COMMIT_SHORT_SHA}-${CI_PIPELINE_ID}
KUBE_CONFIG: /etc/deploy/config
cache:
paths:
- ./mod_cache/
build-rpc-job: # This job runs in the build stage, which runs first.
stage: build
image: tico/docker:latest
tags:
- k8s-runner
variables:
NAME: antd-admin
PORT: 8080
APP_NAME: antd-admin
script:
- ls
- mkdir -p ./.ssh
- mv $SSH_PRIVATE_KEY ./.ssh/id_rsa
- chmod 600 ./.ssh/id_rsa
- docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com
# 如果是登录私有仓库则需要更换如下代码:
- docker build -f Dockerfile -t $APP_NAME:v${BUILD_NUMBER} . --network=host #编译成镜像
- docker tag $APP_NAME:v${BUILD_NUMBER} $REGISTRY_GITLAB/$APP_NAME:v${BUILD_NUMBER} #将镜像加上tag
- docker push $REGISTRY_GITLAB/$APP_NAME:v${BUILD_NUMBER} #提交到镜像仓库
- docker tag $APP_NAME:v${BUILD_NUMBER} $REGISTRY_GITLAB/$APP_NAME:latest #将镜像加上tag
- docker push $REGISTRY_GITLAB/$APP_NAME:latest #提交到镜像仓库
after_script:
- docker rmi $APP_NAME:v${BUILD_NUMBER} #删除镜像
- docker rmi $REGISTRY_GITLAB/$APP_NAME:v${BUILD_NUMBER} #删除镜像
- docker images|grep none|awk '{print $3}'|xargs docker rmi # 清理无tag的镜像
# - docker rm $(docker ps --all -q -f status=exited) # 清理容器
deploy-rpc-dev-job:
stage: deploy-dev
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/kubectl:1.16.6
tags:
- k8s-runner
script:
- mkdir -p /etc/deploy
- echo $kube_config |base64 -d > $KUBE_CONFIG
- kubectl get nodes
- kubectl apply -f deploy/mes-antd-admin-dev.yaml # 部署测试环境
# deploy-rpc-test-job:
# stage: deploy-dev
# image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/kubectl:1.16.6
# tags:
# - k8s-runner
# script:
# - mkdir -p /etc/deploy
# - mv $kube_config $KUBE_CONFIG
# - kubectl get nodes
# - kubectl apply -f deploy/mes-antd-admin-test.yaml # 部署灰度测试环境
deploy-rpc-prod-job:
stage: deploy-prod
image: registry.cn-hangzhou.aliyuncs.com/haoshuwei24/kubectl:1.16.6
tags:
- k8s-runner
script:
- mkdir -p /etc/deploy
- echo $kube_config |base64 -d > $KUBE_CONFIG
- kubectl get nodes
- kubectl apply -f deploy/mes-antd-admin-prod.yaml # 部署正式环境

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
# build stage
FROM node:lts-alpine as build-stage
ENV API_SERVER_HOST=http://zxx4.f3322.net:30880/api/middle/
WORKDIR /app
COPY . .
RUN yarn install && yarn run build
# production stage
FROM nginx:stable-alpine as production-stage
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mes-antd-admin
namespace: sealers-dev
labels:
app: mes-antd-admin
spec:
replicas: 1
revisionHistoryLimit: 5
selector:
matchLabels:
app: mes-antd-admin
template:
metadata:
labels:
app: mes-antd-admin
spec:
containers:
- name: bmes-antd-admin
image: registry.gitlab.com/seasoul/mes-antd-admin:latest
imagePullPolicy: Always
ports:
- containerPort: 80
readinessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
volumeMounts:
- name: timezone
mountPath: /etc/localtime
# ============EnvSetttingStart===========
# 该内容为gozero-batch生成的内容请不要手动修改
env: # 环境变量
- name: API_SERVER_HOST # Api服务地址
value: http://zxx4.f3322.net # 请在files-settings.yaml中修改
# 该内容为gozero-batch生成的内容请不要手动修改
# ============EnvSetttingEnd===========
imagePullSecrets:
- name: gitlab
volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
---
apiVersion: v1
kind: Service
metadata:
name: mes-antd-admin-svc
namespace: sealers-dev
spec:
ports:
- port: 19001
targetPort: 19001
selector:
app: mes-antd-admin
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-c
namespace: sealers-dev
labels:
app: mes-antd-admin-hpa-c
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-m
namespace: sealers-dev
labels:
app: mes-antd-admin-hpa-m
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: memory
targetAverageUtilization: 80

View File

@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mes-antd-admin
namespace: sealers-prod
labels:
app: mes-antd-admin
spec:
replicas: 1
revisionHistoryLimit: 5
selector:
matchLabels:
app: mes-antd-admin
template:
metadata:
labels:
app: mes-antd-admin
spec:
containers:
- name: bmes-antd-admin
image: registry.gitlab.com/seasoul/mes-antd-admin:latest
imagePullPolicy: Always
ports:
- containerPort: 80
readinessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
volumeMounts:
- name: timezone
mountPath: /etc/localtime
# ============EnvSetttingStart===========
# 该内容为gozero-batch生成的内容请不要手动修改
env: # 环境变量
- name: API_SERVER_HOST # Api服务地址
value: http://zxx4.f3322.net # 请在files-settings.yaml中修改
# 该内容为gozero-batch生成的内容请不要手动修改
# ============EnvSetttingEnd===========
imagePullSecrets:
- name: gitlab
volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
---
apiVersion: v1
kind: Service
metadata:
name: mes-antd-admin-svc
namespace: sealers-prod
spec:
ports:
- port: 19001
targetPort: 19001
selector:
app: mes-antd-admin
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-c
namespace: sealers-prod
labels:
app: mes-antd-admin-hpa-c
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-m
namespace: sealers-prod
labels:
app: mes-antd-admin-hpa-m
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: memory
targetAverageUtilization: 80

View File

@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mes-antd-admin
namespace: sealers-test
labels:
app: mes-antd-admin
spec:
replicas: 1
revisionHistoryLimit: 5
selector:
matchLabels:
app: mes-antd-admin
template:
metadata:
labels:
app: mes-antd-admin
spec:
containers:
- name: bmes-antd-admin
image: registry.gitlab.com/seasoul/mes-antd-admin:latest
imagePullPolicy: Always
ports:
- containerPort: 80
readinessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
volumeMounts:
- name: timezone
mountPath: /etc/localtime
# ============EnvSetttingStart===========
# 该内容为gozero-batch生成的内容请不要手动修改
env: # 环境变量
- name: API_SERVER_HOST # Api服务地址
value: http://zxx4.f3322.net # 请在files-settings.yaml中修改
# 该内容为gozero-batch生成的内容请不要手动修改
# ============EnvSetttingEnd===========
imagePullSecrets:
- name: gitlab
volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
---
apiVersion: v1
kind: Service
metadata:
name: mes-antd-admin-svc
namespace: sealers-test
spec:
ports:
- port: 19001
targetPort: 19001
selector:
app: mes-antd-admin
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-c
namespace: sealers-test
labels:
app: mes-antd-admin-hpa-c
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
---
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: mes-antd-admin-hpa-m
namespace: sealers-test
labels:
app: mes-antd-admin-hpa-m
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mes-antd-admin
minReplicas: 1
maxReplicas: 50
metrics:
- type: Resource
resource:
name: memory
targetAverageUtilization: 80

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
version: '3.5'
networks:
backend:
driver: ${NETWORKS_DRIVER}
services:
mes-antd-admin:
container_name: mes-antd-admin
image: registry.gitlab.com/seasoul/mes-antd-admin:latest
# depends_on: # 依赖容器
# - app_config-rpc # 在 app_config-rpc 服务容器启动后启动
restart: always
ports: # 设置端口映射
- "80:80"
network_mode: ${NETWORKS_DRIVER}
environment: # 设置环境变量
- "API_SERVER_HOST=${API_SERVER_HOST}"

55
nginx/nginx.conf Normal file
View File

@ -0,0 +1,55 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 65535;
use epoll;
epoll_events 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
access_log /var/log/nginx/host.access.log main;
error_log /var/log/nginx/error.log error;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}

6
src/bootstrap.js vendored
View File

@ -3,13 +3,15 @@ import {loadInterceptors} from '@/utils/request'
import guards from '@/router/guards'
import interceptors from '@/utils/axios-interceptors'
/**
* 启动引导方法
* 应用启动时需要执行的操作放在这里
* @param router 应用的路由实例
* @param store 应用的 vuex.store 实例
* @param i18n 应用的 vue-i18n 实例
* @param i18n 应用的 message 实例
* @param message 应用的 message 实例
*/
function bootstrap({router, store, i18n, message}) {
// 设置应用配置
@ -19,7 +21,7 @@ function bootstrap({router, store, i18n, message}) {
// 加载路由
loadRoutes()
// 加载路由守卫
loadGuards(guards, {router, store, i18n, message})
loadGuards(guards, {router, store, i18n, message})// 权限配置
}
export default bootstrap

View File

@ -38,14 +38,14 @@ export default {
}
.content {
flex: 1 1 0;
:global {
.ant-form-item:last-child {
margin-right: 0;
}
.ant-form-item {
margin-bottom: 0px;
}
}
// :global {
// .ant-form-item:last-child {
// margin-right: 0;
// }
// .ant-form-item {
// margin-bottom: 0px;
// }
// }
}
}
</style>

View File

@ -49,11 +49,11 @@ export default {
.step-item{
cursor: pointer;
}
:global{
.ant-steps-item-process{
.linkable{
color: @primary-color;
}
}
}
// :global{
// .ant-steps-item-process{
// .linkable{
// color: @primary-color;
// }
// }
// }
</style>

View File

@ -57,13 +57,13 @@ export default {
margin-left: -8px;
width: 20px;
height: 20px;
:global {
.ant-avatar {
border: 1px solid #fff;
width: 20px;
height: 20px;
}
}
// :global {
// .ant-avatar {
// border: 1px solid #fff;
// width: 20px;
// height: 20px;
// }
// }
}
}
</style>

View File

@ -5,7 +5,7 @@ module.exports = {
mode: 'dark',
},
multiPage: true,
// asyncRoutes: true, //异步加载路由true:开启false:不开启
asyncRoutes: true, //异步加载路由true:开启false:不开启
animate: {
name: 'lightSpeed',
direction: 'left'

View File

@ -60,9 +60,11 @@
},
inject:['adminLayout'],
created() {
this.affixed = this.fixedTabs
},
computed: {
...mapState('setting', ['layout', 'pageWidth', 'fixedHeader', 'fixedTabs', 'customTitles']),
lockTitle() {
return this.$t(this.fixedTabs ? 'unlock' : 'lock')

View File

@ -17,10 +17,6 @@ Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/login`, 'post', ({body}) => {
success = true
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
result.data.roles = [{id: 'admin', operation: ['add', 'edit', 'delete']}]
} else if (name === 'test' || password === '888888') {
success = true
result.data.permissions = [{id: 'queryForm', operation: ['add', 'edit']}]
result.data.roles = [{id: 'test', operation: ['add', 'edit', 'delete']}]
} else {
success = false
}

View File

@ -73,11 +73,11 @@ export default {
<style lang="less" scoped>
.stepFormText {
margin-bottom: 24px;
:global {
.ant-form-item-label,
.ant-form-item-control {
line-height: 22px;
}
}
// :global {
// .ant-form-item-label,
// .ant-form-item-control {
// line-height: 22px;
// }
// }
}
</style>

View File

@ -96,7 +96,7 @@ export default {
}
},
methods: {
...mapMutations('account', ['setUser', 'setPermissions', 'setRoles']),
...mapMutations('account', ['setUser']),
onSubmit (e) {
e.preventDefault()
this.form.validateFields((err) => {
@ -109,20 +109,24 @@ export default {
})
},
afterLogin(res) {
console.log(res)
this.logging = false
const loginRes = res.data
if (loginRes.code >= 0) {
const {user, permissions, roles} = loginRes.data
if (loginRes.code == 200) {
const user = loginRes.data.uid
console.log("user",user)
console.log("loginRes.data.access_expire",loginRes.data.access_expire)
this.setUser(user)
this.setPermissions(permissions)
this.setRoles(roles)
setAuthorization({token: loginRes.data.token, expireAt: new Date(loginRes.data.expireAt)})
// this.setPermissions(permissions)
// this.setRoles(roles)
setAuthorization({token: loginRes.data.access_token, expireAt: new Date(loginRes.data.access_expire*1000)})
//
getRoutesConfig().then(result => {
const routesConfig = result.data.data
const routesConfig = result.data.data.router
loadRoutes(routesConfig)
this.$router.push('/dashboard/workplace')
this.$message.success(loginRes.message, 3)
console.log("开始跳转")
this.$router.push('/')
this.$message.success(loginRes.msg, 3)
})
} else {
this.error = loginRes.message

View File

@ -1,6 +1,6 @@
import routerMap from './router.map'
import {parseRoutes} from '@/utils/routerUtil'
// 异步路由中的root为根路由
// 异步路由配置
const routesConfig = [
'login',
@ -14,7 +14,8 @@ const routesConfig = [
router: 'exp403',
path: '/403',
name: '403'
}
},
]
const options = {

View File

@ -275,204 +275,204 @@ const options = {
// {
// path: 'list',
// name: '列表页',
// meta: {
// icon: 'table'
// },
// component: PageView,
// children: [
// {
// path: 'query',
// name: '查询表格',
// meta: {
// authority: 'queryForm',
// },
// component: () => import('@/pages/list/QueryList'),
// },
// {
// path: 'query/detail/:id',
// name: '查询详情',
// meta: {
// highlight: '/list/query',
// invisible: true
// },
// component: () => import('@/pages/Demo')
// },
// {
// path: 'primary',
// name: '标准列表',
// component: () => import('@/pages/list/StandardList'),
// },
// {
// path: 'card',
// name: '卡片列表',
// component: () => import('@/pages/list/CardList'),
// },
// {
// path: 'search',
// name: '搜索列表',
// component: () => import('@/pages/list/search/SearchLayout'),
// children: [
// {
// path: 'article',
// name: '文章',
// component: () => import('@/pages/list/search/ArticleList'),
// },
// {
// path: 'application',
// name: '应用',
// component: () => import('@/pages/list/search/ApplicationList'),
// },
// {
// path: 'project',
// name: '项目',
// component: () => import('@/pages/list/search/ProjectList'),
// }
// ]
// }
// ]
// },
// {
// path: 'details',
// name: '详情页',
// meta: {
// icon: 'profile'
// },
// component: BlankView,
// children: [
// {
// path: 'basic',
// name: '基础详情页',
// component: () => import('@/pages/detail/BasicDetail')
// },
// {
// path: 'advance',
// name: '高级详情页',
// component: () => import('@/pages/detail/AdvancedDetail')
// }
// ]
// },
// {
// path: 'result',
// name: '结果页',
// meta: {
// icon: 'check-circle-o',
// },
// component: PageView,
// children: [
// {
// path: 'success',
// name: '成功',
// component: () => import('@/pages/result/Success')
// },
// {
// path: 'error',
// name: '失败',
// component: () => import('@/pages/result/Error')
// }
// ]
// },
// {
// path: 'exception',
// name: '异常页',
// meta: {
// icon: 'warning',
// },
// component: BlankView,
// children: [
// {
// path: '404',
// name: 'Exp404',
// component: () => import('@/pages/exception/404')
// },
// {
// path: '403',
// name: 'Exp403',
// component: () => import('@/pages/exception/403')
// },
// {
// path: '500',
// name: 'Exp500',
// component: () => import('@/pages/exception/500')
// }
// ]
// },
// {
// path: 'components',
// name: '内置组件',
// meta: {
// icon: 'appstore-o'
// },
// component: PageView,
// children: [
// {
// path: 'taskCard',
// name: '任务卡片',
// component: () => import('@/pages/components/TaskCard')
// },
// {
// path: 'palette',
// name: '颜色复选框',
// component: () => import('@/pages/components/Palette')
// },
// {
// path: 'table',
// name: '高级表格',
// component: () => import('@/pages/components/table')
// }
// ]
// },
// {
// name: '验权表单',
// path: 'auth/form',
// meta: {
// icon: 'file-excel',
// authority: {
// permission: 'form'
// }
// },
// component: () => import('@/pages/form/basic')
// },
// {
// name: '带参菜单',
// path: 'router/query',
// meta: {
// icon: 'project',
// query: {
// name: '菜单默认参数'
// }
// },
// component: () => import('@/pages/Demo')
// },
// {
// name: '动态路由菜单',
// path: 'router/dynamic/:id',
// meta: {
// icon: 'project',
// params: {
// id: 123
// }
// },
// component: () => import('@/pages/Demo')
// },
// {
// name: 'Ant Design Vue',
// path: 'antdv',
// meta: {
// icon: 'ant-design',
// link: 'https://www.antdv.com/docs/vue/introduce-cn/'
// }
// },
// {
// name: '使用文档',
// path: 'document',
// meta: {
// icon: 'file-word',
// link: 'https://iczer.gitee.io/vue-antd-admin-docs/'
// }
// }
{
path: 'list',
name: '列表页',
meta: {
icon: 'table'
},
component: PageView,
children: [
{
path: 'query',
name: '查询表格',
meta: {
authority: 'queryForm',
},
component: () => import('@/pages/list/QueryList'),
},
{
path: 'query/detail/:id',
name: '查询详情',
meta: {
highlight: '/list/query',
invisible: true
},
component: () => import('@/pages/Demo')
},
{
path: 'primary',
name: '标准列表',
component: () => import('@/pages/list/StandardList'),
},
{
path: 'card',
name: '卡片列表',
component: () => import('@/pages/list/CardList'),
},
{
path: 'search',
name: '搜索列表',
component: () => import('@/pages/list/search/SearchLayout'),
children: [
{
path: 'article',
name: '文章',
component: () => import('@/pages/list/search/ArticleList'),
},
{
path: 'application',
name: '应用',
component: () => import('@/pages/list/search/ApplicationList'),
},
{
path: 'project',
name: '项目',
component: () => import('@/pages/list/search/ProjectList'),
}
]
}
]
},
{
path: 'details',
name: '详情页',
meta: {
icon: 'profile'
},
component: BlankView,
children: [
{
path: 'basic',
name: '基础详情页',
component: () => import('@/pages/detail/BasicDetail')
},
{
path: 'advance',
name: '高级详情页',
component: () => import('@/pages/detail/AdvancedDetail')
}
]
},
{
path: 'result',
name: '结果页',
meta: {
icon: 'check-circle-o',
},
component: PageView,
children: [
{
path: 'success',
name: '成功',
component: () => import('@/pages/result/Success')
},
{
path: 'error',
name: '失败',
component: () => import('@/pages/result/Error')
}
]
},
{
path: 'exception',
name: '异常页',
meta: {
icon: 'warning',
},
component: BlankView,
children: [
{
path: '404',
name: 'Exp404',
component: () => import('@/pages/exception/404')
},
{
path: '403',
name: 'Exp403',
component: () => import('@/pages/exception/403')
},
{
path: '500',
name: 'Exp500',
component: () => import('@/pages/exception/500')
}
]
},
{
path: 'components',
name: '内置组件',
meta: {
icon: 'appstore-o'
},
component: PageView,
children: [
{
path: 'taskCard',
name: '任务卡片',
component: () => import('@/pages/components/TaskCard')
},
{
path: 'palette',
name: '颜色复选框',
component: () => import('@/pages/components/Palette')
},
{
path: 'table',
name: '高级表格',
component: () => import('@/pages/components/table')
}
]
},
{
name: '验权表单',
path: 'auth/form',
meta: {
icon: 'file-excel',
authority: {
permission: 'form'
}
},
component: () => import('@/pages/form/basic')
},
{
name: '带参菜单',
path: 'router/query',
meta: {
icon: 'project',
query: {
name: '菜单默认参数'
}
},
component: () => import('@/pages/Demo')
},
{
name: '动态路由菜单',
path: 'router/dynamic/:id',
meta: {
icon: 'project',
params: {
id: 123
}
},
component: () => import('@/pages/Demo')
},
{
name: 'Ant Design Vue',
path: 'antdv',
meta: {
icon: 'ant-design',
link: 'https://www.antdv.com/docs/vue/introduce-cn/'
}
},
{
name: '使用文档',
path: 'document',
meta: {
icon: 'file-word',
link: 'https://iczer.gitee.io/vue-antd-admin-docs/'
}
}
]
},
]

View File

@ -1,9 +1,9 @@
import {hasAuthority} from '@/utils/authority-utils'
import {loginIgnore} from '@/router/index'
import {checkAuthorization} from '@/utils/request'
import NProgress from 'nprogress'
import {hasAuthority} from '@/utils/authority-utils' // 权限判断
import {loginIgnore} from '@/router/index' // 不用登录白名单
import {checkAuthorization} from '@/utils/request' // 登录校验
import NProgress from 'nprogress' // 进度条
NProgress.configure({ showSpinner: false })
NProgress.configure({ showSpinner: false }) // 进度条配置 是否显示加载ico
/**
* 进度条开始
@ -11,12 +11,12 @@ NProgress.configure({ showSpinner: false })
* @param form
* @param next
*/
const progressStart = (to, from, next) => {
const progressStart = (to, from, next) => {
// start progress bar
if (!NProgress.isStarted()) {
NProgress.start()
if (!NProgress.isStarted()) { // 如果进度条没有开始
NProgress.start() // 进度条开始
}
next()
next() // 正常跳转
}
/**
@ -27,12 +27,12 @@ const progressStart = (to, from, next) => {
* @param options
*/
const loginGuard = (to, from, next, options) => {
const {message} = options
if (!loginIgnore.includes(to) && !checkAuthorization()) {
const {message} = options // 获取message
if (!loginIgnore.includes(to) && !checkAuthorization()) { // 如果不是白名单 并且登录校验失败
message.warning('登录已失效,请重新登录')
next({path: '/login'})
next({path: '/login'}) // 跳转到登录页面
} else {
next()
next() // 如果是白名单,正常跳转
}
}
@ -43,16 +43,17 @@ const loginGuard = (to, from, next, options) => {
* @param next
* @param options
*/
const authorityGuard = (to, from, next, options) => {
const {store, message} = options
const permissions = store.getters['account/permissions']
const roles = store.getters['account/roles']
if (!hasAuthority(to, permissions, roles)) {
const permissions = store.getters['account/permissions'] // 获取权限数据
if (!hasAuthority(to, permissions)) { // 判断是否有权限
// 如果没有权限跳转到403页面
message.warning(`对不起,您无权访问页面: ${to.fullPath},请联系管理员`)
next({path: '/403'})
// NProgress.done()
} else {
next()
next() // 如果有权限,正常跳转到你设置好的页面
}
}
@ -65,22 +66,22 @@ const authorityGuard = (to, from, next, options) => {
* @returns {*}
*/
const redirectGuard = (to, from, next, options) => {
const {store} = options
const getFirstChild = (routes) => {
const route = routes[0]
if (!route.children || route.children.length === 0) {
const {store} = options // 获取store
const getFirstChild = (routes) => { // 获取第一个子路由
const route = routes[0] // 获取第一个路由
if (!route.children || route.children.length === 0) { // 如果没有子路由,返回当前路由
return route
}
return getFirstChild(route.children)
return getFirstChild(route.children) // 如果有子路由,递归获取第一个子路由
}
if (store.state.setting.layout === 'mix') {
const firstMenu = store.getters['setting/firstMenu']
if (firstMenu.find(item => item.fullPath === to.fullPath)) {
store.commit('setting/setActivatedFirst', to.fullPath)
const subMenu = store.getters['setting/subMenu']
if (subMenu.length > 0) {
const redirect = getFirstChild(subMenu)
return next({path: redirect.fullPath})
if (store.state.setting.layout === 'mix') { // 如果是混合导航模式
const firstMenu = store.getters['setting/firstMenu'] // 获取第一个菜单
if (firstMenu.find(item => item.fullPath === to.fullPath)) { // 如果是第一个菜单列表,
store.commit('setting/setActivatedFirst', to.fullPath) // 设置激活的第一个菜单
const subMenu = store.getters['setting/subMenu'] // 获取第一个菜单的子菜单
if (subMenu.length > 0) { // 如果有子菜单
const redirect = getFirstChild(subMenu) // 获取第一个子菜单
return next({path: redirect.fullPath}) // 重定向到第一个子菜单
}
}
}
@ -93,11 +94,12 @@ const redirectGuard = (to, from, next, options) => {
* @param form
* @param options
*/
const progressDone = () => {
const progressDone = () => { // 进度条结束
// finish progress bar
NProgress.done()
NProgress.done() // 进度条结束
}
// guards 是登录守卫,权限守卫,进度条守卫,重定向守卫的集合
export default {
beforeEach: [progressStart, loginGuard, authorityGuard, redirectGuard],
afterEach: [progressDone]

View File

@ -1,11 +1,11 @@
import Vue from 'vue'
import Router from 'vue-router'
import {formatRoutes} from '@/utils/routerUtil'
import {formatRoutes} from '@/utils/routerUtil' // 引入路由格式化工具
Vue.use(Router)
// 不需要登录拦截的路由配置
const loginIgnore = {
const loginIgnore = { // 登录白名单
names: ['404', '403'], //根据路由名称匹配
paths: ['/login'], //根据路由fullPath匹配
/**
@ -13,19 +13,26 @@ const loginIgnore = {
* @param route vue-router route 对象
* @returns {boolean}
*/
includes(route) {
return this.names.includes(route.name) || this.paths.includes(route.path)
includes(route) { // 判断路由是否包含在该配置中
return this.names.includes(route.name) || this.paths.includes(route.path) // 返回true则不需要登录拦截
}
}
// 解决重复点击路由报错的BUG
const originalPush = Router.prototype.push // 保存原型对象中的push方法
Router.prototype.push = function push(location) { // 重写原型对象中的push方法
return originalPush.call(this, location).catch((err) => err) // 抛出错误
}
/**
* 初始化路由实例
* @param isAsync 是否异步路由模式
* @returns {VueRouter}
*/
function initRouter(isAsync) {
const options = isAsync ? require('./async/config.async').default : require('./config').default
formatRoutes(options.routes)
return new Router(options)
function initRouter(isAsync) { // 初始化路由
const options = isAsync ? require('./async/config.async').default : require('./config').default // 引入路由配置
formatRoutes(options.routes) // 格式化路由
return new Router(options) // 返回路由实例
}
export {loginIgnore, initRouter}
export {loginIgnore, initRouter} // 导出路由配置和初始化路由方法

View File

@ -1,10 +1,10 @@
//跨域代理前缀
// const API_PROXY_PREFIX='/api'
// const BASE_URL = process.env.NODE_ENV === 'production' ? process.env.VUE_APP_API_BASE_URL : API_PROXY_PREFIX
const BASE_URL = process.env.VUE_APP_API_BASE_URL
const BASE_URL = process.env.VUE_APP_API_BASE_URL // 获取环境变量VUE_APP_API_BASE_URL
module.exports = {
LOGIN: `${BASE_URL}/login`,
ROUTES: `${BASE_URL}/routes`,
GOODS: `${BASE_URL}/goods`,
GOODS_COLUMNS: `${BASE_URL}/columns`,
LOGIN: `${BASE_URL}/AdminUser/v1/login`,
ROUTES: `${BASE_URL}/AdminUser/v1/routers`,
USERINFO: `${BASE_URL}/AdminUser/v1/userinfo`,
PERMISSION: `${BASE_URL}/AdminUser/v1/permission`,
}

View File

@ -1,4 +1,4 @@
import {GOODS, GOODS_COLUMNS} from './api'
import {GOODS, GOODS_COLUMNS} from './api' // 引入api.js中的常量
import {METHOD, request} from '@/utils/request'
export async function goodsList(params) {

View File

@ -1,6 +1,5 @@
import {LOGIN, ROUTES} from '@/services/api'
import {LOGIN, ROUTES, USERINFO,PERMISSION} from '@/services/api'
import {request, METHOD, removeAuthorization} from '@/utils/request'
/**
* 登录服务
* @param name 账户名
@ -9,13 +8,42 @@ import {request, METHOD, removeAuthorization} from '@/utils/request'
*/
export async function login(name, password) {
return request(LOGIN, METHOD.POST, {
name: name,
mobile: name,
password: password
})
}
/**
* 获取用户信息
* @param uid
* @returns {Promise<AxiosResponse<T>>}
*/
export async function getUserInfo() {
const userId = parseInt(localStorage.getItem(process.env.VUE_APP_USER_KEY)) // 获取用户id并转换为整数
return request(USERINFO, METHOD.POST, {uid: userId})
}
/**
* 获取路由配置
* @param uid
* @returns {Promise<AxiosResponse<T>>}
*/
export async function getRoutesConfig() {
return request(ROUTES, METHOD.GET)
const userId = parseInt(localStorage.getItem(process.env.VUE_APP_USER_KEY)) // 获取用户id并转换为整数
return request(ROUTES, METHOD.POST, {
uid:userId
})
}
/**
* 获取权限配置
* @param uid
* @returns {Promise<AxiosResponse<T>>}
*/
export async function getPermission() {
const userId = parseInt(localStorage.getItem(process.env.VUE_APP_USER_KEY)) // 获取用户id并转换为整数
return request(PERMISSION, METHOD.POST, {
uid:userId
})
}
/**

View File

@ -1,76 +1,60 @@
export default {
namespaced: true,
state: {
user: undefined,
permissions: null,
roles: null,
routesConfig: null
user: undefined, // 用户信息
permissions: null, // 权限
routesConfig: null // 路由配置
},
getters: {
user: state => {
if (!state.user) {
user: state => { // 用户信息
if (!state.user) { // 如果没有用户信息
try {
const user = localStorage.getItem(process.env.VUE_APP_USER_KEY)
state.user = JSON.parse(user)
} catch (e) {
const user = localStorage.getItem(process.env.VUE_APP_USER_KEY) // 获取用户信息
state.user = user // 将用户信息赋值给state.user
} catch (e) { // 捕获异常
console.error(e)
}
}
return state.user
return state.user // 如果有用户信息,直接返回
},
permissions: state => {
if (!state.permissions) {
permissions: state => { // 权限
if (!state.permissions) { // 如果没有权限
try {
const permissions = localStorage.getItem(process.env.VUE_APP_PERMISSIONS_KEY)
state.permissions = JSON.parse(permissions)
state.permissions = state.permissions ? state.permissions : []
} catch (e) {
const permissions = localStorage.getItem(process.env.VUE_APP_PERMISSIONS_KEY) // 获取权限
state.permissions = JSON.parse(permissions) // 将权限赋值给state.permissions
state.permissions = state.permissions ? state.permissions : [] // 如果state.permissions为空则赋值为空数组
} catch (e) { // 捕获异常
console.error(e.message)
}
}
return state.permissions
return state.permissions // 如果有权限,则返回权限
},
roles: state => {
if (!state.roles) {
routesConfig: state => { // 路由配置
if (!state.routesConfig) { // 如果没有路由配置
try {
const roles = localStorage.getItem(process.env.VUE_APP_ROLES_KEY)
state.roles = JSON.parse(roles)
state.roles = state.roles ? state.roles : []
} catch (e) {
const routesConfig = localStorage.getItem(process.env.VUE_APP_ROUTES_KEY) // 获取路由配置
state.routesConfig = JSON.parse(routesConfig) // 将字符串转换为json对象
state.routesConfig = state.routesConfig ? state.routesConfig : [] // 如果state.routesConfig为null则赋值为空数组
} catch (e) { // 捕获异常
console.error(e.message)
}
}
return state.roles
},
routesConfig: state => {
if (!state.routesConfig) {
try {
const routesConfig = localStorage.getItem(process.env.VUE_APP_ROUTES_KEY)
state.routesConfig = JSON.parse(routesConfig)
state.routesConfig = state.routesConfig ? state.routesConfig : []
} catch (e) {
console.error(e.message)
}
}
return state.routesConfig
return state.routesConfig // 如果有路由配置,则返回路由配置
}
},
mutations: {
setUser (state, user) {
state.user = user
localStorage.setItem(process.env.VUE_APP_USER_KEY, JSON.stringify(user))
setUser (state, user) { // 设置用户信息
state.user = user
localStorage.setItem(process.env.VUE_APP_USER_KEY, user)// 将所有用户信息存储到process.env.VUE_APP_USER_KEY中
},
setPermissions(state, permissions) {
setPermissions(state, permissions) { // 设置权限
state.permissions = permissions
localStorage.setItem(process.env.VUE_APP_PERMISSIONS_KEY, JSON.stringify(permissions))
localStorage.setItem(process.env.VUE_APP_PERMISSIONS_KEY, JSON.stringify(permissions)) // 将所有权限信息存储到process.env.VUE_APP_PERMISSIONS_KEY中
},
setRoles(state, roles) {
state.roles = roles
localStorage.setItem(process.env.VUE_APP_ROLES_KEY, JSON.stringify(roles))
},
setRoutesConfig(state, routesConfig) {
setRoutesConfig(state, routesConfig) { // 设置路由配置
state.routesConfig = routesConfig
localStorage.setItem(process.env.VUE_APP_ROUTES_KEY, JSON.stringify(routesConfig))
localStorage.setItem(process.env.VUE_APP_ROUTES_KEY, JSON.stringify(routesConfig)) // 将所有路由信息存储到process.env.VUE_APP_ROUTES_KEY中
}
}
}

View File

@ -1,113 +1,113 @@
import config from '@/config'
import {ADMIN} from '@/config/default'
import {formatFullPath} from '@/utils/i18n'
import {filterMenu} from '@/utils/authority-utils'
import {getLocalSetting} from '@/utils/themeUtil'
import deepClone from 'lodash.clonedeep'
import config from '@/config' // 引入配置文件
import {ADMIN} from '@/config/default' // 默认配置
import {formatFullPath} from '@/utils/i18n' // 路由国际化
import {filterMenu} from '@/utils/authority-utils' // 菜单权限过滤
import {getLocalSetting} from '@/utils/themeUtil' // 主题配置
import deepClone from 'lodash.clonedeep' // 深拷贝
const localSetting = getLocalSetting(true)
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY)
const localSetting = getLocalSetting(true) // 获取本地主题配置
const customTitlesStr = sessionStorage.getItem(process.env.VUE_APP_TBAS_TITLES_KEY) // 获取本地自定义标题
const customTitles = (customTitlesStr && JSON.parse(customTitlesStr)) || []
export default {
namespaced: true,
state: {
isMobile: false,
animates: ADMIN.animates,
palettes: ADMIN.palettes,
pageMinHeight: 0,
menuData: [],
activatedFirst: undefined,
customTitles,
...config,
...localSetting
namespaced: true, // 命名空间
state: { // 状态
isMobile: false, // 是否是移动端
animates: ADMIN.animates, // 动画
palettes: ADMIN.palettes, // 主题
pageMinHeight: 0, // 页面最小高度
menuData: [], // 菜单数据
activatedFirst: undefined, // 激活的第一个菜单
customTitles, // 自定义标题
...config, // 配置文件
...localSetting // 本地主题配置
},
getters: {
menuData(state, getters, rootState) {
menuData(state, getters, rootState) { // 菜单数据
if (state.filterMenu) {
const {permissions, roles} = rootState.account
return filterMenu(deepClone(state.menuData), permissions, roles)
return filterMenu(deepClone(state.menuData), permissions, roles) // 过滤菜单 权限 和 角色
}
return state.menuData
},
firstMenu(state, getters) {
const {menuData} = getters
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData)
firstMenu(state, getters) { // 第一个菜单
const {menuData} = getters // 从getters中获取菜单数据
if (menuData.length > 0 && !menuData[0].fullPath) { // 如果菜单数据长度大于0 并且 菜单数据第一个元素没有fullPath
formatFullPath(menuData) // 路由国际化
}
return menuData.map(item => {
const menuItem = {...item}
delete menuItem.children
return menuItem
return menuData.map(item => { // 返回data map item
const menuItem = {...item} // 菜单项
delete menuItem.children // 删除子菜单
return menuItem // 返回菜单项
})
},
subMenu(state) {
const {menuData, activatedFirst} = state
if (menuData.length > 0 && !menuData[0].fullPath) {
formatFullPath(menuData)
subMenu(state) { // 子菜单
const {menuData, activatedFirst} = state // 从state中获取菜单数据 和 激活的第一个菜单
if (menuData.length > 0 && !menuData[0].fullPath) { // 如果菜单数据长度大于0 并且 菜单数据第一个元素没有fullPath
formatFullPath(menuData) // 路由国际化
}
const current = menuData.find(menu => menu.fullPath === activatedFirst)
return current && current.children || []
const current = menuData.find(menu => menu.fullPath === activatedFirst) // 当前菜单
return current && current.children || [] // 返回当前菜单的子菜单
}
},
mutations: {
setDevice (state, isMobile) {
setDevice (state, isMobile) { // 设置设备
state.isMobile = isMobile
},
setTheme (state, theme) {
setTheme (state, theme) { // 设置主题
state.theme = theme
},
setLayout (state, layout) {
setLayout (state, layout) { // 设置布局
state.layout = layout
},
setMultiPage (state, multiPage) {
setMultiPage (state, multiPage) { // 设置多页签
state.multiPage = multiPage
},
setAnimate (state, animate) {
setAnimate (state, animate) { // 设置动画
state.animate = animate
},
setWeekMode(state, weekMode) {
setWeekMode(state, weekMode) { // 设置周模式
state.weekMode = weekMode
},
setFixedHeader(state, fixedHeader) {
setFixedHeader(state, fixedHeader) { // 固定头部
state.fixedHeader = fixedHeader
},
setFixedSideBar(state, fixedSideBar) {
setFixedSideBar(state, fixedSideBar) { // 设置固定侧边栏
state.fixedSideBar = fixedSideBar
},
setLang(state, lang) {
setLang(state, lang) { // 设置语言
state.lang = lang
},
setHideSetting(state, hideSetting) {
setHideSetting(state, hideSetting) { // 设置隐藏设置
state.hideSetting = hideSetting
},
correctPageMinHeight(state, minHeight) {
correctPageMinHeight(state, minHeight) { // 修正页面最小高度
state.pageMinHeight += minHeight
},
setMenuData(state, menuData) {
setMenuData(state, menuData) { // 设置菜单数据
state.menuData = menuData
},
setAsyncRoutes(state, asyncRoutes) {
setAsyncRoutes(state, asyncRoutes) { // 设置异步路由
state.asyncRoutes = asyncRoutes
},
setPageWidth(state, pageWidth) {
setPageWidth(state, pageWidth) { // 设置页面宽度
state.pageWidth = pageWidth
},
setActivatedFirst(state, activatedFirst) {
setActivatedFirst(state, activatedFirst) { // 设置激活的第一个菜单
state.activatedFirst = activatedFirst
},
setFixedTabs(state, fixedTabs) {
setFixedTabs(state, fixedTabs) { // 设置固定标签
state.fixedTabs = fixedTabs
},
setCustomTitle(state, {path, title}) {
if (title) {
const obj = state.customTitles.find(item => item.path === path)
if (obj) {
obj.title = title
} else {
state.customTitles.push({path, title})
setCustomTitle(state, {path, title}) { // 设置自定义标题
if (title) { // 如果标题存在
const obj = state.customTitles.find(item => item.path === path) // 获取路径
if (obj) { // 如果对象存在
obj.title = title // 标题
} else { // 否则
state.customTitles.push({path, title}) // 添加路径和标题
}
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles))
sessionStorage.setItem(process.env.VUE_APP_TBAS_TITLES_KEY, JSON.stringify(state.customTitles)) // 存储自定义标题
}
}
}

View File

@ -4,46 +4,10 @@
* @param permissions 用户权限集合
* @returns {boolean|*}
*/
function hasPermission(authority, permissions) {
let required = '*'
if (typeof authority === 'string') {
required = authority
} else if (typeof authority === 'object') {
required = authority.permission
}
return required === '*' || (permissions && permissions.findIndex(item => item === required || item.id === required) !== -1)
}
/**
* 判断是否有路由需要的角色
* @param authority 路由权限配置
* @param roles 用户角色集合
*/
function hasRole(authority, roles) {
let required = undefined
if (typeof authority === 'object') {
required = authority.role
}
return authority === '*' || hasAnyRole(required, roles)
}
/**
* 判断是否有需要的任意一个角色
* @param required {String | Array[String]} 需要的角色可以是单个角色或者一个角色数组
* @param roles 拥有的角色
* @returns {boolean}
*/
function hasAnyRole(required, roles) {
if (!required) {
return false
} else if(Array.isArray(required)) {
return roles.findIndex(role => {
return required.findIndex(item => item === role || item === role.id) !== -1
}) !== -1
} else {
return roles.findIndex(role => role === required || role.id === required) !== -1
}
}
/**
* 路由权限校验
@ -52,14 +16,9 @@ function hasAnyRole(required, roles) {
* @param roles 用户角色集合
* @returns {boolean}
*/
function hasAuthority(route, permissions, roles) {
const authorities = [...route.meta.pAuthorities, route.meta.authority]
for (let authority of authorities) {
if (!hasPermission(authority, permissions) && !hasRole(authority, roles)) {
return false
}
}
return true
function hasAuthority() { // 权限校验
return true // 如果都满足返回true
}
/**
@ -67,6 +26,7 @@ function hasAuthority(route, permissions, roles) {
* @param menuData
* @param permissions
* @param roles
* 主要是在src\store\modules\setting.js中使用
*/
function filterMenu(menuData, permissions, roles) {
return menuData.filter(menu => {

View File

@ -47,6 +47,8 @@ async function request(url, method, params, config) {
* @param authType {AUTH_TYPE} 认证类型默认{AUTH_TYPE.BEARER}
*/
function setAuthorization(auth, authType = AUTH_TYPE.BEARER) {
console.log("authType:", authType);
console.log("auth:", auth);
switch (authType) {
case AUTH_TYPE.BEARER:
Cookie.set(xsrfHeaderName, 'Bearer ' + auth.token, {expires: auth.expireAt})

View File

@ -107,18 +107,24 @@ function loadRoutes(routesConfig) {
const {router, store, i18n} = appOptions
// 如果 routesConfig 有值,则更新到本地,否则从本地获取
if (routesConfig) {
store.commit('account/setRoutesConfig', routesConfig)
console.log("setRoutesConfig:", routesConfig)
if (routesConfig) { // 如果routesConfig已经存在
store.commit('account/setRoutesConfig', routesConfig) // 设置路由配置
} else {
routesConfig = store.getters['account/routesConfig']
}
// 如果开启了异步路由,则加载异步路由配置
const asyncRoutes = store.state.setting.asyncRoutes
if (asyncRoutes) {
if (routesConfig && routesConfig.length > 0) {
const routes = parseRoutes(routesConfig, routerMap)
if (asyncRoutes) { // 如果动态路由存在
if (routesConfig && routesConfig.length > 0) { // 如果本地路由配置存在 并且 数量大于0
const routes = parseRoutes(routesConfig, routerMap) // 解析路由
// 合并路由 生成路由表
const finalRoutes = mergeRoutes(basicOptions.routes, routes)
// 格式化路由
formatRoutes(finalRoutes)
console.log('最终路由表finalRoutes:', finalRoutes) // 最终路由表
router.options = {...router.options, routes: finalRoutes}
router.matcher = new Router({...router.options, routes:[]}).matcher
router.addRoutes(finalRoutes)
@ -252,9 +258,9 @@ function getI18nKey(path) {
* @param guards
* @param options
*/
function loadGuards(guards, options) {
const {beforeEach, afterEach} = guards
const {router} = options
function loadGuards(guards, options) { // 加载导航守卫 guards
const {beforeEach, afterEach} = guards // 解构出 beforeEach 和 afterEach
const {router} = options // 解构出 router
beforeEach.forEach(guard => {
if (guard && typeof guard === 'function') {
router.beforeEach((to, from, next) => guard(to, from, next, options))