feat: 更新mini分支

This commit is contained in:
kailong321200875 2023-12-19 09:46:22 +08:00
parent a1e89b7c8a
commit 607b73a7b3
58 changed files with 930 additions and 1225 deletions

View File

@ -2,7 +2,7 @@
NODE_ENV=development
# 接口前缀
VITE_API_BASE_PATH=base
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/

View File

@ -2,7 +2,7 @@
NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=dev
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/dist-dev/

View File

@ -2,7 +2,7 @@
NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=pro
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/vue-element-plus-admin/

View File

@ -2,7 +2,7 @@
NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=pro
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/

View File

@ -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
})

91
mock/role/index.mock.ts Normal file
View File

@ -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[]

View File

@ -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[]

View File

@ -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[]
]

View File

@ -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": {

View File

@ -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' })
}

View File

@ -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' })
}

View File

@ -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
}

View File

@ -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' })
}

View File

@ -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
}

View File

@ -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 })
}

View File

@ -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
}

View File

@ -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 })
}

View File

@ -1,5 +0,0 @@
import request from '@/config/axios'
export const getMenuListApi = () => {
return request.get({ url: '/menu/list' })
}

View File

@ -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 } })
}

View File

@ -1,9 +0,0 @@
export type TableData = {
id: string
author: string
title: string
content: string
importance: number
display_time: string
pageviews: number
}

45
src/axios/config.ts Normal file
View File

@ -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 }

View File

@ -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
}
})
}

View File

@ -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) => {

View File

@ -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,

View File

@ -0,0 +1,3 @@
import BaseButton from './src/Button.vue'
export { BaseButton }

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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,7 +49,6 @@ export const useRenderMenuItem = (
</ElSubMenu>
)
}
}
})
}

View File

@ -88,6 +88,9 @@ const newSchema = computed(() => {
/>
</div>
)
},
label: () => {
return <span>&nbsp;</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"
/>

View File

@ -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>

View File

@ -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" />

View File

@ -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;

View File

@ -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: () => ({})
},
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
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'],
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">
{isImgPath(url) ? (
<ElImage
src={url}
fit="cover"
class="w-[100%] h-100px"
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,28 +496,53 @@ 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).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}
@ -524,6 +556,8 @@ export default defineComponent({
...tableSlots
}}
</ElTable>
</>
)}
{unref(getProps).pagination ? (
<ElPagination
v-model:pageSize={pageSizeRef.value}

View File

@ -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>

View File

@ -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)
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
import VideoPlayerViewer from './src/VideoPlayerViewer.vue'
export { VideoPlayerViewer }

View File

@ -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>

View File

@ -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)
}

View File

@ -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

24
src/constants/index.ts Normal file
View File

@ -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']

View File

@ -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',

View File

@ -91,7 +91,8 @@ export default {
tagsViewIcon: '标签页图标',
dynamicRouter: '动态路由',
reExperienced: '请重新退出登录体验',
fixedMenu: '固定菜单'
fixedMenu: '固定菜单',
serverDynamicRouter: '服务端动态路由'
},
size: {
default: '默认',

View File

@ -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}`) // 否则全部重定向到登录页

View File

@ -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)

View File

@ -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 = () => {

View File

@ -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)
}

View File

@ -40,10 +40,7 @@ export const useLockStore = defineStore('lock', {
}
}
},
persist: {
enabled: true,
strategies: [{ key: 'lock', storage: localStorage }]
}
persist: true
})
export const useLockStoreWithOut = () => {

View File

@ -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']
}
})

View File

@ -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 = () => {

102
src/store/modules/user.ts Normal file
View File

@ -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)
}

View File

@ -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()
}
// 路由降级

View File

@ -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) // 访

View File

@ -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,
{

View File

@ -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"]

View File

@ -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']
}
}

7
types/global.d.ts vendored
View File

@ -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
}

View File

@ -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,10 +144,9 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
'@vueuse/core',
'axios',
'qs',
'echarts',
'echarts-wordcloud',
'@wangeditor/editor',
'@wangeditor/editor-for-vue'
'@zxcvbn-ts/core',
'dayjs',
'xgplayer'
]
}
}