feat: 更新mini分支
This commit is contained in:
parent
a1e89b7c8a
commit
607b73a7b3
|
@ -2,7 +2,7 @@
|
|||
NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=base
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
|
2
.env.dev
2
.env.dev
|
@ -2,7 +2,7 @@
|
|||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=dev
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-dev/
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=pro
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/vue-element-plus-admin/
|
||||
|
|
2
.env.pro
2
.env.pro
|
@ -2,7 +2,7 @@
|
|||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=pro
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||
|
||||
const modules = import.meta.glob('./**/*.ts', {
|
||||
const modules = import.meta.glob('./**/*.mock.ts', {
|
||||
import: 'default',
|
||||
eager: true
|
||||
})
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { SUCCESS_CODE } from '@/constants'
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const adminList = [
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: 'router.level',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu11',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
meta: {
|
||||
title: 'router.menu111'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
meta: {
|
||||
title: 'router.menu12'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
meta: {
|
||||
title: 'router.menu2'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const testList: string[] = [
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
'/level/menu1/menu1-1/menu1-1-1',
|
||||
'/level/menu1/menu1-2',
|
||||
'/level/menu2'
|
||||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/mock/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
return {
|
||||
code: SUCCESS_CODE,
|
||||
data: roleName === 'admin' ? adminList : testList
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,544 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const adminList = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
title: 'router.dashboard',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
meta: {
|
||||
title: 'router.analysis',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
meta: {
|
||||
title: 'router.workplace',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {},
|
||||
name: 'ExternalLink',
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
meta: {
|
||||
title: 'router.document',
|
||||
icon: 'clarity:document-solid'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/guide',
|
||||
component: '#',
|
||||
name: 'Guide',
|
||||
meta: {},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: 'views/Guide/Guide',
|
||||
name: 'GuideDemo',
|
||||
meta: {
|
||||
title: 'router.guide',
|
||||
icon: 'cib:telegram-plane'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
component: '#',
|
||||
redirect: '/components/form/default-form',
|
||||
name: 'ComponentsDemo',
|
||||
meta: {
|
||||
title: 'router.component',
|
||||
icon: 'bx:bxs-component',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'form',
|
||||
component: '##',
|
||||
name: 'Form',
|
||||
meta: {
|
||||
title: 'router.form',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-form',
|
||||
component: 'views/Components/Form/DefaultForm',
|
||||
name: 'DefaultForm',
|
||||
meta: {
|
||||
title: 'router.defaultForm'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-form',
|
||||
component: 'views/Components/Form/UseFormDemo',
|
||||
name: 'UseForm',
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: '##',
|
||||
redirect: '/components/table/default-table',
|
||||
name: 'TableDemo',
|
||||
meta: {
|
||||
title: 'router.table',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-table',
|
||||
component: 'views/Components/Table/DefaultTable',
|
||||
name: 'DefaultTable',
|
||||
meta: {
|
||||
title: 'router.defaultTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-table',
|
||||
component: 'views/Components/Table/UseTableDemo',
|
||||
name: 'UseTable',
|
||||
meta: {
|
||||
title: 'UseTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tree-table',
|
||||
component: 'views/Components/Table/TreeTable',
|
||||
name: 'TreeTable',
|
||||
meta: {
|
||||
title: 'router.TreeTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'table-image-preview',
|
||||
component: 'views/Components/Table/TableImagePreview',
|
||||
name: 'TableImagePreview',
|
||||
meta: {
|
||||
title: 'router.PicturePreview'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-table',
|
||||
component: 'views/Components/Table/RefTable',
|
||||
name: 'RefTable',
|
||||
meta: {
|
||||
title: 'RefTable'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'editor-demo',
|
||||
component: '##',
|
||||
redirect: '/components/editor-demo/editor',
|
||||
name: 'EditorDemo',
|
||||
meta: {
|
||||
title: 'router.editor',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: 'views/Components/Editor/Editor',
|
||||
name: 'Editor',
|
||||
meta: {
|
||||
title: 'router.richText'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: 'views/Components/Search',
|
||||
name: 'Search',
|
||||
meta: {
|
||||
title: 'router.search'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'descriptions',
|
||||
component: 'views/Components/Descriptions',
|
||||
name: 'Descriptions',
|
||||
meta: {
|
||||
title: 'router.descriptions'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'image-viewer',
|
||||
component: 'views/Components/ImageViewer',
|
||||
name: 'ImageViewer',
|
||||
meta: {
|
||||
title: 'router.imageViewer'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
component: 'views/Components/Dialog',
|
||||
name: 'Dialog',
|
||||
meta: {
|
||||
title: 'router.dialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: 'views/Components/Icon',
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: 'router.icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'echart',
|
||||
component: 'views/Components/Echart',
|
||||
name: 'Echart',
|
||||
meta: {
|
||||
title: 'router.echart'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'count-to',
|
||||
component: 'views/Components/CountTo',
|
||||
name: 'CountTo',
|
||||
meta: {
|
||||
title: 'router.countTo'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'qrcode',
|
||||
component: 'views/Components/Qrcode',
|
||||
name: 'Qrcode',
|
||||
meta: {
|
||||
title: 'router.qrcode'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'highlight',
|
||||
component: 'views/Components/Highlight',
|
||||
name: 'Highlight',
|
||||
meta: {
|
||||
title: 'router.highlight'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'infotip',
|
||||
component: 'views/Components/Infotip',
|
||||
name: 'Infotip',
|
||||
meta: {
|
||||
title: 'router.infotip'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'input-password',
|
||||
component: 'views/Components/InputPassword',
|
||||
name: 'InputPassword',
|
||||
meta: {
|
||||
title: 'router.inputPassword'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'sticky',
|
||||
component: 'views/Components/Sticky',
|
||||
name: 'Sticky',
|
||||
meta: {
|
||||
title: 'router.sticky'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/hooks',
|
||||
component: '#',
|
||||
redirect: '/hooks/useWatermark',
|
||||
name: 'Hooks',
|
||||
meta: {
|
||||
title: 'hooks',
|
||||
icon: 'ic:outline-webhook',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'useWatermark',
|
||||
component: 'views/hooks/useWatermark',
|
||||
name: 'UseWatermark',
|
||||
meta: {
|
||||
title: 'useWatermark'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useCrudSchemas',
|
||||
component: 'views/hooks/useCrudSchemas',
|
||||
name: 'UseCrudSchemas',
|
||||
meta: {
|
||||
title: 'useCrudSchemas'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: 'router.level',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu11',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
meta: {
|
||||
title: 'router.menu111'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
meta: {
|
||||
title: 'router.menu12'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
meta: {
|
||||
title: 'router.menu2'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
meta: {
|
||||
title: 'router.example',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
meta: {
|
||||
title: 'router.exampleDialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
meta: {
|
||||
title: 'router.examplePage'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
meta: {
|
||||
title: 'router.exampleAdd',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
meta: {
|
||||
title: 'router.exampleEdit',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
meta: {
|
||||
title: 'router.exampleDetail',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: '#',
|
||||
redirect: '/error/404',
|
||||
name: 'Error',
|
||||
meta: {
|
||||
title: 'router.errorPage',
|
||||
icon: 'ci:error',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '404-demo',
|
||||
component: 'views/Error/404',
|
||||
name: '404Demo',
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '403-demo',
|
||||
component: 'views/Error/403',
|
||||
name: '403Demo',
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '500-demo',
|
||||
component: 'views/Error/500',
|
||||
name: '500Demo',
|
||||
meta: {
|
||||
title: '500'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const testList: string[] = [
|
||||
'/dashboard',
|
||||
'/dashboard/analysis',
|
||||
'/dashboard/workplace',
|
||||
'external-link',
|
||||
'https://element-plus-admin-doc.cn/',
|
||||
'/guide',
|
||||
'/guide/index',
|
||||
'/components',
|
||||
'/components/form',
|
||||
'/components/form/default-form',
|
||||
'/components/form/use-form',
|
||||
'/components/form/ref-form',
|
||||
'/components/table',
|
||||
'/components/table/default-table',
|
||||
'/components/table/use-table',
|
||||
'/components/table/tree-table',
|
||||
'/components/table/table-image-preview',
|
||||
'/components/table/ref-table',
|
||||
'/components/editor-demo',
|
||||
'/components/editor-demo/editor',
|
||||
'/components/search',
|
||||
'/components/descriptions',
|
||||
'/components/image-viewer',
|
||||
'/components/dialog',
|
||||
'/components/icon',
|
||||
'/components/echart',
|
||||
'/components/count-to',
|
||||
'/components/qrcode',
|
||||
'/components/highlight',
|
||||
'/components/infotip',
|
||||
'/Components/InputPassword',
|
||||
'/Components/Sticky',
|
||||
'/hooks',
|
||||
'/hooks/useWatermark',
|
||||
'/hooks/useCrudSchemas',
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
'/level/menu1/menu1-1/menu1-1-1',
|
||||
'/level/menu1/menu1-2',
|
||||
'/level/menu2',
|
||||
'/example',
|
||||
'/example/example-dialog',
|
||||
'/example/example-page',
|
||||
'/example/example-add',
|
||||
'/example/example-edit',
|
||||
'/example/example-detail',
|
||||
'/error',
|
||||
'/error/404-demo',
|
||||
'/error/403-demo',
|
||||
'/error/500-demo'
|
||||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
return {
|
||||
code: code,
|
||||
data: roleName === 'admin' ? adminList : testList
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,7 +1,4 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
import { SUCCESS_CODE } from '@/constants'
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
|
@ -31,7 +28,7 @@ const List: {
|
|||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/user/list',
|
||||
url: '/mock/user/list',
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
const { username, pageIndex, pageSize } = query
|
||||
|
@ -45,8 +42,8 @@ export default [
|
|||
)
|
||||
|
||||
return {
|
||||
code: SUCCESS_CODE,
|
||||
data: {
|
||||
code: code,
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
|
@ -55,7 +52,7 @@ export default [
|
|||
},
|
||||
// 登录接口
|
||||
{
|
||||
url: '/user/login',
|
||||
url: '/mock/user/login',
|
||||
method: 'post',
|
||||
timeout,
|
||||
response: ({ body }) => {
|
||||
|
@ -65,7 +62,7 @@ export default [
|
|||
if (user.username === data.username && user.password === data.password) {
|
||||
hasUser = true
|
||||
return {
|
||||
code: code,
|
||||
code: SUCCESS_CODE,
|
||||
data: user
|
||||
}
|
||||
}
|
||||
|
@ -80,14 +77,14 @@ export default [
|
|||
},
|
||||
// 退出接口
|
||||
{
|
||||
url: '/user/loginOut',
|
||||
url: '/mock/user/loginOut',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
code: code,
|
||||
code: SUCCESS_CODE,
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
||||
]
|
93
package.json
93
package.json
|
@ -6,18 +6,18 @@
|
|||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode base",
|
||||
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:gitee": "vite build --mode gitee",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"build:test": "npm run ts:check && vite build --mode test",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"serve:test": "vite preview --mode test",
|
||||
"npm:check": "npx npm-check-updates",
|
||||
"clean": "npx rimraf node_modules",
|
||||
"clean:cache": "npx rimraf node_modules/.cache",
|
||||
"dev": "pnpm vite --mode base",
|
||||
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "pnpm vite build --mode pro",
|
||||
"build:gitee": "pnpm vite build --mode gitee",
|
||||
"build:dev": "pnpm vite build --mode dev",
|
||||
"build:test": "pnpm run ts:check && vite build --mode test",
|
||||
"serve:pro": "pnpm vite preview --mode pro",
|
||||
"serve:dev": "pnpm vite preview --mode dev",
|
||||
"serve:test": "pnpm vite preview --mode test",
|
||||
"npm:check": "pnpx npm-check-updates -u",
|
||||
"clean": "pnpx rimraf node_modules",
|
||||
"clean:cache": "pnpx rimraf node_modules/.cache",
|
||||
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
||||
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
|
||||
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
|
@ -26,90 +26,99 @@
|
|||
"p": "plop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.3.1",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@vueuse/core": "^10.6.1",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.6.2",
|
||||
"cropperjs": "^1.6.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"driver.js": "^1.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.4.2",
|
||||
"element-plus": "^2.4.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"url": "^0.11.3",
|
||||
"vue": "3.3.8",
|
||||
"vue-i18n": "9.7.0",
|
||||
"vue": "3.3.10",
|
||||
"vue-i18n": "9.8.0",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-types": "^5.1.1"
|
||||
"vue-types": "^5.1.1",
|
||||
"xgplayer": "^3.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^18.4.3",
|
||||
"@commitlint/config-conventional": "^18.4.3",
|
||||
"@iconify/json": "^2.2.144",
|
||||
"@iconify/json": "^2.2.153",
|
||||
"@intlify/unplugin-vue-i18n": "^1.5.0",
|
||||
"@purge-icons/generated": "^0.10.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.9.4",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/qs": "^6.9.10",
|
||||
"@types/sortablejs": "^1.15.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@unocss/transformer-variant-group": "^0.57.7",
|
||||
"@vitejs/plugin-legacy": "^5.1.0",
|
||||
"@vitejs/plugin-vue": "^4.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||
"@typescript-eslint/parser": "^6.13.2",
|
||||
"@unocss/transformer-variant-group": "^0.58.0",
|
||||
"@vitejs/plugin-legacy": "^5.2.0",
|
||||
"@vitejs/plugin-vue": "^4.5.1",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"@vue-macros/volar": "^0.17.3",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"chalk": "^5.3.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "^2.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-vue": "^9.18.1",
|
||||
"eslint-plugin-vue": "^9.19.2",
|
||||
"esno": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"husky": "^8.0.3",
|
||||
"inquirer": "^9.2.12",
|
||||
"less": "^4.2.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"plop": "^4.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.5.1",
|
||||
"rollup": "^4.6.1",
|
||||
"stylelint": "^15.11.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.24.0",
|
||||
"typescript": "5.3.2",
|
||||
"unocss": "^0.57.7",
|
||||
"unplugin-vue-define-options": "^1.4.0",
|
||||
"vite": "5.0.2",
|
||||
"terser": "^5.25.0",
|
||||
"typescript": "5.3.3",
|
||||
"unocss": "^0.58.0",
|
||||
"vite": "5.0.6",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.10.0",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^1.8.22"
|
||||
"vue-tsc": "^1.8.25"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.0",
|
||||
"engines": {
|
||||
"node": ">= 18.0.0 || >= 20.0.0"
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.1.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// 获取所有字典
|
||||
export const getDictApi = () => {
|
||||
return request.get({ url: '/dict/list' })
|
||||
}
|
||||
|
||||
// 模拟获取某个字典
|
||||
export const getDictOneApi = async () => {
|
||||
return request.get({ url: '/dict/one' })
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type {
|
||||
AnalysisTotalTypes,
|
||||
UserAccessSource,
|
||||
WeeklyUserActivity,
|
||||
MonthlySales
|
||||
} from './types'
|
||||
|
||||
export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
|
||||
return request.get({ url: '/analysis/total' })
|
||||
}
|
||||
|
||||
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
|
||||
return request.get({ url: '/analysis/userAccessSource' })
|
||||
}
|
||||
|
||||
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
|
||||
return request.get({ url: '/analysis/weeklyUserActivity' })
|
||||
}
|
||||
|
||||
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
|
||||
return request.get({ url: '/analysis/monthlySales' })
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
export type AnalysisTotalTypes = {
|
||||
users: number
|
||||
messages: number
|
||||
moneys: number
|
||||
shoppings: number
|
||||
}
|
||||
|
||||
export type UserAccessSource = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WeeklyUserActivity = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MonthlySales = {
|
||||
name: string
|
||||
estimate: number
|
||||
actual: number
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
|
||||
|
||||
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
|
||||
return request.get({ url: '/workplace/total' })
|
||||
}
|
||||
|
||||
export const getProjectApi = (): Promise<IResponse<Project>> => {
|
||||
return request.get({ url: '/workplace/project' })
|
||||
}
|
||||
|
||||
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
|
||||
return request.get({ url: '/workplace/dynamic' })
|
||||
}
|
||||
|
||||
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
|
||||
return request.get({ url: '/workplace/team' })
|
||||
}
|
||||
|
||||
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
|
||||
return request.get({ url: '/workplace/radar' })
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
export type WorkplaceTotal = {
|
||||
project: number
|
||||
access: number
|
||||
todo: number
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
name: string
|
||||
icon: string
|
||||
message: string
|
||||
personal: string
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Dynamic = {
|
||||
keys: string[]
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Team = {
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export type RadarData = {
|
||||
personal: number
|
||||
team: number
|
||||
max: number
|
||||
name: string
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
|
||||
|
||||
export const getDepartmentApi = () => {
|
||||
return request.get<DepartmentListResponse>({ url: '/department/list' })
|
||||
}
|
||||
|
||||
export const getUserByIdApi = (params: DepartmentUserParams) => {
|
||||
return request.get<DepartmentUserResponse>({ url: '/department/users', params })
|
||||
}
|
||||
|
||||
export const deleteUserByIdApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/user/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const saveUserApi = (data: any) => {
|
||||
return request.post({ url: '/department/user/save', data })
|
||||
}
|
||||
|
||||
export const saveDepartmentApi = (data: any) => {
|
||||
return request.post({ url: '/department/save', data })
|
||||
}
|
||||
|
||||
export const deleteDepartmentApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const getDepartmentTableApi = (params: any) => {
|
||||
return request.get({ url: '/department/table/list', params })
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
export interface DepartmentItem {
|
||||
id: string
|
||||
departmentName: string
|
||||
children?: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentListResponse {
|
||||
list: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentUserParams {
|
||||
pageSize: number
|
||||
pageIndex: number
|
||||
id: string
|
||||
username?: string
|
||||
account?: string
|
||||
}
|
||||
|
||||
export interface DepartmentUserItem {
|
||||
id: string
|
||||
username: string
|
||||
account: string
|
||||
email: string
|
||||
createTime: string
|
||||
role: string
|
||||
department: DepartmentItem
|
||||
}
|
||||
|
||||
export interface DepartmentUserResponse {
|
||||
list: DepartmentUserItem[]
|
||||
total: number
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import request from '@/config/axios'
|
||||
import request from '@/axios'
|
||||
import type { UserType } from './types'
|
||||
|
||||
interface RoleParams {
|
||||
|
@ -6,29 +6,19 @@ interface RoleParams {
|
|||
}
|
||||
|
||||
export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
|
||||
return request.post({ url: '/user/login', data })
|
||||
return request.post({ url: '/mock/user/login', data })
|
||||
}
|
||||
|
||||
export const loginOutApi = (): Promise<IResponse> => {
|
||||
return request.get({ url: '/user/loginOut' })
|
||||
}
|
||||
|
||||
export const getUserListApi = ({ params }: AxiosConfig) => {
|
||||
return request.get<{
|
||||
code: string
|
||||
data: {
|
||||
list: UserType[]
|
||||
total: number
|
||||
}
|
||||
}>({ url: '/user/list', params })
|
||||
return request.get({ url: '/mock/user/loginOut' })
|
||||
}
|
||||
|
||||
export const getAdminRoleApi = (
|
||||
params: RoleParams
|
||||
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
|
||||
return request.get({ url: '/role/list', params })
|
||||
return request.get({ url: '/mock/role/list', params })
|
||||
}
|
||||
|
||||
export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
|
||||
return request.get({ url: '/role/list', params })
|
||||
return request.get({ url: '/mock/role/list2', params })
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export const getMenuListApi = () => {
|
||||
return request.get({ url: '/menu/list' })
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type { TableData } from './types'
|
||||
|
||||
export const getTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/list', params })
|
||||
}
|
||||
|
||||
export const getTreeTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/treeList', params })
|
||||
}
|
||||
|
||||
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
|
||||
return request.post({ url: '/example/save', data })
|
||||
}
|
||||
|
||||
export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
|
||||
return request.get({ url: '/example/detail', params: { id } })
|
||||
}
|
||||
|
||||
export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
|
||||
return request.post({ url: '/example/delete', data: { ids } })
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export type TableData = {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
content: string
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
import { SUCCESS_CODE } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
|
||||
if (
|
||||
config.method === 'post' &&
|
||||
config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
if (config.method === 'get' && config.params) {
|
||||
let url = config.url as string
|
||||
url += '?'
|
||||
const keys = Object.keys(config.params)
|
||||
for (const key of keys) {
|
||||
if (config.params[key] !== void 0 && config.params[key] !== null) {
|
||||
url += `${key}=${encodeURIComponent(config.params[key])}&`
|
||||
}
|
||||
}
|
||||
url = url.substring(0, url.length - 1)
|
||||
config.params = {}
|
||||
config.url = url
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const defaultResponseInterceptors = (response: AxiosResponse) => {
|
||||
if (response?.config?.responseType === 'blob') {
|
||||
// 如果是文件流,直接过
|
||||
return response
|
||||
} else if (response.data.code === SUCCESS_CODE) {
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage.error(response?.data?.message)
|
||||
if (response?.data?.code === 401) {
|
||||
const userStore = useUserStoreWithOut()
|
||||
userStore.logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { defaultResponseInterceptors, defaultRequestInterceptors }
|
|
@ -1,11 +1,10 @@
|
|||
import service from './service'
|
||||
|
||||
import config from './config'
|
||||
|
||||
const { defaultHeaders } = config
|
||||
import { CONTENT_TYPE } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const request = (option: AxiosConfig) => {
|
||||
const { url, method, params, data, headersType, responseType } = option
|
||||
const { url, method, params, data, headers, responseType } = option
|
||||
const userStore = useUserStoreWithOut()
|
||||
return service.request({
|
||||
url: url,
|
||||
method,
|
||||
|
@ -13,7 +12,9 @@ const request = (option: AxiosConfig) => {
|
|||
data,
|
||||
responseType: responseType,
|
||||
headers: {
|
||||
'Content-Type': headersType || defaultHeaders
|
||||
'Content-Type': CONTENT_TYPE,
|
||||
[userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
|
||||
...headers
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
import axios, { AxiosError } from 'axios'
|
||||
import config, { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
|
||||
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
|
||||
|
||||
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { REQUEST_TIMEOUT } from '@/constants'
|
||||
|
||||
const { interceptors, baseUrl } = config
|
||||
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
|
||||
|
||||
const { requestInterceptors, responseInterceptors } = interceptors
|
||||
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
|
||||
|
||||
const abortControllerMap: Map<string, AbortController> = new Map()
|
||||
|
||||
const axiosInstance: AxiosInstance = axios.create({
|
||||
...config,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
baseURL: PATH_URL
|
||||
})
|
||||
|
||||
|
@ -28,6 +26,7 @@ axiosInstance.interceptors.response.use(
|
|||
(res: AxiosResponse) => {
|
||||
const url = res.config.url || ''
|
||||
abortControllerMap.delete(url)
|
||||
// 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
|
||||
return res
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
|
@ -37,8 +36,8 @@ axiosInstance.interceptors.response.use(
|
|||
}
|
||||
)
|
||||
|
||||
axiosInstance.interceptors.request.use(requestInterceptors || defaultRequestInterceptors)
|
||||
axiosInstance.interceptors.response.use(responseInterceptors || defaultResponseInterceptors)
|
||||
axiosInstance.interceptors.request.use(defaultRequestInterceptors)
|
||||
axiosInstance.interceptors.response.use(defaultResponseInterceptors)
|
||||
|
||||
const service = {
|
||||
request: (config: RequestConfig) => {
|
|
@ -15,18 +15,6 @@ interface RequestInterceptors<T> {
|
|||
responseInterceptors?: (config: T) => T
|
||||
responseInterceptorsCatch?: (err: any) => any
|
||||
}
|
||||
interface AxiosConfig<T = AxiosResponse> {
|
||||
baseUrl: {
|
||||
base: string
|
||||
dev: string
|
||||
pro: string
|
||||
test: string
|
||||
}
|
||||
code: number
|
||||
defaultHeaders: AxiosHeaders
|
||||
timeout: number
|
||||
interceptors: RequestInterceptors<T>
|
||||
}
|
||||
|
||||
interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
|
||||
interceptors?: RequestInterceptors<T>
|
||||
|
@ -36,7 +24,6 @@ export {
|
|||
AxiosResponse,
|
||||
RequestInterceptors,
|
||||
RequestConfig,
|
||||
AxiosConfig,
|
||||
AxiosInstance,
|
||||
InternalAxiosRequestConfig,
|
||||
AxiosRequestHeaders,
|
|
@ -0,0 +1,3 @@
|
|||
import BaseButton from './src/Button.vue'
|
||||
|
||||
export { BaseButton }
|
|
@ -0,0 +1,121 @@
|
|||
<script setup lang="ts">
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
|
||||
import { PropType, Component, computed, unref } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const getTheme = computed(() => appStore.getTheme)
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('button')
|
||||
|
||||
const props = defineProps({
|
||||
size: {
|
||||
type: String as PropType<ComponentSize>,
|
||||
default: undefined
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<ButtonType>,
|
||||
default: 'default'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
bg: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadingIcon: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: undefined
|
||||
},
|
||||
icon: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: undefined
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
nativeType: {
|
||||
type: String as PropType<'button' | 'submit' | 'reset'>,
|
||||
default: 'button'
|
||||
},
|
||||
autoInsertSpace: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
darker: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tag: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: 'button'
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['click'])
|
||||
|
||||
const color = computed(() => {
|
||||
const { type } = props
|
||||
if (type === 'primary') {
|
||||
return unref(getTheme).elColorPrimary
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const style = computed(() => {
|
||||
const { type } = props
|
||||
if (type === 'primary') {
|
||||
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElButton
|
||||
:class="`${prefixCls} color-#fff`"
|
||||
v-bind="{ ...props }"
|
||||
:color="color"
|
||||
:style="style"
|
||||
@click="() => emits('click')"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot name="icon"></slot>
|
||||
<slot name="loading"></slot>
|
||||
</ElButton>
|
||||
</template>
|
|
@ -155,6 +155,7 @@ export default defineComponent({
|
|||
.is-active {
|
||||
& > .@{elNamespace}-sub-menu__title {
|
||||
color: var(--left-menu-text-active-color) !important;
|
||||
// background-color: var(--left-menu-bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +169,6 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// 设置选中时的高亮背景和高亮颜色
|
||||
.@{elNamespace}-sub-menu.is-active,
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
color: var(--left-menu-text-active-color) !important;
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
|
@ -235,7 +235,7 @@ export default defineComponent({
|
|||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +270,7 @@ export default defineComponent({
|
|||
.is-active {
|
||||
& > .el-sub-menu__title {
|
||||
color: var(--left-menu-text-active-color) !important;
|
||||
// background-color: var(--left-menu-bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { ElSubMenu, ElMenuItem } from 'element-plus'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import { hasOneShowingChild } from '../helper'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
export const useRenderMenuItem = (
|
||||
// allRouters: AppRouteRecordRaw[] = [],
|
||||
menuMode: 'vertical' | 'horizontal'
|
||||
) => {
|
||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers.map((v) => {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
if (!meta.hidden) {
|
||||
return routers
|
||||
.filter((v) => !v.meta?.hidden)
|
||||
.map((v) => {
|
||||
const meta = v.meta ?? {}
|
||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
|
@ -49,8 +49,7 @@ export const useRenderMenuItem = (
|
|||
</ElSubMenu>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -88,6 +88,9 @@ const newSchema = computed(() => {
|
|||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
label: () => {
|
||||
return <span> </span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,11 +120,14 @@ const setProps = (props: SearchProps = {}) => {
|
|||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const schemaRef = ref<FormSchema[]>([])
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(newSchema),
|
||||
async (schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
schemaRef.value = schema
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -241,7 +247,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
|
|||
hide-required-asterisk
|
||||
:inline="getProps.inline"
|
||||
:is-col="getProps.isCol"
|
||||
:schema="newSchema"
|
||||
:schema="schemaRef"
|
||||
@register="formRegister"
|
||||
@validate="onFormValidate"
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||
import { ref, unref, computed, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
|
@ -14,7 +14,7 @@ import { useStorage } from '@/hooks/web/useStorage'
|
|||
import { useClipboard } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { removeStorage } = useStorage()
|
||||
const { clear: storageClear } = useStorage('localStorage')
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -174,7 +174,8 @@ const copyConfig = async () => {
|
|||
// 头部边框颜色
|
||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||
}
|
||||
`
|
||||
`,
|
||||
legacy: true
|
||||
})
|
||||
if (!isSupported) {
|
||||
ElMessage.error(t('setting.copyFailed'))
|
||||
|
@ -188,9 +189,7 @@ const copyConfig = async () => {
|
|||
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
removeStorage('layout')
|
||||
removeStorage('theme')
|
||||
removeStorage('isDark')
|
||||
storageClear()
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
@ -278,12 +277,14 @@ const clear = () => {
|
|||
|
||||
<ElDivider />
|
||||
<div>
|
||||
<ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
|
||||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||||
t('setting.copy')
|
||||
}}</BaseButton>
|
||||
</div>
|
||||
<div class="mt-5px">
|
||||
<ElButton type="danger" class="w-full" @click="clear">
|
||||
<BaseButton type="danger" class="w-full" @click="clear">
|
||||
{{ t('setting.clearAndReset') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
|
|
|
@ -108,13 +108,21 @@ const greyModeChange = (show: boolean) => {
|
|||
}
|
||||
|
||||
// 动态路由
|
||||
const dynamicRouter = ref(appStore.getDynamicRouter)
|
||||
const dynamicRouter = ref(!!appStore.getDynamicRouter)
|
||||
|
||||
const dynamicRouterChange = (show: boolean) => {
|
||||
ElMessage.info(t('setting.reExperienced'))
|
||||
appStore.setDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 服务端动态路由
|
||||
const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
|
||||
|
||||
const serverDynamicRouterChange = (show: boolean) => {
|
||||
ElMessage.info(t('setting.reExperienced'))
|
||||
appStore.setServerDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 固定菜单
|
||||
const fixedMenu = ref(appStore.getFixedMenu)
|
||||
|
||||
|
@ -206,6 +214,11 @@ watch(
|
|||
<ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
|
||||
<ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.fixedMenu') }}</span>
|
||||
<ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
|
||||
|
|
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -79,14 +79,14 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px 4px 0 4px;
|
||||
border-radius: 4px 4px 0;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
|
@ -5,7 +5,9 @@ import {
|
|||
ElPagination,
|
||||
ComponentSize,
|
||||
ElTooltipProps,
|
||||
ElImage
|
||||
ElImage,
|
||||
ElEmpty,
|
||||
ElCard
|
||||
} from 'element-plus'
|
||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
@ -15,8 +17,10 @@ import { set, get } from 'lodash-es'
|
|||
import { CSSProperties } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import TableActions from './components/TableActions.vue'
|
||||
// import Sortable from 'sortablejs'
|
||||
// import { Icon } from '@/components/Icon'
|
||||
import { isImgPath } from '@/utils/is'
|
||||
import { createVideoViewer } from '@/components/VideoPlayer'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
|
@ -32,8 +36,6 @@ export default defineComponent({
|
|||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 展开行
|
||||
// expand: propTypes.bool.def(false),
|
||||
// 是否展示分页
|
||||
pagination: {
|
||||
type: Object as PropType<Pagination>,
|
||||
|
@ -62,7 +64,6 @@ export default defineComponent({
|
|||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// sortable: propTypes.bool.def(false),
|
||||
height: propTypes.oneOfType([Number, String]),
|
||||
maxHeight: propTypes.oneOfType([Number, String]),
|
||||
stripe: propTypes.bool.def(false),
|
||||
|
@ -186,9 +187,27 @@ export default defineComponent({
|
|||
default: 'fixed'
|
||||
},
|
||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||
flexible: propTypes.bool.def(false)
|
||||
flexible: propTypes.bool.def(false),
|
||||
// 自定义内容
|
||||
customContent: propTypes.bool.def(false),
|
||||
cardBodyStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardBodyClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
cardWrapStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardWrapClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
|
@ -213,33 +232,6 @@ export default defineComponent({
|
|||
return propsObj
|
||||
})
|
||||
|
||||
// const sortableEl = ref()
|
||||
// 初始化拖拽
|
||||
// const initDropTable = () => {
|
||||
// const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
|
||||
// if (!el) return
|
||||
// if (unref(sortableEl)) unref(sortableEl).destroy()
|
||||
|
||||
// sortableEl.value = Sortable.create(el, {
|
||||
// handle: '.table-move',
|
||||
// animation: 180,
|
||||
// onEnd(e: any) {
|
||||
// emit('sortable-change', e)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// watch(
|
||||
// () => getProps.value.sortable,
|
||||
// async (v) => {
|
||||
// await nextTick()
|
||||
// v && initDropTable()
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// )
|
||||
|
||||
const setProps = (props: TableProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = { ...props } as any
|
||||
|
@ -260,7 +252,7 @@ export default defineComponent({
|
|||
|
||||
const addColumn = (column: TableColumn, index?: number) => {
|
||||
const { columns } = unref(getProps)
|
||||
if (index) {
|
||||
if (index !== void 0) {
|
||||
columns.splice(index, 0, column)
|
||||
} else {
|
||||
columns.push(column)
|
||||
|
@ -391,14 +383,28 @@ export default defineComponent({
|
|||
const renderPreview = (url: string) => {
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%] h-100px"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
{isImgPath(url) ? (
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%]"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
) : (
|
||||
<BaseButton
|
||||
type="primary"
|
||||
icon={<Icon icon="ep:video-play" />}
|
||||
onClick={() => {
|
||||
createVideoViewer({
|
||||
url
|
||||
})
|
||||
}}
|
||||
>
|
||||
预览
|
||||
</BaseButton>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -438,6 +444,7 @@ export default defineComponent({
|
|||
reserveSelection={reserveSelection}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
selectable={v.selectable}
|
||||
width="50"
|
||||
></ElTableColumn>
|
||||
)
|
||||
|
@ -489,41 +496,68 @@ export default defineComponent({
|
|||
return () => {
|
||||
const tableSlots = {}
|
||||
if (getSlot(slots, 'empty')) {
|
||||
tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', ...args)
|
||||
tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
|
||||
}
|
||||
if (getSlot(slots, 'append')) {
|
||||
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', ...args)
|
||||
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
|
||||
}
|
||||
|
||||
// const { sortable } = unref(getProps)
|
||||
|
||||
// const sortableEl = sortable ? (
|
||||
// <ElTableColumn
|
||||
// className="table-move cursor-move"
|
||||
// type="sortable"
|
||||
// prop="sortable"
|
||||
// width="60px"
|
||||
// align="center"
|
||||
// >
|
||||
// <Icon icon="ant-design:drag-outlined" />
|
||||
// </ElTableColumn>
|
||||
// ) : null
|
||||
|
||||
return (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
{unref(getProps).customContent ? (
|
||||
<div class="flex flex-wrap">
|
||||
{unref(getProps)?.data?.length ? (
|
||||
unref(getProps)?.data.map((item) => {
|
||||
const cardSlots = {
|
||||
default: () => {
|
||||
return getSlot(slots, 'content', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-header')) {
|
||||
cardSlots['header'] = () => {
|
||||
return getSlot(slots, 'content-header', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-footer')) {
|
||||
cardSlots['footer'] = () => {
|
||||
return getSlot(slots, 'content-footer', item)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class={unref(getProps).cardWrapClass}
|
||||
style={unref(getProps).cardWrapStyle}
|
||||
bodyClass={unref(getProps).cardBodyClass}
|
||||
bodyStyle={unref(getProps).cardBodyStyle}
|
||||
>
|
||||
{cardSlots}
|
||||
</ElCard>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div class="flex flex-1 justify-center">
|
||||
<ElEmpty description="暂无数据" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
</>
|
||||
)}
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
|
|
|
@ -1,49 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { resetRouter } from '@/router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { loginOutApi } from '@/api/login'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import LockDialog from './components/LockDialog.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import LockPage from './components/LockPage.vue'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const lockStore = useLockStore()
|
||||
|
||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('user-info')
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { clear } = useStorage()
|
||||
|
||||
const { replace } = useRouter()
|
||||
|
||||
const loginOut = () => {
|
||||
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await loginOutApi().catch(() => {})
|
||||
if (res) {
|
||||
clear()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter() // 重置静态路由表
|
||||
replace('/login')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
userStore.logoutConfirm()
|
||||
}
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
@ -66,7 +44,9 @@ const toDocument = () => {
|
|||
alt=""
|
||||
class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
|
||||
/>
|
||||
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
|
||||
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
|
||||
userStore.getUserInfo?.username
|
||||
}}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { VNode, createVNode, render } from 'vue'
|
||||
import VideoPlayer from './src/VideoPlayer.vue'
|
||||
import { isClient } from '@/utils/is'
|
||||
import { VideoPlayerViewer } from '@/components/VideoPlayerViewer'
|
||||
import { toAnyString } from '@/utils'
|
||||
|
||||
export { VideoPlayer }
|
||||
|
||||
let instance: Nullable<VNode> = null
|
||||
|
||||
export function createVideoViewer(options: { url: string; poster?: string; show?: boolean }) {
|
||||
if (!isClient) return
|
||||
const { url, poster } = options
|
||||
|
||||
const propsData: Partial<{ url: string; poster?: string; show?: boolean; id?: string }> = {}
|
||||
const container = document.createElement('div')
|
||||
const id = toAnyString()
|
||||
container.id = id
|
||||
propsData.url = url
|
||||
propsData.poster = poster
|
||||
propsData.show = true
|
||||
propsData.id = id
|
||||
|
||||
document.body.appendChild(container)
|
||||
instance = createVNode(VideoPlayerViewer, propsData)
|
||||
render(instance, container)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import Player from 'xgplayer'
|
||||
import { ref, unref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
|
||||
import 'xgplayer/dist/index.min.css'
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const playerRef = ref<Player>()
|
||||
|
||||
const videoEl = ref<HTMLDivElement>()
|
||||
|
||||
const intiPlayer = () => {
|
||||
if (!unref(videoEl)) return
|
||||
new Player({
|
||||
autoplay: false,
|
||||
...props,
|
||||
el: unref(videoEl)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
intiPlayer()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
async (newProps) => {
|
||||
await nextTick()
|
||||
if (newProps) {
|
||||
unref(playerRef)?.setConfig(newProps)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unref(playerRef)?.destroy()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
playerExpose: () => unref(playerRef)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="videoEl"></div>
|
||||
</template>
|
|
@ -0,0 +1,3 @@
|
|||
import VideoPlayerViewer from './src/VideoPlayerViewer.vue'
|
||||
|
||||
export { VideoPlayerViewer }
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { VideoPlayer } from '@/components/VideoPlayer'
|
||||
import { ElOverlay } from 'element-plus'
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const visible = ref(props.show)
|
||||
|
||||
const close = async () => {
|
||||
visible.value = false
|
||||
await nextTick()
|
||||
const wrap = document.getElementById(props.id)
|
||||
if (!wrap) return
|
||||
document.body.removeChild(wrap)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ElOverlay v-show="visible" @click="close">
|
||||
<div class="w-full h-full flex justify-center items-center relative" @click="close">
|
||||
<div
|
||||
class="w-44px h-44px color-[#fff] bg-[var(--el-text-color-regular)] rounded-full border-[#fff] flex justify-center items-center cursor-pointer absolute top-40px right-40px"
|
||||
@click="close"
|
||||
>
|
||||
<Icon icon="ep:close" :size="24" />
|
||||
</div>
|
||||
<VideoPlayer :url="url" :poster="poster" />
|
||||
</div>
|
||||
</ElOverlay>
|
||||
</template>
|
|
@ -1,6 +1,8 @@
|
|||
import type { App } from 'vue'
|
||||
import { Icon } from './Icon'
|
||||
import { BaseButton } from './Button'
|
||||
|
||||
export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Icon', Icon)
|
||||
app.component('BaseButton', BaseButton)
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
import {
|
||||
AxiosConfig,
|
||||
AxiosResponse,
|
||||
AxiosRequestHeaders,
|
||||
InternalAxiosRequestConfig
|
||||
} from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
|
||||
const { clear } = useStorage()
|
||||
|
||||
const config: AxiosConfig = {
|
||||
/**
|
||||
* api请求基础路径
|
||||
*/
|
||||
baseUrl: {
|
||||
// 开发环境接口前缀
|
||||
base: '',
|
||||
|
||||
// 打包开发环境接口前缀
|
||||
dev: '',
|
||||
|
||||
// 打包生产环境接口前缀
|
||||
pro: '',
|
||||
|
||||
// 打包测试环境接口前缀
|
||||
test: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 接口成功返回状态码
|
||||
*/
|
||||
code: 0,
|
||||
|
||||
/**
|
||||
* 接口请求超时时间
|
||||
*/
|
||||
timeout: 60000,
|
||||
|
||||
/**
|
||||
* 默认接口请求类型
|
||||
* 可选值:application/x-www-form-urlencoded multipart/form-data
|
||||
*/
|
||||
defaultHeaders: 'application/json',
|
||||
|
||||
interceptors: {
|
||||
//请求拦截
|
||||
// requestInterceptors: (config) => {
|
||||
// return config
|
||||
// },
|
||||
// 响应拦截器
|
||||
// responseInterceptors: (result: AxiosResponse) => {
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
|
||||
if (
|
||||
config.method === 'post' &&
|
||||
(config.headers as AxiosRequestHeaders)['Content-Type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
if (config.method === 'get' && config.params) {
|
||||
let url = config.url as string
|
||||
url += '?'
|
||||
const keys = Object.keys(config.params)
|
||||
for (const key of keys) {
|
||||
if (config.params[key] !== void 0 && config.params[key] !== null) {
|
||||
url += `${key}=${encodeURIComponent(config.params[key])}&`
|
||||
}
|
||||
}
|
||||
url = url.substring(0, url.length - 1)
|
||||
config.params = {}
|
||||
config.url = url
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
|
||||
if (response?.config?.responseType === 'blob') {
|
||||
// 如果是文件流,直接过
|
||||
return response
|
||||
} else if (response.data.code === config.code) {
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage.error(response?.data?.message)
|
||||
if (response?.data?.code === 401) {
|
||||
// token过期
|
||||
clear()
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { defaultResponseInterceptors, defaultRequestInterceptors }
|
||||
export default config
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* 请求成功状态码
|
||||
*/
|
||||
export const SUCCESS_CODE = 0
|
||||
|
||||
/**
|
||||
* 请求contentType
|
||||
*/
|
||||
export const CONTENT_TYPE = 'application/json'
|
||||
|
||||
/**
|
||||
* 请求超时时间
|
||||
*/
|
||||
export const REQUEST_TIMEOUT = 60000
|
||||
|
||||
/**
|
||||
* 不重定向白名单
|
||||
*/
|
||||
export const NO_REDIRECT_WHITE_LIST = ['/login']
|
||||
|
||||
/**
|
||||
* 不重置路由白名单
|
||||
*/
|
||||
export const NO_RESET_WHITE_LIST = ['Redirect', 'Login', 'NoFind', 'Root']
|
|
@ -91,7 +91,8 @@ export default {
|
|||
tagsViewIcon: 'Tags view icon',
|
||||
dynamicRouter: 'Dynamic router',
|
||||
reExperienced: 'Please exit the login experience again',
|
||||
fixedMenu: 'Fixed menu'
|
||||
fixedMenu: 'Fixed menu',
|
||||
serverDynamicRouter: 'Server dynamic router'
|
||||
},
|
||||
size: {
|
||||
default: 'Default',
|
||||
|
|
|
@ -91,7 +91,8 @@ export default {
|
|||
tagsViewIcon: '标签页图标',
|
||||
dynamicRouter: '动态路由',
|
||||
reExperienced: '请重新退出登录体验',
|
||||
fixedMenu: '固定菜单'
|
||||
fixedMenu: '固定菜单',
|
||||
serverDynamicRouter: '服务端动态路由'
|
||||
},
|
||||
size: {
|
||||
default: '默认',
|
||||
|
|
|
@ -1,28 +1,24 @@
|
|||
import router from './router'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
import { NO_REDIRECT_WHITE_LIST } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const { start, done } = useNProgress()
|
||||
|
||||
const { loadStart, loadDone } = usePageLoading()
|
||||
|
||||
const whiteList = ['/login'] // 不重定向白名单
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
start()
|
||||
loadStart()
|
||||
if (getStorage(appStore.getUserInfo)) {
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
const appStore = useAppStoreWithOut()
|
||||
const userStore = useUserStoreWithOut()
|
||||
if (userStore.getUserInfo) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
|
@ -32,16 +28,15 @@ router.beforeEach(async (to, from, next) => {
|
|||
}
|
||||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = getStorage('roleRouters') || []
|
||||
const userInfo = getStorage(appStore.getUserInfo)
|
||||
const roleRouters = userStore.getRoleRouters || []
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
appStore.serverDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none')
|
||||
await permissionStore.generateRoutes('static')
|
||||
}
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
|
@ -54,7 +49,7 @@ router.beforeEach(async (to, from, next) => {
|
|||
next(nextData)
|
||||
}
|
||||
} else {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPersist from 'pinia-plugin-persist'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
store.use(piniaPersist)
|
||||
store.use(piniaPluginPersistedstate)
|
||||
|
||||
export const setupStore = (app: App<Element>) => {
|
||||
app.use(store)
|
||||
|
|
|
@ -2,9 +2,6 @@ import { defineStore } from 'pinia'
|
|||
import { store } from '../index'
|
||||
import { setCssVar, humpToUnderline } from '@/utils'
|
||||
import { ElMessage, ComponentSize } from 'element-plus'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
|
||||
const { getStorage, setStorage } = useStorage()
|
||||
|
||||
interface AppState {
|
||||
breadcrumb: boolean
|
||||
|
@ -21,10 +18,10 @@ interface AppState {
|
|||
fixedHeader: boolean
|
||||
greyMode: boolean
|
||||
dynamicRouter: boolean
|
||||
serverDynamicRouter: boolean
|
||||
pageLoading: boolean
|
||||
layout: LayoutType
|
||||
title: string
|
||||
userInfo: string
|
||||
isDark: boolean
|
||||
currentSize: ComponentSize
|
||||
sizeMap: ComponentSize[]
|
||||
|
@ -37,12 +34,10 @@ interface AppState {
|
|||
export const useAppStore = defineStore('app', {
|
||||
state: (): AppState => {
|
||||
return {
|
||||
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其它项目冲突
|
||||
sizeMap: ['default', 'large', 'small'],
|
||||
mobile: false, // 是否是移动端
|
||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||
pageLoading: false, // 路由跳转loading
|
||||
|
||||
breadcrumb: true, // 面包屑
|
||||
breadcrumbIcon: true, // 面包屑图标
|
||||
collapse: false, // 折叠菜单
|
||||
|
@ -57,13 +52,14 @@ export const useAppStore = defineStore('app', {
|
|||
fixedHeader: true, // 固定toolheader
|
||||
footer: true, // 显示页脚
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
dynamicRouter: getStorage('dynamicRouter') || false, // 是否动态路由
|
||||
fixedMenu: getStorage('fixedMenu') || false, // 是否固定菜单
|
||||
dynamicRouter: true, // 是否动态路由
|
||||
serverDynamicRouter: true, // 是否服务端渲染动态路由
|
||||
fixedMenu: false, // 是否固定菜单
|
||||
|
||||
layout: getStorage('layout') || 'classic', // layout布局
|
||||
isDark: getStorage('isDark') || false, // 是否是暗黑模式
|
||||
currentSize: getStorage('default') || 'default', // 组件尺寸
|
||||
theme: getStorage('theme') || {
|
||||
layout: 'classic', // layout布局
|
||||
isDark: false, // 是否是暗黑模式
|
||||
currentSize: 'default', // 组件尺寸
|
||||
theme: {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
// 左侧菜单边框颜色
|
||||
|
@ -138,6 +134,9 @@ export const useAppStore = defineStore('app', {
|
|||
getDynamicRouter(): boolean {
|
||||
return this.dynamicRouter
|
||||
},
|
||||
getServerDynamicRouter(): boolean {
|
||||
return this.serverDynamicRouter
|
||||
},
|
||||
getFixedMenu(): boolean {
|
||||
return this.fixedMenu
|
||||
},
|
||||
|
@ -150,9 +149,6 @@ export const useAppStore = defineStore('app', {
|
|||
getTitle(): string {
|
||||
return this.title
|
||||
},
|
||||
getUserInfo(): string {
|
||||
return this.userInfo
|
||||
},
|
||||
getIsDark(): boolean {
|
||||
return this.isDark
|
||||
},
|
||||
|
@ -213,11 +209,12 @@ export const useAppStore = defineStore('app', {
|
|||
this.greyMode = greyMode
|
||||
},
|
||||
setDynamicRouter(dynamicRouter: boolean) {
|
||||
setStorage('dynamicRouter', dynamicRouter)
|
||||
this.dynamicRouter = dynamicRouter
|
||||
},
|
||||
setServerDynamicRouter(serverDynamicRouter: boolean) {
|
||||
this.serverDynamicRouter = serverDynamicRouter
|
||||
},
|
||||
setFixedMenu(fixedMenu: boolean) {
|
||||
setStorage('fixedMenu', fixedMenu)
|
||||
this.fixedMenu = fixedMenu
|
||||
},
|
||||
setPageLoading(pageLoading: boolean) {
|
||||
|
@ -229,7 +226,6 @@ export const useAppStore = defineStore('app', {
|
|||
return
|
||||
}
|
||||
this.layout = layout
|
||||
setStorage('layout', this.layout)
|
||||
},
|
||||
setTitle(title: string) {
|
||||
this.title = title
|
||||
|
@ -243,18 +239,15 @@ export const useAppStore = defineStore('app', {
|
|||
document.documentElement.classList.add('light')
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
setStorage('isDark', this.isDark)
|
||||
},
|
||||
setCurrentSize(currentSize: ComponentSize) {
|
||||
this.currentSize = currentSize
|
||||
setStorage('currentSize', this.currentSize)
|
||||
},
|
||||
setMobile(mobile: boolean) {
|
||||
this.mobile = mobile
|
||||
},
|
||||
setTheme(theme: ThemeTypes) {
|
||||
this.theme = Object.assign(this.theme, theme)
|
||||
setStorage('theme', this.theme)
|
||||
},
|
||||
setCssVarTheme() {
|
||||
for (const key in this.theme) {
|
||||
|
@ -264,7 +257,8 @@ export const useAppStore = defineStore('app', {
|
|||
setFooter(footer: boolean) {
|
||||
this.footer = footer
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useAppStoreWithOut = () => {
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
|
||||
export interface DictState {
|
||||
isSetDict: boolean
|
||||
dictObj: Recordable
|
||||
}
|
||||
|
||||
export const useDictStore = defineStore('dict', {
|
||||
state: (): DictState => ({
|
||||
isSetDict: false,
|
||||
dictObj: {}
|
||||
}),
|
||||
getters: {
|
||||
getDictObj(): Recordable {
|
||||
return this.dictObj
|
||||
},
|
||||
getIsSetDict(): boolean {
|
||||
return this.isSetDict
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setDictObj(dictObj: Recordable) {
|
||||
this.dictObj = dictObj
|
||||
},
|
||||
setIsSetDict(isSetDict: boolean) {
|
||||
this.isSetDict = isSetDict
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const useDictStoreWithOut = () => {
|
||||
return useDictStore(store)
|
||||
}
|
|
@ -40,10 +40,7 @@ export const useLockStore = defineStore('lock', {
|
|||
}
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [{ key: 'lock', storage: localStorage }]
|
||||
}
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useLockStoreWithOut = () => {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { asyncRouterMap, constantRouterMap } from '@/router'
|
||||
import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
import {
|
||||
generateRoutesByFrontEnd,
|
||||
generateRoutesByServer,
|
||||
flatMultiLevelRoutes
|
||||
} from '@/utils/routerHelper'
|
||||
import { store } from '../index'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
|
@ -34,17 +38,17 @@ export const usePermissionStore = defineStore('permission', {
|
|||
},
|
||||
actions: {
|
||||
generateRoutes(
|
||||
type: 'admin' | 'test' | 'none',
|
||||
type: 'server' | 'frontEnd' | 'static',
|
||||
routers?: AppCustomRouteRecordRaw[] | string[]
|
||||
): Promise<unknown> {
|
||||
return new Promise<void>((resolve) => {
|
||||
let routerMap: AppRouteRecordRaw[] = []
|
||||
if (type === 'admin') {
|
||||
if (type === 'server') {
|
||||
// 模拟后端过滤菜单
|
||||
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
|
||||
} else if (type === 'test') {
|
||||
routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
|
||||
} else if (type === 'frontEnd') {
|
||||
// 模拟前端过滤菜单
|
||||
routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
|
||||
routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
|
||||
} else {
|
||||
// 直接读取静态路由表
|
||||
routerMap = cloneDeep(asyncRouterMap)
|
||||
|
@ -72,6 +76,9 @@ export const usePermissionStore = defineStore('permission', {
|
|||
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
||||
this.menuTabRouters = routers
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
paths: ['routers', 'addRouters', 'menuTabRouters']
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
|
|||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { findIndex } from '@/utils'
|
||||
import { useUserStoreWithOut } from './user'
|
||||
|
||||
export interface TagsViewState {
|
||||
visitedViews: RouteLocationNormalizedLoaded[]
|
||||
cachedViews: Set<string>
|
||||
selectedTag?: RouteLocationNormalizedLoaded
|
||||
}
|
||||
|
||||
export const useTagsViewStore = defineStore('tagsView', {
|
||||
state: (): TagsViewState => ({
|
||||
visitedViews: [],
|
||||
cachedViews: new Set()
|
||||
cachedViews: new Set(),
|
||||
selectedTag: undefined
|
||||
}),
|
||||
getters: {
|
||||
getVisitedViews(): RouteLocationNormalizedLoaded[] {
|
||||
|
@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
},
|
||||
getCachedViews(): string[] {
|
||||
return Array.from(this.cachedViews)
|
||||
},
|
||||
getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
|
||||
return this.selectedTag
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -44,7 +50,7 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
const cacheMap: Set<string> = new Set()
|
||||
for (const v of this.visitedViews) {
|
||||
const item = getRawRoute(v)
|
||||
const needCache = !item.meta?.noCache
|
||||
const needCache = !item?.meta?.noCache
|
||||
if (!needCache) {
|
||||
continue
|
||||
}
|
||||
|
@ -84,8 +90,12 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
},
|
||||
// 删除所有tag
|
||||
delAllVisitedViews() {
|
||||
const userStore = useUserStoreWithOut()
|
||||
|
||||
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
||||
this.visitedViews = []
|
||||
this.visitedViews = userStore.getUserInfo
|
||||
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
||||
: []
|
||||
},
|
||||
// 删除其它
|
||||
delOthersViews(view: RouteLocationNormalizedLoaded) {
|
||||
|
@ -131,8 +141,21 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
// 设置当前选中的tag
|
||||
setSelectedTag(tag: RouteLocationNormalizedLoaded) {
|
||||
this.selectedTag = tag
|
||||
},
|
||||
setTitle(title: string, path?: string) {
|
||||
for (const v of this.visitedViews) {
|
||||
if (v.path === (path ?? this.selectedTag?.path)) {
|
||||
v.meta.title = title
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: false
|
||||
})
|
||||
|
||||
export const useTagsViewStoreWithOut = () => {
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { UserLoginType, UserType } from '@/api/login/types'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { loginOutApi } from '@/api/login'
|
||||
import { useTagsViewStore } from './tagsView'
|
||||
import router from '@/router'
|
||||
|
||||
interface UserState {
|
||||
userInfo?: UserType
|
||||
tokenKey: string
|
||||
token: string
|
||||
roleRouters?: string[] | AppCustomRouteRecordRaw[]
|
||||
rememberMe: boolean
|
||||
loginInfo?: UserLoginType
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: (): UserState => {
|
||||
return {
|
||||
userInfo: undefined,
|
||||
tokenKey: 'Authorization',
|
||||
token: '',
|
||||
roleRouters: undefined,
|
||||
// 记住我
|
||||
rememberMe: true,
|
||||
loginInfo: undefined
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getTokenKey(): string {
|
||||
return this.tokenKey
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token
|
||||
},
|
||||
getUserInfo(): UserType | undefined {
|
||||
return this.userInfo
|
||||
},
|
||||
getRoleRouters(): string[] | AppCustomRouteRecordRaw[] | undefined {
|
||||
return this.roleRouters
|
||||
},
|
||||
getRememberMe(): boolean {
|
||||
return this.rememberMe
|
||||
},
|
||||
getLoginInfo(): UserLoginType | undefined {
|
||||
return this.loginInfo
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setTokenKey(tokenKey: string) {
|
||||
this.tokenKey = tokenKey
|
||||
},
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
},
|
||||
setUserInfo(userInfo?: UserType) {
|
||||
this.userInfo = userInfo
|
||||
},
|
||||
setRoleRouters(roleRouters: string[] | AppCustomRouteRecordRaw[]) {
|
||||
this.roleRouters = roleRouters
|
||||
},
|
||||
logoutConfirm() {
|
||||
const { t } = useI18n()
|
||||
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await loginOutApi().catch(() => {})
|
||||
if (res) {
|
||||
this.reset()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
reset() {
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
tagsViewStore.delAllViews()
|
||||
this.setToken('')
|
||||
this.setUserInfo(undefined)
|
||||
this.setRoleRouters([])
|
||||
router.replace('/login')
|
||||
},
|
||||
logout() {
|
||||
this.reset()
|
||||
},
|
||||
setRememberMe(rememberMe: boolean) {
|
||||
this.rememberMe = rememberMe
|
||||
},
|
||||
setLoginInfo(loginInfo: UserLoginType | undefined) {
|
||||
this.loginInfo = loginInfo
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useUserStoreWithOut = () => {
|
||||
return useUserStore(store)
|
||||
}
|
|
@ -3,7 +3,6 @@ import type {
|
|||
Router,
|
||||
RouteLocationNormalized,
|
||||
RouteRecordNormalized,
|
||||
RouteMeta,
|
||||
RouteRecordRaw
|
||||
} from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
|
@ -39,7 +38,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
|
|||
}
|
||||
|
||||
// 前端控制路由生成
|
||||
export const generateRoutesFn1 = (
|
||||
export const generateRoutesByFrontEnd = (
|
||||
routes: AppRouteRecordRaw[],
|
||||
keys: string[],
|
||||
basePath = '/'
|
||||
|
@ -47,7 +46,7 @@ export const generateRoutesFn1 = (
|
|||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
const meta = route.meta as RouteMeta
|
||||
const meta = route.meta ?? {}
|
||||
// skip some route
|
||||
if (meta.hidden && !meta.canTo) {
|
||||
continue
|
||||
|
@ -70,7 +69,7 @@ export const generateRoutesFn1 = (
|
|||
if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
|
||||
data = Object.assign({}, route)
|
||||
} else {
|
||||
const routePath = onlyOneChild ?? pathResolve(basePath, route.path)
|
||||
const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
|
||||
if (routePath === item || meta.followRoute === item) {
|
||||
data = Object.assign({}, route)
|
||||
}
|
||||
|
@ -79,7 +78,11 @@ export const generateRoutesFn1 = (
|
|||
|
||||
// recursive child routes
|
||||
if (route.children && data) {
|
||||
data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
|
||||
data.children = generateRoutesByFrontEnd(
|
||||
route.children,
|
||||
keys,
|
||||
pathResolve(basePath, data.path)
|
||||
)
|
||||
}
|
||||
if (data) {
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
|
@ -89,7 +92,7 @@ export const generateRoutesFn1 = (
|
|||
}
|
||||
|
||||
// 后端控制路由生成
|
||||
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
|
@ -112,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
|
|||
}
|
||||
// recursive child routes
|
||||
if (route.children) {
|
||||
data.children = generateRoutesFn2(route.children)
|
||||
data.children = generateRoutesByServer(route.children)
|
||||
}
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
}
|
||||
|
@ -122,7 +125,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
|
|||
export const pathResolve = (parentPath: string, path: string) => {
|
||||
if (isUrl(path)) return path
|
||||
const childPath = path.startsWith('/') || !path ? path : `/${path}`
|
||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/')
|
||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/').trim()
|
||||
}
|
||||
|
||||
// 路由降级
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script setup lang="tsx">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { reactive, ref, watch, onMounted, unref } from 'vue'
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
||||
import { ElCheckbox, ElLink } from 'element-plus'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
@ -13,6 +12,8 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
|||
import { UserType } from '@/api/login/types'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
|
@ -20,12 +21,12 @@ const emit = defineEmits(['to-register'])
|
|||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
|
||||
const { setStorage } = useStorage()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = {
|
||||
|
@ -50,19 +51,19 @@ const schema = reactive<FormSchema[]>([
|
|||
{
|
||||
field: 'username',
|
||||
label: t('login.username'),
|
||||
value: 'admin',
|
||||
// value: 'admin',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: t('login.usernamePlaceholder')
|
||||
placeholder: 'admin or test'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('login.password'),
|
||||
value: 'admin',
|
||||
// value: 'admin',
|
||||
component: 'InputPassword',
|
||||
colProps: {
|
||||
span: 24
|
||||
|
@ -71,7 +72,7 @@ const schema = reactive<FormSchema[]>([
|
|||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: t('login.passwordPlaceholder')
|
||||
placeholder: 'admin or test'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -107,14 +108,19 @@ const schema = reactive<FormSchema[]>([
|
|||
return (
|
||||
<>
|
||||
<div class="w-[100%]">
|
||||
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={signIn}>
|
||||
<BaseButton
|
||||
loading={loading.value}
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
onClick={signIn}
|
||||
>
|
||||
{t('login.login')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<ElButton class="w-[100%]" onClick={toRegister}>
|
||||
<BaseButton class="w-[100%]" onClick={toRegister}>
|
||||
{t('login.register')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -180,10 +186,21 @@ const schema = reactive<FormSchema[]>([
|
|||
|
||||
const iconSize = 30
|
||||
|
||||
const remember = ref(false)
|
||||
const remember = ref(userStore.getRememberMe)
|
||||
|
||||
const initLoginInfo = () => {
|
||||
const loginInfo = userStore.getLoginInfo
|
||||
if (loginInfo) {
|
||||
const { username, password } = loginInfo
|
||||
setValues({ username, password })
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
initLoginInfo()
|
||||
})
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
const { getFormData, getElFormExpose } = formMethods
|
||||
const { getFormData, getElFormExpose, setValues } = formMethods
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
|
@ -205,13 +222,6 @@ watch(
|
|||
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await permissionStore.generateRoutes('none').catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
|
||||
const formRef = await getElFormExpose()
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
|
@ -222,12 +232,22 @@ const signIn = async () => {
|
|||
const res = await loginApi(formData)
|
||||
|
||||
if (res) {
|
||||
setStorage(appStore.getUserInfo, res.data)
|
||||
// 是否记住我
|
||||
if (unref(remember)) {
|
||||
userStore.setLoginInfo({
|
||||
username: formData.username,
|
||||
password: formData.password
|
||||
})
|
||||
} else {
|
||||
userStore.setLoginInfo(undefined)
|
||||
}
|
||||
userStore.setRememberMe(unref(remember))
|
||||
userStore.setUserInfo(res.data)
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
getRole()
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none').catch(() => {})
|
||||
await permissionStore.generateRoutes('static').catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
|
@ -248,17 +268,16 @@ const getRole = async () => {
|
|||
const params = {
|
||||
roleName: formData.username
|
||||
}
|
||||
// admin - 模拟后端过滤菜单
|
||||
// test - 模拟前端过滤菜单
|
||||
const res =
|
||||
formData.username === 'admin' ? await getAdminRoleApi(params) : await getTestRoleApi(params)
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await getAdminRoleApi(params)
|
||||
: await getTestRoleApi(params)
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
|
||||
formData.username === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('test', routers).catch(() => {})
|
||||
userStore.setRoleRouters(routers)
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
|
|
|
@ -215,6 +215,8 @@ module.exports = {
|
|||
extends: ['stylelint-config-recommended', 'stylelint-config-html'],
|
||||
rules: {
|
||||
'keyframes-name-pattern': null,
|
||||
'selector-class-pattern': null,
|
||||
'no-duplicate-selectors': null,
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
"@intlify/unplugin-vue-i18n/types",
|
||||
"vite/client",
|
||||
"element-plus/global",
|
||||
"vite-plugin-svg-icons/client",
|
||||
"unplugin-vue-define-options/macros-global"
|
||||
"@types/qrcode",
|
||||
"vite-plugin-svg-icons/client"
|
||||
]
|
||||
},
|
||||
"include": ["src", "types/**/*.d.ts", "mock/**/*.ts"]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Icon: typeof import('../components/Icon/src/Icon.vue')['default']
|
||||
Icon: (typeof import('../components/Icon/src/Icon.vue'))['default']
|
||||
BaseButton: (typeof import('../components/Button/src/Button.vue'))['default']
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { CSSProperties } from 'vue'
|
||||
import { RawAxiosRequestHeaders } from 'axios'
|
||||
declare global {
|
||||
declare interface Fn<T = any> {
|
||||
(...arg: T[]): T
|
||||
|
@ -25,7 +26,7 @@ declare global {
|
|||
|
||||
declare type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
|
||||
|
||||
declare type AxiosHeaders =
|
||||
declare type AxiosContentType =
|
||||
| 'application/json'
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'multipart/form-data'
|
||||
|
@ -39,12 +40,12 @@ declare global {
|
|||
data?: any
|
||||
url?: string
|
||||
method?: AxiosMethod
|
||||
headersType?: string
|
||||
headers?: RawAxiosRequestHeaders
|
||||
responseType?: AxiosResponseType
|
||||
}
|
||||
|
||||
declare interface IResponse<T = any> {
|
||||
code: string
|
||||
code: number
|
||||
data: T extends any ? T : T & any
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import { viteMockServe } from 'vite-plugin-mock'
|
|||
import PurgeIcons from 'vite-plugin-purge-icons'
|
||||
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import DefineOptions from "unplugin-vue-define-options/vite"
|
||||
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
|
||||
|
@ -32,9 +31,13 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
return {
|
||||
base: env.VITE_BASE_PATH,
|
||||
plugins: [
|
||||
Vue(),
|
||||
Vue({
|
||||
script: {
|
||||
// 开启defineModel
|
||||
defineModel: true
|
||||
}
|
||||
}),
|
||||
VueJsx(),
|
||||
// WindiCSS(),
|
||||
progress(),
|
||||
createStyleImportPlugin({
|
||||
resolves: [ElementPlusResolve()],
|
||||
|
@ -75,7 +78,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
setupProdMockServer()
|
||||
`
|
||||
}),
|
||||
DefineOptions(),
|
||||
ViteEjsPlugin({
|
||||
title: env.VITE_APP_TITLE
|
||||
}),
|
||||
|
@ -142,11 +144,10 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
'@vueuse/core',
|
||||
'axios',
|
||||
'qs',
|
||||
'echarts',
|
||||
'echarts-wordcloud',
|
||||
'@wangeditor/editor',
|
||||
'@wangeditor/editor-for-vue'
|
||||
'@zxcvbn-ts/core',
|
||||
'dayjs',
|
||||
'xgplayer'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue