mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-10-05 00:15:56 +08:00
Compare commits
No commits in common. "main" and "v.0.9.16" have entirely different histories.
2
.env
2
.env
@ -20,7 +20,7 @@ VITE_STORAGE_PREFIX =
|
|||||||
VITE_COPYRIGHT_INFO = Copyright © 2024 chansee97
|
VITE_COPYRIGHT_INFO = Copyright © 2024 chansee97
|
||||||
|
|
||||||
# 自动刷新token
|
# 自动刷新token
|
||||||
VITE_AUTO_REFRESH_TOKEN = N
|
VITE_AUTO_REFRESH_TOKEN = Y
|
||||||
|
|
||||||
# 默认多语言 enUS | zhCN
|
# 默认多语言 enUS | zhCN
|
||||||
VITE_DEFAULT_LANG = enUS
|
VITE_DEFAULT_LANG = enUS
|
||||||
|
2
.env.dev
2
.env.dev
@ -1,2 +1,2 @@
|
|||||||
# 是否开启服务接口代理 Y | N
|
# 是否开启服务接口代理 Y | N
|
||||||
VITE_HTTP_PROXY=N
|
VITE_HTTP_PROXY=Y
|
||||||
|
6
.env.test
Normal file
6
.env.test
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 是否开启压缩资源
|
||||||
|
VITE_BUILD_COMPRESS=N
|
||||||
|
|
||||||
|
# 压缩算法 gzip | brotliCompress | deflate | deflateRaw
|
||||||
|
VITE_COMPRESS_TYPE=gzip
|
||||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -10,7 +10,6 @@
|
|||||||
"antfu.iconify",
|
"antfu.iconify",
|
||||||
"kisstkondoros.vscode-gutter-preview",
|
"kisstkondoros.vscode-gutter-preview",
|
||||||
"antfu.unocss",
|
"antfu.unocss",
|
||||||
"vue.volar",
|
"vue.volar"
|
||||||
"tu6ge.naive-ui-intelligence"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,11 @@ export interface ServiceProxyPluginOptions {
|
|||||||
serviceConfig: FullServiceConfig
|
serviceConfig: FullServiceConfig
|
||||||
/** 代理路径前缀(可选,默认为 'proxy-') */
|
/** 代理路径前缀(可选,默认为 'proxy-') */
|
||||||
proxyPrefix?: string
|
proxyPrefix?: string
|
||||||
|
/** 开发环境名称(可选,默认为 'development') */
|
||||||
|
devEnvName?: ServiceEnvType
|
||||||
/** 是否启用代理配置 */
|
/** 是否启用代理配置 */
|
||||||
enableProxy?: boolean
|
enableProxy?: boolean
|
||||||
/** 环境变量名(可选,默认为 '__URL_MAP__') */
|
/** 环境变量名(可选,默认为 '__PROXY_MAPPING__') */
|
||||||
envName?: string
|
envName?: string
|
||||||
/** d.ts 类型文件生成路径(可选,如果传入路径则在该路径生成 d.ts 类型文件) */
|
/** d.ts 类型文件生成路径(可选,如果传入路径则在该路径生成 d.ts 类型文件) */
|
||||||
dts?: string
|
dts?: string
|
||||||
@ -45,15 +47,16 @@ export interface ServiceProxyPluginOptions {
|
|||||||
export default function createServiceProxyPlugin(options: ServiceProxyPluginOptions) {
|
export default function createServiceProxyPlugin(options: ServiceProxyPluginOptions) {
|
||||||
const {
|
const {
|
||||||
serviceConfig,
|
serviceConfig,
|
||||||
|
devEnvName = 'development',
|
||||||
proxyPrefix = 'proxy-',
|
proxyPrefix = 'proxy-',
|
||||||
enableProxy = true,
|
enableProxy = true,
|
||||||
envName = '__URL_MAP__',
|
envName = '__PROXY_MAPPING__',
|
||||||
dts,
|
dts,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'vite-auto-proxy',
|
name: 'vite-auto-proxy',
|
||||||
config(config: UserConfig, { mode, command }: { mode: string, command: 'build' | 'serve' }) {
|
config(config: UserConfig, { command }: { mode: string, command: 'build' | 'serve' }) {
|
||||||
// 只在开发环境(serve命令)时生成代理配置
|
// 只在开发环境(serve命令)时生成代理配置
|
||||||
const isDev = command === 'serve'
|
const isDev = command === 'serve'
|
||||||
|
|
||||||
@ -63,9 +66,9 @@ export default function createServiceProxyPlugin(options: ServiceProxyPluginOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!enableProxy || !isDev) {
|
if (!enableProxy || !isDev) {
|
||||||
|
// 在非开发环境下,生成原始地址映射(path 和 rawPath 都是原始地址)
|
||||||
const rawMapping: ProxyMapping = {}
|
const rawMapping: ProxyMapping = {}
|
||||||
const envConfig = serviceConfig[mode]
|
const envConfig = serviceConfig[devEnvName]
|
||||||
|
|
||||||
if (envConfig) {
|
if (envConfig) {
|
||||||
Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => {
|
Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => {
|
||||||
rawMapping[serviceName] = {
|
rawMapping[serviceName] = {
|
||||||
@ -73,12 +76,7 @@ export default function createServiceProxyPlugin(options: ServiceProxyPluginOpti
|
|||||||
rawPath: serviceUrl,
|
rawPath: serviceUrl,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.warn(`[auto-proxy] 已加载 ${Object.keys(envConfig).length} 个服务地址`)
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
console.warn(`[auto-proxy] 未找到环境 "${mode}" 的配置`)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.define[envName] = JSON.stringify(rawMapping)
|
config.define[envName] = JSON.stringify(rawMapping)
|
||||||
|
|
||||||
// 生成 d.ts 类型文件(如果指定了路径)
|
// 生成 d.ts 类型文件(如果指定了路径)
|
||||||
@ -88,9 +86,9 @@ export default function createServiceProxyPlugin(options: ServiceProxyPluginOpti
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(`[auto-proxy] 已加载${mode}模式 ${Object.keys(serviceConfig[mode]).length} 个服务地址`)
|
console.warn(`[auto-proxy] 已加载${devEnvName}模式 ${Object.keys(serviceConfig[devEnvName]).length} 个服务地址`)
|
||||||
|
|
||||||
const { proxyConfig, proxyMapping } = generateProxyFromServiceConfig(serviceConfig, mode, proxyPrefix)
|
const { proxyConfig, proxyMapping } = generateProxyFromServiceConfig(serviceConfig, devEnvName, proxyPrefix)
|
||||||
|
|
||||||
Object.entries(proxyMapping).forEach(([serviceName, proxyItem]) => {
|
Object.entries(proxyMapping).forEach(([serviceName, proxyItem]) => {
|
||||||
console.warn(`[auto-proxy] 服务: ${serviceName} | 代理地址: ${proxyItem.path} | 实际地址: ${proxyItem.rawPath}`)
|
console.warn(`[auto-proxy] 服务: ${serviceName} | 代理地址: ${proxyItem.path} | 实际地址: ${proxyItem.rawPath}`)
|
||||||
|
@ -83,6 +83,7 @@ export function createVitePlugins(env: ImportMetaEnv) {
|
|||||||
AutoProxy({
|
AutoProxy({
|
||||||
enableProxy: env.VITE_HTTP_PROXY === 'Y',
|
enableProxy: env.VITE_HTTP_PROXY === 'Y',
|
||||||
serviceConfig,
|
serviceConfig,
|
||||||
|
devEnvName: 'dev',
|
||||||
dts: 'src/typings/auto-proxy.d.ts',
|
dts: 'src/typings/auto-proxy.d.ts',
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="appLoading"></div>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -108,6 +108,9 @@
|
|||||||
"QRCode": "QR code",
|
"QRCode": "QR code",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"clipboard": "Clipboard",
|
"clipboard": "Clipboard",
|
||||||
|
"demo403": "403",
|
||||||
|
"demo404": "404",
|
||||||
|
"demo500": "500",
|
||||||
"dictionarySetting": "Dictionary settings",
|
"dictionarySetting": "Dictionary settings",
|
||||||
"documents": "Document",
|
"documents": "Document",
|
||||||
"documentsVite": "Vite",
|
"documentsVite": "Vite",
|
||||||
@ -119,6 +122,7 @@
|
|||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"editorMd": "MarkDown editor",
|
"editorMd": "MarkDown editor",
|
||||||
"editorRich": "Rich text editor",
|
"editorRich": "Rich text editor",
|
||||||
|
"error": "Exception page",
|
||||||
"icons": "Icon",
|
"icons": "Icon",
|
||||||
"justSuper": "Supervisible",
|
"justSuper": "Supervisible",
|
||||||
"map": "Map",
|
"map": "Map",
|
||||||
|
@ -151,6 +151,10 @@
|
|||||||
"permission": "权限",
|
"permission": "权限",
|
||||||
"permissionDemo": "权限示例",
|
"permissionDemo": "权限示例",
|
||||||
"justSuper": "super可见",
|
"justSuper": "super可见",
|
||||||
|
"error": "异常页",
|
||||||
|
"demo403": "403",
|
||||||
|
"demo404": "404",
|
||||||
|
"demo500": "500",
|
||||||
"setting": "系统设置",
|
"setting": "系统设置",
|
||||||
"accountSetting": "用户设置",
|
"accountSetting": "用户设置",
|
||||||
"dictionarySetting": "字典设置",
|
"dictionarySetting": "字典设置",
|
||||||
|
17
netlify.toml
Normal file
17
netlify.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[build]
|
||||||
|
publish = "dist"
|
||||||
|
command = "vite build --mode prod"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NODE_VERSION = "20"
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
[[headers]]
|
||||||
|
for = "/manifest.webmanifest"
|
||||||
|
|
||||||
|
[headers.values]
|
||||||
|
Content-Type = "application/manifest+json"
|
48
package.json
48
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "nova-admin",
|
"name": "nova-admin",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.9.18",
|
"version": "0.9.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "a clean and concise back-end management template based on Vue3, Vite5, Typescript, and Naive UI.",
|
"description": "a clean and concise back-end management template based on Vue3, Vite5, Typescript, and Naive UI.",
|
||||||
"author": {
|
"author": {
|
||||||
@ -38,9 +38,11 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --mode dev --port 9980",
|
"dev": "vite --mode dev --port 9980",
|
||||||
"dev:prod": "vite --mode production",
|
"dev:test": "vite --mode test",
|
||||||
"build": "vite build",
|
"dev:prod": "vite --mode prod",
|
||||||
|
"build": "vite build --mode prod",
|
||||||
"build:dev": "vite build --mode dev",
|
"build:dev": "vite build --mode dev",
|
||||||
|
"build:test": "vite build --mode test",
|
||||||
"preview": "vite preview --port 9981",
|
"preview": "vite preview --port 9981",
|
||||||
"lint": "eslint . && vue-tsc --noEmit",
|
"lint": "eslint . && vue-tsc --noEmit",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
@ -48,43 +50,43 @@
|
|||||||
"sizecheck": "npx vite-bundle-visualizer"
|
"sizecheck": "npx vite-bundle-visualizer"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^13.6.0",
|
"@vueuse/core": "^13.3.0",
|
||||||
"alova": "^3.3.4",
|
"alova": "^3.3.2",
|
||||||
"colord": "^2.9.3",
|
"colord": "^2.9.3",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"md-editor-v3": "^5.6.1",
|
"md-editor-v3": "^5.6.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.4.1",
|
"pinia-plugin-persistedstate": "^4.3.0",
|
||||||
"pro-naive-ui": "^2.4.3",
|
"pro-naive-ui": "^2.4.3",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"radash": "^12.1.1",
|
"radash": "^12.1.0",
|
||||||
"vue": "^3.5.18",
|
"vue": "^3.5.16",
|
||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^11.1.11",
|
"vue-i18n": "^11.1.5",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^5.0.0",
|
"@antfu/eslint-config": "^4.14.1",
|
||||||
"@iconify-json/icon-park-outline": "^1.2.2",
|
"@iconify-json/icon-park-outline": "^1.2.2",
|
||||||
"@iconify/vue": "^5.0.0",
|
"@iconify/vue": "^4.3.0",
|
||||||
"@types/node": "^24.1.0",
|
"@types/node": "^24.0.1",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
"@vitejs/plugin-vue-jsx": "^4.2.0",
|
||||||
"eslint": "^9.29.0",
|
"eslint": "^9.29.0",
|
||||||
"lint-staged": "^16.1.2",
|
"lint-staged": "^16.1.2",
|
||||||
"naive-ui": "^2.42.0",
|
"naive-ui": "^2.41.1",
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.86.3",
|
||||||
"simple-git-hooks": "^2.13.1",
|
"simple-git-hooks": "^2.13.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"unocss": "^66.3.3",
|
"unocss": "^66.2.0",
|
||||||
"unplugin-auto-import": "^19.3.0",
|
"unplugin-auto-import": "^19.3.0",
|
||||||
"unplugin-icons": "^22.2.0",
|
"unplugin-icons": "^22.1.0",
|
||||||
"unplugin-vue-components": "^28.8.0",
|
"unplugin-vue-components": "^28.7.0",
|
||||||
"vite": "^7.0.6",
|
"vite": "^6.3.5",
|
||||||
"vite-bundle-visualizer": "^1.2.1",
|
"vite-bundle-visualizer": "^1.2.1",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-vue-devtools": "8.0.0",
|
"vite-plugin-vue-devtools": "7.7.6",
|
||||||
"vue-tsc": "^3.0.5"
|
"vue-tsc": "^2.2.10"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "pnpm lint-staged"
|
"pre-commit": "pnpm lint-staged"
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
ignoredBuiltDependencies:
|
|
||||||
- '@parcel/watcher'
|
|
||||||
- esbuild
|
|
||||||
- simple-git-hooks
|
|
||||||
- vue-demi
|
|
||||||
- unrs-resolver
|
|
@ -1,9 +1,12 @@
|
|||||||
/** 不同请求服务的环境配置 */
|
/** 不同请求服务的环境配置 */
|
||||||
export const serviceConfig: Record<ServiceEnvType, Record<string, string>> = {
|
export const serviceConfig: Record<ServiceEnvType, Record<string, string>> = {
|
||||||
dev: {
|
dev: {
|
||||||
url: 'http://localhost:3000',
|
url: 'https://mock.apifox.cn/m1/4071143-0-default',
|
||||||
},
|
},
|
||||||
production: {
|
test: {
|
||||||
|
url: 'https://mock.apifox.cn/m1/4071143-0-default',
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
url: 'https://mock.apifox.cn/m1/4071143-0-default',
|
url: 'https://mock.apifox.cn/m1/4071143-0-default',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
30
src/App.vue
30
src/App.vue
@ -1,18 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppMain from './AppMain.vue'
|
import { naiveI18nOptions } from '@/utils'
|
||||||
import AppLoading from './components/common/AppLoading.vue'
|
import { darkTheme } from 'naive-ui'
|
||||||
|
import { useAppStore } from './store'
|
||||||
|
|
||||||
// 使用 Suspense 处理异步组件加载
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const naiveLocale = computed(() => {
|
||||||
|
return naiveI18nOptions[appStore.lang] ? naiveI18nOptions[appStore.lang] : naiveI18nOptions.enUS
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<n-config-provider
|
||||||
<!-- 异步组件 -->
|
class="wh-full" inline-theme-disabled :theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||||
<AppMain />
|
:locale="naiveLocale.locale" :date-locale="naiveLocale.dateLocale" :theme-overrides="appStore.theme"
|
||||||
|
>
|
||||||
<!-- 加载状态 -->
|
<naive-provider>
|
||||||
<template #fallback>
|
<router-view />
|
||||||
<AppLoading />
|
<Watermark :show-watermark="appStore.showWatermark" />
|
||||||
</template>
|
</naive-provider>
|
||||||
</Suspense>
|
</n-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { App } from 'vue'
|
|
||||||
import { installRouter } from '@/router'
|
|
||||||
import { installPinia } from '@/store'
|
|
||||||
import { naiveI18nOptions } from '@/utils'
|
|
||||||
import { darkTheme } from 'naive-ui'
|
|
||||||
import { useAppStore } from './store'
|
|
||||||
|
|
||||||
// 创建异步初始化 Promise - 这会让组件变成异步组件
|
|
||||||
const initializationPromise = (async () => {
|
|
||||||
// 获取当前应用实例
|
|
||||||
const app = getCurrentInstance()?.appContext.app
|
|
||||||
if (!app) {
|
|
||||||
throw new Error('Failed to get app instance')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册模块 Pinia
|
|
||||||
await installPinia(app)
|
|
||||||
|
|
||||||
// 注册模块 Vue-router
|
|
||||||
await installRouter(app)
|
|
||||||
|
|
||||||
// 注册模块 指令/静态资源
|
|
||||||
const modules = import.meta.glob<{ install: (app: App) => void }>('./modules/*.ts', {
|
|
||||||
eager: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.values(modules).forEach(module => app.use(module))
|
|
||||||
|
|
||||||
return true
|
|
||||||
})()
|
|
||||||
|
|
||||||
// 等待初始化完成 - 这使得整个 setup 函数变成异步的
|
|
||||||
await initializationPromise
|
|
||||||
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const naiveLocale = computed(() => {
|
|
||||||
return naiveI18nOptions[appStore.lang] ? naiveI18nOptions[appStore.lang] : naiveI18nOptions.enUS
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-config-provider
|
|
||||||
class="wh-full"
|
|
||||||
inline-theme-disabled
|
|
||||||
:theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
|
||||||
:locale="naiveLocale.locale"
|
|
||||||
:date-locale="naiveLocale.dateLocale"
|
|
||||||
:theme-overrides="appStore.theme"
|
|
||||||
>
|
|
||||||
<naive-provider>
|
|
||||||
<router-view />
|
|
||||||
<Watermark :show-watermark="appStore.showWatermark" />
|
|
||||||
</naive-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</template>
|
|
1
src/assets/svg/error-403.svg
Normal file
1
src/assets/svg/error-403.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
1
src/assets/svg/error-500.svg
Normal file
1
src/assets/svg/error-500.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 41 KiB |
36
src/components/common/ErrorTip.vue
Normal file
36
src/components/common/ErrorTip.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
/** 异常类型 403 404 500 */
|
||||||
|
type: '403' | '404' | '500'
|
||||||
|
}>()
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-col-center h-full">
|
||||||
|
<img
|
||||||
|
v-if="type === '403'"
|
||||||
|
src="@/assets/svg/error-403.svg"
|
||||||
|
alt=""
|
||||||
|
class="w-1/3"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="type === '404'"
|
||||||
|
src="@/assets/svg/error-404.svg"
|
||||||
|
alt=""
|
||||||
|
class="w-1/3"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="type === '500'"
|
||||||
|
src="@/assets/svg/error-500.svg"
|
||||||
|
alt=""
|
||||||
|
class="w-1/3"
|
||||||
|
>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
@click="router.push('/')"
|
||||||
|
>
|
||||||
|
{{ $t('app.backHome') }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -6,15 +6,11 @@ const routeStore = useRouteStore()
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-el
|
<n-el>
|
||||||
class="h-full"
|
|
||||||
:class="[
|
|
||||||
appStore.layoutMode === 'full-content' ? 'p-0' : 'p-16px',
|
|
||||||
]"
|
|
||||||
style="background-color: var(--action-color);"
|
|
||||||
>
|
|
||||||
<router-view
|
<router-view
|
||||||
v-slot="{ Component, route }"
|
v-slot="{ Component, route }"
|
||||||
|
class="flex-1 p-16px"
|
||||||
|
style="background-color: var(--action-color);"
|
||||||
>
|
>
|
||||||
<transition :name="appStore.transitionAnimation" mode="out-in">
|
<transition :name="appStore.transitionAnimation" mode="out-in">
|
||||||
<keep-alive :include="routeStore.cacheRoutes">
|
<keep-alive :include="routeStore.cacheRoutes">
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Search from '../header/Search.vue'
|
|
||||||
import Notices from '../header/Notices.vue'
|
|
||||||
import UserCenter from '../header/UserCenter.vue'
|
|
||||||
import Setting from './Setting.vue'
|
|
||||||
|
|
||||||
const showDrawer = defineModel<boolean>('show', { default: false })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<n-drawer
|
|
||||||
v-model:show="showDrawer"
|
|
||||||
:width="280"
|
|
||||||
placement="right"
|
|
||||||
:mask-closable="true"
|
|
||||||
:close-on-esc="true"
|
|
||||||
>
|
|
||||||
<n-drawer-content :native-scrollbar="false" :body-content-style="{ padding: '0' }">
|
|
||||||
<template #header>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<UserCenter />
|
|
||||||
<div class="ml-auto" />
|
|
||||||
<Search />
|
|
||||||
<Notices />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<DarkModeSwitch />
|
|
||||||
<LangsSwitch />
|
|
||||||
<div class="ml-auto" />
|
|
||||||
<Setting />
|
|
||||||
</template>
|
|
||||||
</n-drawer-content>
|
|
||||||
</n-drawer>
|
|
||||||
</template>
|
|
@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBoolean } from '@/hooks'
|
import { useBoolean } from '@/hooks'
|
||||||
import { useAppStore, useRouteStore } from '@/store'
|
import { useRouteStore } from '@/store'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
|
||||||
const routeStore = useRouteStore()
|
const routeStore = useRouteStore()
|
||||||
|
|
||||||
// 搜索值
|
// 搜索值
|
||||||
@ -144,14 +143,13 @@ function handleMouseEnter(index: number) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CommonWrapper @click="openModal">
|
<CommonWrapper @click="openModal">
|
||||||
<icon-park-outline-search />
|
<icon-park-outline-search /><n-tag round size="small" class="font-mono cursor-pointer">
|
||||||
<n-tag v-if="!appStore.isMobile" round size="small" class="font-mono cursor-pointer">
|
|
||||||
CtrlK
|
CtrlK
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</CommonWrapper>
|
</CommonWrapper>
|
||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="showModal"
|
v-model:show="showModal"
|
||||||
class="w-560px fixed top-60px inset-x-0 max-w-full"
|
class="w-560px fixed top-60px inset-x-0"
|
||||||
size="small"
|
size="small"
|
||||||
preset="card"
|
preset="card"
|
||||||
:segmented="{
|
:segmented="{
|
||||||
|
@ -61,7 +61,7 @@ function handleSelect(key: string | number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (key === 'userCenter')
|
if (key === 'userCenter')
|
||||||
router.push('/user-center')
|
router.push('/userCenter')
|
||||||
|
|
||||||
if (key === 'guthub')
|
if (key === 'guthub')
|
||||||
window.open('https://github.com/chansee97/nova-admin')
|
window.open('https://github.com/chansee97/nova-admin')
|
||||||
|
@ -2,7 +2,6 @@ import BackTop from './common/BackTop.vue'
|
|||||||
import Setting from './common/Setting.vue'
|
import Setting from './common/Setting.vue'
|
||||||
import SettingDrawer from './common/SettingDrawer.vue'
|
import SettingDrawer from './common/SettingDrawer.vue'
|
||||||
import Logo from './common/Logo.vue'
|
import Logo from './common/Logo.vue'
|
||||||
import MobileDrawer from './common/MobileDrawer.vue'
|
|
||||||
|
|
||||||
import Breadcrumb from './header/Breadcrumb.vue'
|
import Breadcrumb from './header/Breadcrumb.vue'
|
||||||
import CollapaseButton from './header/CollapaseButton.vue'
|
import CollapaseButton from './header/CollapaseButton.vue'
|
||||||
@ -19,7 +18,6 @@ export {
|
|||||||
CollapaseButton,
|
CollapaseButton,
|
||||||
FullScreen,
|
FullScreen,
|
||||||
Logo,
|
Logo,
|
||||||
MobileDrawer,
|
|
||||||
Notices,
|
Notices,
|
||||||
Search,
|
Search,
|
||||||
Setting,
|
Setting,
|
||||||
|
@ -2,49 +2,16 @@
|
|||||||
import { useAppStore } from '@/store'
|
import { useAppStore } from '@/store'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
let previousLayoutMode = appStore.layoutMode
|
|
||||||
|
|
||||||
function enterFullContent() {
|
|
||||||
previousLayoutMode = appStore.layoutMode
|
|
||||||
appStore.layoutMode = 'full-content'
|
|
||||||
}
|
|
||||||
|
|
||||||
function exitFullContent() {
|
|
||||||
// 如果是全屏或者数据不存在,则恢复为默认的vertical
|
|
||||||
if (previousLayoutMode === 'full-content' || !previousLayoutMode) {
|
|
||||||
previousLayoutMode = 'vertical'
|
|
||||||
}
|
|
||||||
appStore.layoutMode = previousLayoutMode
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-tooltip v-if="!appStore.isMobile" placement="bottom" trigger="hover">
|
<n-tooltip placement="bottom" trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<CommonWrapper @click="enterFullContent">
|
<CommonWrapper @click="appStore.contentFullScreen = !appStore.contentFullScreen">
|
||||||
<icon-park-outline-full-screen-one />
|
<icon-park-outline-off-screen-one v-if="appStore.contentFullScreen" />
|
||||||
|
<icon-park-outline-full-screen-one v-else />
|
||||||
</CommonWrapper>
|
</CommonWrapper>
|
||||||
</template>
|
</template>
|
||||||
{{ $t('app.togglContentFullScreen') }}
|
<span>{{ $t('app.togglContentFullScreen') }}</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
|
|
||||||
<Teleport to="body">
|
|
||||||
<div
|
|
||||||
v-if="appStore.layoutMode === 'full-content'"
|
|
||||||
class="fixed top-4 right-0 z-[9999]"
|
|
||||||
>
|
|
||||||
<n-tooltip placement="left" trigger="hover">
|
|
||||||
<template #trigger>
|
|
||||||
<n-el
|
|
||||||
class="bg-[var(--primary-color)] c-[var(--base-color)] rounded-l-lg shadow-lg p-2 cursor-pointer"
|
|
||||||
@click="exitFullContent"
|
|
||||||
>
|
|
||||||
<icon-park-outline-off-screen-one />
|
|
||||||
</n-el>
|
|
||||||
</template>
|
|
||||||
{{ $t('app.togglContentFullScreen') }}
|
|
||||||
</n-tooltip>
|
|
||||||
</div>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
CollapaseButton,
|
CollapaseButton,
|
||||||
FullScreen,
|
FullScreen,
|
||||||
Logo,
|
Logo,
|
||||||
MobileDrawer,
|
|
||||||
Notices,
|
Notices,
|
||||||
Search,
|
Search,
|
||||||
Setting,
|
Setting,
|
||||||
@ -15,12 +14,12 @@ import {
|
|||||||
UserCenter,
|
UserCenter,
|
||||||
} from './components'
|
} from './components'
|
||||||
import Content from './Content.vue'
|
import Content from './Content.vue'
|
||||||
|
|
||||||
import { ProLayout, useLayoutMenu } from 'pro-naive-ui'
|
import { ProLayout, useLayoutMenu } from 'pro-naive-ui'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const routeStore = useRouteStore()
|
const routeStore = useRouteStore()
|
||||||
|
|
||||||
const { layoutMode } = storeToRefs(useAppStore())
|
const { layoutMode } = storeToRefs(useAppStore())
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -32,19 +31,16 @@ const {
|
|||||||
menus: routeStore.menus,
|
menus: routeStore.menus,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => route.path, () => {
|
watch(() => route.path, (value) => {
|
||||||
activeKey.value = routeStore.activeMenu
|
activeKey.value = value
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 移动端抽屉控制
|
|
||||||
const showMobileDrawer = ref(false)
|
|
||||||
|
|
||||||
const sidebarWidth = ref(240)
|
const sidebarWidth = ref(240)
|
||||||
const sidebarCollapsedWidth = ref(64)
|
const sidebarCollapsedWidth = ref(64)
|
||||||
|
|
||||||
const hasHorizontalMenu = computed(() => ['horizontal', 'mixed-two-column', 'mixed-sidebar'].includes(layoutMode.value))
|
const hasHorizontalMenu = computed(() => ['horizontal', 'mixed-two-column', 'mixed-sidebar'].includes(layoutMode.value))
|
||||||
|
|
||||||
const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.value) || appStore.isMobile)
|
const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.value))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -52,8 +48,7 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
|
|||||||
<ProLayout
|
<ProLayout
|
||||||
v-model:collapsed="appStore.collapsed"
|
v-model:collapsed="appStore.collapsed"
|
||||||
:mode="layoutMode"
|
:mode="layoutMode"
|
||||||
:is-mobile="appStore.isMobile"
|
:show-logo="appStore.showLogo"
|
||||||
:show-logo="appStore.showLogo && !appStore.isMobile"
|
|
||||||
:show-footer="appStore.showFooter"
|
:show-footer="appStore.showFooter"
|
||||||
:show-tabbar="appStore.showTabs"
|
:show-tabbar="appStore.showTabs"
|
||||||
nav-fixed
|
nav-fixed
|
||||||
@ -70,16 +65,10 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #nav-left>
|
<template #nav-left>
|
||||||
<template v-if="appStore.isMobile">
|
<div v-if="!hasHorizontalMenu || !hidenCollapaseButton" class="h-full flex-y-center gap-1 p-x-sm">
|
||||||
<Logo />
|
<CollapaseButton v-if="!hidenCollapaseButton" />
|
||||||
</template>
|
<Breadcrumb v-if="!hasHorizontalMenu" />
|
||||||
|
</div>
|
||||||
<template v-else>
|
|
||||||
<div v-if="!hasHorizontalMenu || !hidenCollapaseButton" class="h-full flex-y-center gap-1 p-x-sm">
|
|
||||||
<CollapaseButton v-if="!hidenCollapaseButton" />
|
|
||||||
<Breadcrumb v-if="!hasHorizontalMenu" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #nav-center>
|
<template #nav-center>
|
||||||
@ -90,30 +79,13 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
|
|||||||
|
|
||||||
<template #nav-right>
|
<template #nav-right>
|
||||||
<div class="h-full flex-y-center gap-1 p-x-xl">
|
<div class="h-full flex-y-center gap-1 p-x-xl">
|
||||||
<!-- 移动端:只显示菜单按钮 -->
|
<Search />
|
||||||
<template v-if="appStore.isMobile">
|
<Notices />
|
||||||
<n-button
|
<FullScreen />
|
||||||
quaternary
|
<DarkModeSwitch />
|
||||||
@click="showMobileDrawer = true"
|
<LangsSwitch />
|
||||||
>
|
<Setting />
|
||||||
<template #icon>
|
<UserCenter />
|
||||||
<n-icon size="18">
|
|
||||||
<icon-park-outline-hamburger-button />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 桌面端:显示完整功能组件 -->
|
|
||||||
<template v-else>
|
|
||||||
<Search />
|
|
||||||
<Notices />
|
|
||||||
<FullScreen />
|
|
||||||
<DarkModeSwitch />
|
|
||||||
<LangsSwitch />
|
|
||||||
<Setting />
|
|
||||||
<UserCenter />
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -139,10 +111,5 @@ const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.v
|
|||||||
<Content />
|
<Content />
|
||||||
<BackTop />
|
<BackTop />
|
||||||
<SettingDrawer />
|
<SettingDrawer />
|
||||||
|
|
||||||
<!-- 移动端功能抽屉 -->
|
|
||||||
<MobileDrawer v-model:show="showMobileDrawer">
|
|
||||||
<n-menu v-bind="layout.verticalMenuProps" />
|
|
||||||
</MobileDrawer>
|
|
||||||
</ProLayout>
|
</ProLayout>
|
||||||
</template>
|
</template>
|
||||||
|
38
src/main.ts
38
src/main.ts
@ -1,5 +1,35 @@
|
|||||||
import App from './App.vue'
|
import type { App } from 'vue'
|
||||||
|
import { installRouter } from '@/router'
|
||||||
|
import { installPinia } from '@/store'
|
||||||
|
import AppVue from './App.vue'
|
||||||
|
import AppLoading from './components/common/AppLoading.vue'
|
||||||
|
|
||||||
// 创建应用实例并挂载
|
async function setupApp() {
|
||||||
const app = createApp(App)
|
// 载入全局loading加载状态
|
||||||
app.mount('#app')
|
const appLoading = createApp(AppLoading)
|
||||||
|
appLoading.mount('#appLoading')
|
||||||
|
|
||||||
|
// 创建vue实例
|
||||||
|
const app = createApp(AppVue)
|
||||||
|
|
||||||
|
// 注册模块Pinia
|
||||||
|
await installPinia(app)
|
||||||
|
|
||||||
|
// 注册模块 Vue-router
|
||||||
|
await installRouter(app)
|
||||||
|
|
||||||
|
/* 注册模块 指令/静态资源 */
|
||||||
|
Object.values(
|
||||||
|
import.meta.glob<{ install: (app: App) => void }>('./modules/*.ts', {
|
||||||
|
eager: true,
|
||||||
|
}),
|
||||||
|
).map(i => app.use(i))
|
||||||
|
|
||||||
|
// 卸载载入动画
|
||||||
|
appLoading.unmount()
|
||||||
|
|
||||||
|
// 挂载
|
||||||
|
app.mount('#app')
|
||||||
|
}
|
||||||
|
|
||||||
|
setupApp()
|
||||||
|
@ -22,19 +22,6 @@ export function setupRouterGuard(router: Router) {
|
|||||||
// 判断有无TOKEN,登录鉴权
|
// 判断有无TOKEN,登录鉴权
|
||||||
const isLogin = Boolean(local.get('accessToken'))
|
const isLogin = Boolean(local.get('accessToken'))
|
||||||
|
|
||||||
// 处理根路由重定向
|
|
||||||
if (to.name === 'root') {
|
|
||||||
if (isLogin) {
|
|
||||||
// 已登录,重定向到首页
|
|
||||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// 未登录,重定向到登录页
|
|
||||||
next({ path: '/login', replace: true })
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是login路由,直接放行
|
// 如果是login路由,直接放行
|
||||||
if (to.name === 'login') {
|
if (to.name === 'login') {
|
||||||
// login页面不需要任何认证检查,直接放行
|
// login页面不需要任何认证检查,直接放行
|
||||||
@ -47,31 +34,23 @@ export function setupRouterGuard(router: Router) {
|
|||||||
}
|
}
|
||||||
// 如果路由设置了requiresAuth为true,且用户未登录,重定向到登录页
|
// 如果路由设置了requiresAuth为true,且用户未登录,重定向到登录页
|
||||||
else if (to.meta.requiresAuth === true && !isLogin) {
|
else if (to.meta.requiresAuth === true && !isLogin) {
|
||||||
const redirect = to.name === 'not-found' ? undefined : to.fullPath
|
const redirect = to.name === '404' ? undefined : to.fullPath
|
||||||
next({ path: '/login', query: { redirect } })
|
next({ path: '/login', query: { redirect } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断路由有无进行初始化
|
// 判断路由有无进行初始化
|
||||||
if (!routeStore.isInitAuthRoute && to.name !== 'login') {
|
if (!routeStore.isInitAuthRoute) {
|
||||||
try {
|
await routeStore.initAuthRoute()
|
||||||
await routeStore.initAuthRoute()
|
// 动态路由加载完回到根路由
|
||||||
// 动态路由加载完回到根路由
|
if (to.name === '404') {
|
||||||
if (to.name === 'not-found') {
|
// 等待权限路由加载好了,回到之前的路由,否则404
|
||||||
// 等待权限路由加载好了,回到之前的路由,否则404
|
next({
|
||||||
next({
|
path: to.fullPath,
|
||||||
path: to.fullPath,
|
replace: true,
|
||||||
replace: true,
|
query: to.query,
|
||||||
query: to.query,
|
hash: to.hash,
|
||||||
hash: to.hash,
|
})
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// 如果路由初始化失败(比如 401 错误),重定向到登录页
|
|
||||||
const redirect = to.fullPath !== '/' ? to.fullPath : undefined
|
|
||||||
next({ path: '/login', query: redirect ? { redirect } : undefined })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'root',
|
name: 'root',
|
||||||
|
redirect: '/appRoot',
|
||||||
children: [
|
children: [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: () => import('@/views/build-in/login/index.vue'), // 注意这里要带上 文件后缀.vue
|
component: () => import('@/views/login/index.vue'), // 注意这里要带上 文件后缀.vue
|
||||||
meta: {
|
meta: {
|
||||||
title: '登录',
|
title: '登录',
|
||||||
withoutTab: true,
|
withoutTab: true,
|
||||||
@ -20,7 +21,7 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/public',
|
path: '/public',
|
||||||
name: 'publicAccess',
|
name: 'publicAccess',
|
||||||
component: () => import('@/views/build-in/public-access/index.vue'),
|
component: () => import('@/views/demo/publicAccess/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '公共访问示例',
|
title: '公共访问示例',
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
@ -28,19 +29,38 @@ export const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/not-found',
|
path: '/403',
|
||||||
name: 'not-found',
|
name: '403',
|
||||||
component: () => import('@/views/build-in/not-found/index.vue'),
|
component: () => import('@/views/error/403/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '用户无权限',
|
||||||
|
withoutTab: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/404',
|
||||||
|
name: '404',
|
||||||
|
component: () => import('@/views/error/404/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '找不到页面',
|
title: '找不到页面',
|
||||||
icon: 'icon-park-outline:ghost',
|
icon: 'icon-park-outline:ghost',
|
||||||
withoutTab: true,
|
withoutTab: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/500',
|
||||||
|
name: '500',
|
||||||
|
component: () => import('@/views/error/500/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '服务器错误',
|
||||||
|
icon: 'icon-park-outline:close-wifi',
|
||||||
|
withoutTab: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
component: () => import('@/views/build-in/not-found/index.vue'),
|
component: () => import('@/views/error/404/index.vue'),
|
||||||
name: 'not-found',
|
name: '404',
|
||||||
meta: {
|
meta: {
|
||||||
title: '找不到页面',
|
title: '找不到页面',
|
||||||
icon: 'icon-park-outline:ghost',
|
icon: 'icon-park-outline:ghost',
|
||||||
|
@ -19,7 +19,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
pinTab: true,
|
pinTab: true,
|
||||||
menuType: 'page',
|
menuType: 'page',
|
||||||
componentPath: '/dashboard/workbench/index.vue',
|
componentPath: '/dashboard/workbench/index.vue',
|
||||||
id: 101,
|
id: 2,
|
||||||
pid: 1,
|
pid: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -30,7 +30,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:anchor',
|
icon: 'icon-park-outline:anchor',
|
||||||
menuType: 'page',
|
menuType: 'page',
|
||||||
componentPath: '/dashboard/monitor/index.vue',
|
componentPath: '/dashboard/monitor/index.vue',
|
||||||
id: 102,
|
id: 3,
|
||||||
pid: 1,
|
pid: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -41,53 +41,53 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:list',
|
icon: 'icon-park-outline:list',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 2,
|
id: 4,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multi2',
|
name: 'multi2',
|
||||||
path: '/multi/multi-2',
|
path: '/multi/multi2',
|
||||||
title: '多级菜单子页',
|
title: '多级菜单子页',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:list',
|
icon: 'icon-park-outline:list',
|
||||||
menuType: 'page',
|
menuType: 'page',
|
||||||
componentPath: '/demo/multi/multi-2/index.vue',
|
componentPath: '/demo/multi/multi2/index.vue',
|
||||||
id: 201,
|
id: 6,
|
||||||
pid: 2,
|
pid: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multi2-detail',
|
name: 'multi2Detail',
|
||||||
path: '/multi/multi-2/detail',
|
path: '/multi/multi2/detail',
|
||||||
title: '菜单详情页',
|
title: '菜单详情页',
|
||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
icon: 'icon-park-outline:list',
|
icon: 'icon-park-outline:list',
|
||||||
hide: true,
|
hide: true,
|
||||||
activeMenu: '/multi/multi-2',
|
activeMenu: '/multi/multi2',
|
||||||
menuType: 'page',
|
menuType: 'page',
|
||||||
componentPath: '/demo/multi/multi-2/detail/index.vue',
|
componentPath: '/demo/multi/multi2/detail/index.vue',
|
||||||
id: 20101,
|
id: 7,
|
||||||
pid: 2,
|
pid: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multi3',
|
name: 'multi3',
|
||||||
path: '/multi/multi-3',
|
path: '/multi/multi3',
|
||||||
title: '多级菜单',
|
title: '多级菜单',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:list',
|
icon: 'icon-park-outline:list',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 202,
|
id: 8,
|
||||||
pid: 2,
|
pid: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'multi4',
|
name: 'multi4',
|
||||||
path: '/multi/multi-3/multi-4',
|
path: '/multi/multi3/multi4',
|
||||||
title: '多级菜单3-1',
|
title: '多级菜单3-1',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:list',
|
icon: 'icon-park-outline:list',
|
||||||
componentPath: '/demo/multi/multi-3/multi-4/index.vue',
|
componentPath: '/demo/multi/multi3/multi4/index.vue',
|
||||||
id: 20201,
|
id: 9,
|
||||||
pid: 202,
|
pid: 8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'list',
|
name: 'list',
|
||||||
@ -97,38 +97,28 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:list-two',
|
icon: 'icon-park-outline:list-two',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 3,
|
id: 10,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'commonList',
|
name: 'commonList',
|
||||||
path: '/list/common-list',
|
path: '/list/commonList',
|
||||||
title: '常用列表',
|
title: '常用列表',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:list-view',
|
icon: 'icon-park-outline:list-view',
|
||||||
componentPath: '/demo/list/common-list/index.vue',
|
componentPath: '/demo/list/commonList/index.vue',
|
||||||
id: 301,
|
id: 11,
|
||||||
pid: 3,
|
pid: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cardList',
|
name: 'cardList',
|
||||||
path: '/list/card-list',
|
path: '/list/cardList',
|
||||||
title: '卡片列表',
|
title: '卡片列表',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:view-grid-list',
|
icon: 'icon-park-outline:view-grid-list',
|
||||||
componentPath: '/demo/list/card-list/index.vue',
|
componentPath: '/demo/list/cardList/index.vue',
|
||||||
id: 302,
|
id: 12,
|
||||||
pid: 3,
|
pid: 10,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'draggableList',
|
|
||||||
path: '/list/draggable-list',
|
|
||||||
title: '拖拽列表',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'icon-park-outline:menu-fold',
|
|
||||||
componentPath: '/demo/list/draggable-list/index.vue',
|
|
||||||
id: 303,
|
|
||||||
pid: 3,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'demo',
|
name: 'demo',
|
||||||
@ -138,7 +128,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:application-one',
|
icon: 'icon-park-outline:application-one',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 4,
|
id: 13,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -148,8 +138,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:international',
|
icon: 'icon-park-outline:international',
|
||||||
componentPath: '/demo/fetch/index.vue',
|
componentPath: '/demo/fetch/index.vue',
|
||||||
id: 401,
|
id: 5,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'echarts',
|
name: 'echarts',
|
||||||
@ -158,8 +148,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:chart-proportion',
|
icon: 'icon-park-outline:chart-proportion',
|
||||||
componentPath: '/demo/echarts/index.vue',
|
componentPath: '/demo/echarts/index.vue',
|
||||||
id: 402,
|
id: 15,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'map',
|
name: 'map',
|
||||||
@ -169,8 +159,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'carbon:map',
|
icon: 'carbon:map',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
componentPath: '/demo/map/index.vue',
|
componentPath: '/demo/map/index.vue',
|
||||||
id: 403,
|
id: 17,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
@ -180,8 +170,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:editor',
|
icon: 'icon-park-outline:editor',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 404,
|
id: 18,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'editorMd',
|
name: 'editorMd',
|
||||||
@ -190,8 +180,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'ri:markdown-line',
|
icon: 'ri:markdown-line',
|
||||||
componentPath: '/demo/editor/md/index.vue',
|
componentPath: '/demo/editor/md/index.vue',
|
||||||
id: 40401,
|
id: 19,
|
||||||
pid: 404,
|
pid: 18,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'editorRich',
|
name: 'editorRich',
|
||||||
@ -200,8 +190,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:edit-one',
|
icon: 'icon-park-outline:edit-one',
|
||||||
componentPath: '/demo/editor/rich/index.vue',
|
componentPath: '/demo/editor/rich/index.vue',
|
||||||
id: 40402,
|
id: 20,
|
||||||
pid: 404,
|
pid: 18,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'clipboard',
|
name: 'clipboard',
|
||||||
@ -210,8 +200,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:clipboard',
|
icon: 'icon-park-outline:clipboard',
|
||||||
componentPath: '/demo/clipboard/index.vue',
|
componentPath: '/demo/clipboard/index.vue',
|
||||||
id: 405,
|
id: 21,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'icons',
|
name: 'icons',
|
||||||
@ -220,38 +210,18 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'local:cool',
|
icon: 'local:cool',
|
||||||
componentPath: '/demo/icons/index.vue',
|
componentPath: '/demo/icons/index.vue',
|
||||||
id: 406,
|
id: 22,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'QRCode',
|
name: 'QRCode',
|
||||||
path: '/demo/qr-code',
|
path: '/demo/QRCode',
|
||||||
title: '二维码',
|
title: '二维码',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:two-dimensional-code',
|
icon: 'icon-park-outline:two-dimensional-code',
|
||||||
componentPath: '/demo/qr-code/index.vue',
|
componentPath: '/demo/QRCode/index.vue',
|
||||||
id: 407,
|
id: 23,
|
||||||
pid: 4,
|
pid: 13,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cascader',
|
|
||||||
path: '/demo/cascader',
|
|
||||||
title: '省市区联动',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'icon-park-outline:add-subset',
|
|
||||||
componentPath: '/demo/cascader/index.vue',
|
|
||||||
id: 408,
|
|
||||||
pid: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dict',
|
|
||||||
path: '/demo/dict',
|
|
||||||
title: '字典示例',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'icon-park-outline:book-one',
|
|
||||||
componentPath: '/demo/dict/index.vue',
|
|
||||||
id: 409,
|
|
||||||
pid: 4,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documents',
|
name: 'documents',
|
||||||
@ -261,7 +231,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:file-doc',
|
icon: 'icon-park-outline:file-doc',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 5,
|
id: 24,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -271,8 +241,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'logos:vue',
|
icon: 'logos:vue',
|
||||||
componentPath: '/demo/documents/vue/index.vue',
|
componentPath: '/demo/documents/vue/index.vue',
|
||||||
id: 501,
|
id: 25,
|
||||||
pid: 5,
|
pid: 24,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documentsVite',
|
name: 'documentsVite',
|
||||||
@ -281,42 +251,19 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'logos:vitejs',
|
icon: 'logos:vitejs',
|
||||||
componentPath: '/demo/documents/vite/index.vue',
|
componentPath: '/demo/documents/vite/index.vue',
|
||||||
id: 502,
|
id: 26,
|
||||||
pid: 5,
|
pid: 24,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'documentsVueuse',
|
name: 'documentsVueuse',
|
||||||
path: '/documents/vue-use',
|
path: '/documents/vueuse',
|
||||||
title: 'VueUse(外链)',
|
title: 'VueUse(外链)',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'logos:vueuse',
|
icon: 'logos:vueuse',
|
||||||
href: 'https://vueuse.org/guide/',
|
href: 'https://vueuse.org/guide/',
|
||||||
componentPath: 'null',
|
componentPath: 'null',
|
||||||
id: 503,
|
id: 27,
|
||||||
pid: 5,
|
pid: 24,
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'documentsNova',
|
|
||||||
path: '/documents/nova',
|
|
||||||
title: 'Nova docs',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'local:logo',
|
|
||||||
href: 'https://nova-admin-docs.netlify.app/',
|
|
||||||
componentPath: '2333333',
|
|
||||||
id: 504,
|
|
||||||
pid: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'documentsPublic',
|
|
||||||
path: '/documents/public',
|
|
||||||
title: '公共示例页(外链)',
|
|
||||||
requiresAuth: true,
|
|
||||||
icon: 'local:logo',
|
|
||||||
href: '/public',
|
|
||||||
componentPath: 'null',
|
|
||||||
id: 505,
|
|
||||||
pid: 5,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'permission',
|
name: 'permission',
|
||||||
@ -326,7 +273,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:people-safe',
|
icon: 'icon-park-outline:people-safe',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 6,
|
id: 28,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -336,21 +283,65 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:right-user',
|
icon: 'icon-park-outline:right-user',
|
||||||
componentPath: '/demo/permission/permission/index.vue',
|
componentPath: '/demo/permission/permission/index.vue',
|
||||||
id: 601,
|
id: 29,
|
||||||
pid: 6,
|
pid: 28,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'justSuper',
|
name: 'justSuper',
|
||||||
path: '/permission/just-super',
|
path: '/permission/justSuper',
|
||||||
title: 'super可见',
|
title: 'super可见',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
roles: [
|
roles: [
|
||||||
'super',
|
'super',
|
||||||
],
|
],
|
||||||
icon: 'icon-park-outline:wrong-user',
|
icon: 'icon-park-outline:wrong-user',
|
||||||
componentPath: '/demo/permission/just-super/index.vue',
|
componentPath: '/demo/permission/justSuper/index.vue',
|
||||||
id: 602,
|
id: 30,
|
||||||
pid: 6,
|
pid: 28,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'error',
|
||||||
|
path: '/error',
|
||||||
|
title: '异常页',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:error-computer',
|
||||||
|
menuType: 'dir',
|
||||||
|
componentPath: null,
|
||||||
|
id: 31,
|
||||||
|
pid: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'demo403',
|
||||||
|
path: '/error/403',
|
||||||
|
title: '403',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'carbon:error',
|
||||||
|
order: 3,
|
||||||
|
componentPath: '/error/403/index.vue',
|
||||||
|
id: 32,
|
||||||
|
pid: 31,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'demo404',
|
||||||
|
path: '/error/404',
|
||||||
|
title: '404',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:error',
|
||||||
|
order: 2,
|
||||||
|
componentPath: '/error/404/index.vue',
|
||||||
|
id: 33,
|
||||||
|
pid: 31,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'demo500',
|
||||||
|
path: '/error/500',
|
||||||
|
title: '500',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'carbon:data-error',
|
||||||
|
order: 1,
|
||||||
|
componentPath: '/error/500/index.vue',
|
||||||
|
id: 34,
|
||||||
|
pid: 31,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'setting',
|
name: 'setting',
|
||||||
@ -360,7 +351,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
icon: 'icon-park-outline:setting',
|
icon: 'icon-park-outline:setting',
|
||||||
menuType: 'dir',
|
menuType: 'dir',
|
||||||
componentPath: null,
|
componentPath: null,
|
||||||
id: 7,
|
id: 35,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -370,8 +361,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:every-user',
|
icon: 'icon-park-outline:every-user',
|
||||||
componentPath: '/setting/account/index.vue',
|
componentPath: '/setting/account/index.vue',
|
||||||
id: 701,
|
id: 36,
|
||||||
pid: 7,
|
pid: 35,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dictionarySetting',
|
name: 'dictionarySetting',
|
||||||
@ -380,8 +371,8 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:book-one',
|
icon: 'icon-park-outline:book-one',
|
||||||
componentPath: '/setting/dictionary/index.vue',
|
componentPath: '/setting/dictionary/index.vue',
|
||||||
id: 702,
|
id: 37,
|
||||||
pid: 7,
|
pid: 35,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'menuSetting',
|
name: 'menuSetting',
|
||||||
@ -390,8 +381,18 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:application-menu',
|
icon: 'icon-park-outline:application-menu',
|
||||||
componentPath: '/setting/menu/index.vue',
|
componentPath: '/setting/menu/index.vue',
|
||||||
id: 703,
|
id: 38,
|
||||||
pid: 7,
|
pid: 35,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userCenter',
|
||||||
|
path: '/userCenter',
|
||||||
|
title: '个人中心',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'carbon:user-avatar-filled-alt',
|
||||||
|
componentPath: '/demo/userCenter/index.vue',
|
||||||
|
id: 39,
|
||||||
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'about',
|
name: 'about',
|
||||||
@ -400,19 +401,59 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'icon-park-outline:info',
|
icon: 'icon-park-outline:info',
|
||||||
componentPath: '/demo/about/index.vue',
|
componentPath: '/demo/about/index.vue',
|
||||||
id: 8,
|
id: 40,
|
||||||
pid: null,
|
pid: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'userCenter',
|
name: 'cascader',
|
||||||
path: '/user-center',
|
path: '/demo/cascader',
|
||||||
title: '个人中心',
|
title: '省市区联动',
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
hide: true,
|
icon: 'icon-park-outline:add-subset',
|
||||||
icon: 'carbon:user-avatar-filled-alt',
|
componentPath: '/demo/cascader/index.vue',
|
||||||
componentPath: '/build-in/user-center/index.vue',
|
id: 41,
|
||||||
id: 999,
|
pid: 13,
|
||||||
pid: null,
|
},
|
||||||
|
{
|
||||||
|
name: 'documentsNova',
|
||||||
|
path: '/documents/nova',
|
||||||
|
title: 'Nova docs',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'local:logo',
|
||||||
|
href: 'https://nova-admin-docs.netlify.app/',
|
||||||
|
componentPath: '2333333',
|
||||||
|
id: 42,
|
||||||
|
pid: 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dict',
|
||||||
|
path: '/demo/dict',
|
||||||
|
title: '字典示例',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:book-one',
|
||||||
|
componentPath: '/demo/dict/index.vue',
|
||||||
|
id: 43,
|
||||||
|
pid: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'draggableList',
|
||||||
|
path: '/list/draggableList',
|
||||||
|
title: '拖拽列表',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'icon-park-outline:menu-fold',
|
||||||
|
componentPath: '/demo/list/draggableList/index.vue',
|
||||||
|
id: 44,
|
||||||
|
pid: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'documentsPublic',
|
||||||
|
path: '/documents/public',
|
||||||
|
title: '公共示例页(外链)',
|
||||||
|
requiresAuth: true,
|
||||||
|
icon: 'local:logo',
|
||||||
|
href: '/public',
|
||||||
|
componentPath: 'null',
|
||||||
|
id: 45,
|
||||||
|
pid: 24,
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -19,11 +19,9 @@ const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthenticati
|
|||||||
// 服务端判定token过期
|
// 服务端判定token过期
|
||||||
refreshTokenOnSuccess: {
|
refreshTokenOnSuccess: {
|
||||||
// 当服务端返回401时,表示token过期
|
// 当服务端返回401时,表示token过期
|
||||||
isExpired: async (response, method) => {
|
isExpired: (response, method) => {
|
||||||
const res = await response.clone().json()
|
|
||||||
|
|
||||||
const isExpired = method.meta && method.meta.isExpired
|
const isExpired = method.meta && method.meta.isExpired
|
||||||
return (response.status === 401 || res.code === 401) && !isExpired
|
return response.status === 401 && !isExpired
|
||||||
},
|
},
|
||||||
|
|
||||||
// 当token过期时触发,在此函数中触发刷新token
|
// 当token过期时触发,在此函数中触发刷新token
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createAlovaInstance } from './alova'
|
import { createAlovaInstance } from './alova'
|
||||||
|
|
||||||
export const request = createAlovaInstance({
|
export const request = createAlovaInstance({
|
||||||
baseURL: __URL_MAP__.url.path,
|
baseURL: __PROXY_MAPPING__.url.path,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const blankInstance = createAlovaInstance({
|
export const blankInstance = createAlovaInstance({
|
||||||
|
@ -17,8 +17,6 @@ const { system, store } = useColorMode({
|
|||||||
emitAuto: true,
|
emitAuto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMobile = useMediaQuery('(max-width: 700px)')
|
|
||||||
|
|
||||||
export const useAppStore = defineStore('app-store', {
|
export const useAppStore = defineStore('app-store', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
@ -40,6 +38,7 @@ export const useAppStore = defineStore('app-store', {
|
|||||||
showSetting: false,
|
showSetting: false,
|
||||||
transitionAnimation: 'fade-slide' as TransitionAnimation,
|
transitionAnimation: 'fade-slide' as TransitionAnimation,
|
||||||
layoutMode: 'vertical' as ProLayoutMode,
|
layoutMode: 'vertical' as ProLayoutMode,
|
||||||
|
contentFullScreen: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@ -52,9 +51,6 @@ export const useAppStore = defineStore('app-store', {
|
|||||||
fullScreen() {
|
fullScreen() {
|
||||||
return isFullscreen.value
|
return isFullscreen.value
|
||||||
},
|
},
|
||||||
isMobile() {
|
|
||||||
return isMobile.value
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 重置所有设置
|
// 重置所有设置
|
||||||
@ -72,7 +68,8 @@ export const useAppStore = defineStore('app-store', {
|
|||||||
this.showBreadcrumbIcon = true
|
this.showBreadcrumbIcon = true
|
||||||
this.showWatermark = false
|
this.showWatermark = false
|
||||||
this.transitionAnimation = 'fade-slide'
|
this.transitionAnimation = 'fade-slide'
|
||||||
this.layoutMode = 'vertical'
|
this.layoutMode = 'leftMenu'
|
||||||
|
this.contentFullScreen = false
|
||||||
|
|
||||||
// 重置所有配色
|
// 重置所有配色
|
||||||
this.setPrimaryColor(this.primaryColor)
|
this.setPrimaryColor(this.primaryColor)
|
||||||
|
@ -39,22 +39,23 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
|
|
||||||
async initRouteInfo() {
|
async initRouteInfo() {
|
||||||
if (import.meta.env.VITE_ROUTE_LOAD_MODE === 'dynamic') {
|
if (import.meta.env.VITE_ROUTE_LOAD_MODE === 'dynamic') {
|
||||||
try {
|
const userInfo = local.get('userInfo')
|
||||||
// Get user's route
|
|
||||||
const result = await fetchUserRoutes({
|
|
||||||
id: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!result.isSuccess || !result.data) {
|
if (!userInfo || !userInfo.id) {
|
||||||
throw new Error('Failed to fetch user routes')
|
const authStore = useAuthStore()
|
||||||
}
|
authStore.logout()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return result.data
|
// Get user's route
|
||||||
}
|
const { data } = await fetchUserRoutes({
|
||||||
catch (error) {
|
id: userInfo.id,
|
||||||
console.error('Failed to initialize route info:', error)
|
})
|
||||||
throw error
|
|
||||||
}
|
if (!data)
|
||||||
|
return
|
||||||
|
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.rowRoutes = staticRoutes
|
this.rowRoutes = staticRoutes
|
||||||
@ -64,33 +65,25 @@ export const useRouteStore = defineStore('route-store', {
|
|||||||
async initAuthRoute() {
|
async initAuthRoute() {
|
||||||
this.isInitAuthRoute = false
|
this.isInitAuthRoute = false
|
||||||
|
|
||||||
try {
|
// Initialize route information
|
||||||
// Initialize route information
|
const rowRoutes = await this.initRouteInfo()
|
||||||
const rowRoutes = await this.initRouteInfo()
|
if (!rowRoutes) {
|
||||||
if (!rowRoutes) {
|
window.$message.error($t(`app.getRouteError`))
|
||||||
const error = new Error('Failed to get route information')
|
return
|
||||||
window.$message.error($t(`app.getRouteError`))
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
this.rowRoutes = rowRoutes
|
|
||||||
|
|
||||||
// Generate actual route and insert
|
|
||||||
const routes = createRoutes(rowRoutes)
|
|
||||||
router.addRoute(routes)
|
|
||||||
|
|
||||||
// Generate side menu
|
|
||||||
this.menus = createMenus(rowRoutes)
|
|
||||||
|
|
||||||
// Generate the route cache
|
|
||||||
this.cacheRoutes = generateCacheRoutes(rowRoutes)
|
|
||||||
|
|
||||||
this.isInitAuthRoute = true
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
// 重置状态并重新抛出错误
|
|
||||||
this.isInitAuthRoute = false
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
|
this.rowRoutes = rowRoutes
|
||||||
|
|
||||||
|
// Generate actual route and insert
|
||||||
|
const routes = createRoutes(rowRoutes)
|
||||||
|
router.addRoute(routes)
|
||||||
|
|
||||||
|
// Generate side menu
|
||||||
|
this.menus = createMenus(rowRoutes)
|
||||||
|
|
||||||
|
// Generate the route cache
|
||||||
|
this.cacheRoutes = generateCacheRoutes(rowRoutes)
|
||||||
|
|
||||||
|
this.isInitAuthRoute = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
3
src/typings/env.d.ts
vendored
3
src/typings/env.d.ts
vendored
@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
*后台服务的环境类型
|
*后台服务的环境类型
|
||||||
* - dev: 后台开发环境
|
* - dev: 后台开发环境
|
||||||
|
* - test: 后台测试环境
|
||||||
* - prod: 后台生产环境
|
* - prod: 后台生产环境
|
||||||
*/
|
*/
|
||||||
type ServiceEnvType = 'dev' | 'production'
|
type ServiceEnvType = 'dev' | 'test' | 'prod'
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
/** 项目基本地址 */
|
/** 项目基本地址 */
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const router = useRouter()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex-col-center h-full">
|
|
||||||
<img
|
|
||||||
src="@/assets/svg/error-not-found.svg"
|
|
||||||
alt=""
|
|
||||||
class="w-1/3"
|
|
||||||
>
|
|
||||||
<n-button
|
|
||||||
type="primary"
|
|
||||||
@click="router.push('/')"
|
|
||||||
>
|
|
||||||
{{ $t('app.backHome') }}
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,11 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAppStore } from '@/store'
|
|
||||||
import Chart from './components/chart.vue'
|
import Chart from './components/chart.vue'
|
||||||
import Chart2 from './components/chart2.vue'
|
import Chart2 from './components/chart2.vue'
|
||||||
import Chart3 from './components/chart3.vue'
|
import Chart3 from './components/chart3.vue'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const tableData = [
|
const tableData = [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -35,224 +32,218 @@ const tableData = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-grid
|
<div>
|
||||||
:x-gap="16"
|
<n-grid
|
||||||
:y-gap="16"
|
:x-gap="16"
|
||||||
:cols="12"
|
:y-gap="16"
|
||||||
item-responsive
|
>
|
||||||
responsive="screen"
|
<n-gi :span="6">
|
||||||
>
|
<n-card>
|
||||||
<!-- 统计卡片 - 移动端每行2个,桌面端每行4个 -->
|
<n-space
|
||||||
<n-gi span="6 m:3">
|
justify="space-between"
|
||||||
<n-card>
|
align="center"
|
||||||
<n-space
|
|
||||||
justify="space-between"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<n-statistic label="访问量">
|
|
||||||
<n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="12039"
|
|
||||||
show-separator
|
|
||||||
/>
|
|
||||||
</n-statistic>
|
|
||||||
<n-icon
|
|
||||||
color="#de4307"
|
|
||||||
size="42"
|
|
||||||
>
|
>
|
||||||
<icon-park-outline-chart-histogram />
|
<n-statistic label="访问量">
|
||||||
</n-icon>
|
<n-number-animation
|
||||||
</n-space>
|
:from="0"
|
||||||
<template #footer>
|
:to="12039"
|
||||||
<n-space justify="space-between">
|
show-separator
|
||||||
<span>累计访问数</span>
|
/>
|
||||||
<span><n-number-animation
|
</n-statistic>
|
||||||
:from="0"
|
<n-icon
|
||||||
:to="322039"
|
color="#de4307"
|
||||||
show-separator
|
size="42"
|
||||||
/></span>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
<n-gi span="6 m:3">
|
|
||||||
<n-card>
|
|
||||||
<n-space
|
|
||||||
justify="space-between"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<n-statistic label="下载量">
|
|
||||||
<n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="12039"
|
|
||||||
show-separator
|
|
||||||
/>
|
|
||||||
</n-statistic>
|
|
||||||
<n-icon
|
|
||||||
color="#ffb549"
|
|
||||||
size="42"
|
|
||||||
>
|
|
||||||
<icon-park-outline-chart-graph />
|
|
||||||
</n-icon>
|
|
||||||
</n-space>
|
|
||||||
<template #footer>
|
|
||||||
<n-space justify="space-between">
|
|
||||||
<span>累计下载量</span>
|
|
||||||
<span><n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="322039"
|
|
||||||
show-separator
|
|
||||||
/></span>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
<n-gi span="6 m:3">
|
|
||||||
<n-card>
|
|
||||||
<n-space
|
|
||||||
justify="space-between"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<n-statistic label="浏览量">
|
|
||||||
<n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="12039"
|
|
||||||
show-separator
|
|
||||||
/>
|
|
||||||
</n-statistic>
|
|
||||||
<n-icon
|
|
||||||
color="#1687a7"
|
|
||||||
size="42"
|
|
||||||
>
|
|
||||||
<icon-park-outline-average />
|
|
||||||
</n-icon>
|
|
||||||
</n-space>
|
|
||||||
<template #footer>
|
|
||||||
<n-space justify="space-between">
|
|
||||||
<span>累计浏览量</span>
|
|
||||||
<span><n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="322039"
|
|
||||||
show-separator
|
|
||||||
/></span>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
<n-gi span="6 m:3">
|
|
||||||
<n-card>
|
|
||||||
<n-space
|
|
||||||
justify="space-between"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<n-statistic label="注册量">
|
|
||||||
<n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="12039"
|
|
||||||
show-separator
|
|
||||||
/>
|
|
||||||
</n-statistic>
|
|
||||||
<n-icon
|
|
||||||
color="#42218E"
|
|
||||||
size="42"
|
|
||||||
>
|
|
||||||
<icon-park-outline-chart-pie />
|
|
||||||
</n-icon>
|
|
||||||
</n-space>
|
|
||||||
<template #footer>
|
|
||||||
<n-space justify="space-between">
|
|
||||||
<span>累计注册量</span>
|
|
||||||
<span><n-number-animation
|
|
||||||
:from="0"
|
|
||||||
:to="322039"
|
|
||||||
show-separator
|
|
||||||
/></span>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
<!-- 图表区域 - 全宽显示 -->
|
|
||||||
<n-gi :span="12">
|
|
||||||
<n-card content-style="padding: 0;">
|
|
||||||
<n-tabs
|
|
||||||
type="line"
|
|
||||||
size="large"
|
|
||||||
:tabs-padding="20"
|
|
||||||
pane-style="padding: 20px;"
|
|
||||||
>
|
|
||||||
<n-tab-pane name="流量趋势">
|
|
||||||
<Chart />
|
|
||||||
</n-tab-pane>
|
|
||||||
<n-tab-pane name="访问量趋势">
|
|
||||||
<Chart2 />
|
|
||||||
</n-tab-pane>
|
|
||||||
</n-tabs>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
|
|
||||||
<!-- 访问来源 - 移动端全宽,桌面端1/3宽 -->
|
|
||||||
<n-gi span="12 m:4">
|
|
||||||
<n-card
|
|
||||||
title="访问来源"
|
|
||||||
:segmented="{
|
|
||||||
content: true,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<Chart3 />
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
|
|
||||||
<!-- 成交记录 - 移动端全宽,桌面端2/3宽 -->
|
|
||||||
<n-gi span="12 m:8">
|
|
||||||
<n-card
|
|
||||||
title="成交记录"
|
|
||||||
:segmented="{
|
|
||||||
content: true,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #header-extra>
|
|
||||||
<n-button
|
|
||||||
type="primary"
|
|
||||||
quaternary
|
|
||||||
>
|
|
||||||
更多
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
<n-table
|
|
||||||
:bordered="false"
|
|
||||||
:single-line="false"
|
|
||||||
:scroll-x="appStore.isMobile ? 600 : undefined"
|
|
||||||
>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>交易名称</th>
|
|
||||||
<th>开始时间</th>
|
|
||||||
<th>结束时间</th>
|
|
||||||
<th>进度</th>
|
|
||||||
<th>状态</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="item in tableData"
|
|
||||||
:key="item.id"
|
|
||||||
>
|
>
|
||||||
<td>{{ item.name }}</td>
|
<icon-park-outline-chart-histogram />
|
||||||
<td>{{ item.start }}</td>
|
</n-icon>
|
||||||
<td>{{ item.end }}</td>
|
</n-space>
|
||||||
<td>{{ item.prograss }}%</td>
|
<template #footer>
|
||||||
<td>
|
<n-space justify="space-between">
|
||||||
<n-tag
|
<span>累计访问数</span>
|
||||||
:bordered="false"
|
<span><n-number-animation
|
||||||
type="info"
|
:from="0"
|
||||||
>
|
:to="322039"
|
||||||
{{ item.status }}
|
show-separator
|
||||||
</n-tag>
|
/></span>
|
||||||
</td>
|
</n-space>
|
||||||
</tr>
|
</template>
|
||||||
</tbody>
|
</n-card>
|
||||||
</n-table>
|
</n-gi>
|
||||||
</n-card>
|
<n-gi :span="6">
|
||||||
</n-gi>
|
<n-card>
|
||||||
</n-grid>
|
<n-space
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<n-statistic label="下载量">
|
||||||
|
<n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="12039"
|
||||||
|
show-separator
|
||||||
|
/>
|
||||||
|
</n-statistic>
|
||||||
|
<n-icon
|
||||||
|
color="#ffb549"
|
||||||
|
size="42"
|
||||||
|
>
|
||||||
|
<icon-park-outline-chart-graph />
|
||||||
|
</n-icon>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="space-between">
|
||||||
|
<span>累计下载量</span>
|
||||||
|
<span><n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="322039"
|
||||||
|
show-separator
|
||||||
|
/></span>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-card>
|
||||||
|
<n-space
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<n-statistic label="浏览量">
|
||||||
|
<n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="12039"
|
||||||
|
show-separator
|
||||||
|
/>
|
||||||
|
</n-statistic>
|
||||||
|
<n-icon
|
||||||
|
color="#1687a7"
|
||||||
|
size="42"
|
||||||
|
>
|
||||||
|
<icon-park-outline-average />
|
||||||
|
</n-icon>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="space-between">
|
||||||
|
<span>累计浏览量</span>
|
||||||
|
<span><n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="322039"
|
||||||
|
show-separator
|
||||||
|
/></span>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="6">
|
||||||
|
<n-card>
|
||||||
|
<n-space
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<n-statistic label="注册量">
|
||||||
|
<n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="12039"
|
||||||
|
show-separator
|
||||||
|
/>
|
||||||
|
</n-statistic>
|
||||||
|
<n-icon
|
||||||
|
color="#42218E"
|
||||||
|
size="42"
|
||||||
|
>
|
||||||
|
<icon-park-outline-chart-pie />
|
||||||
|
</n-icon>
|
||||||
|
</n-space>
|
||||||
|
<template #footer>
|
||||||
|
<n-space justify="space-between">
|
||||||
|
<span>累计注册量</span>
|
||||||
|
<span><n-number-animation
|
||||||
|
:from="0"
|
||||||
|
:to="322039"
|
||||||
|
show-separator
|
||||||
|
/></span>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="24">
|
||||||
|
<n-card content-style="padding: 0;">
|
||||||
|
<n-tabs
|
||||||
|
type="line"
|
||||||
|
size="large"
|
||||||
|
:tabs-padding="20"
|
||||||
|
pane-style="padding: 20px;"
|
||||||
|
>
|
||||||
|
<n-tab-pane name="流量趋势">
|
||||||
|
<Chart />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="访问量趋势">
|
||||||
|
<Chart2 />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="8">
|
||||||
|
<n-card
|
||||||
|
title="访问来源"
|
||||||
|
:segmented="{
|
||||||
|
content: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Chart3 />
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi :span="16">
|
||||||
|
<n-card
|
||||||
|
title="成交记录"
|
||||||
|
:segmented="{
|
||||||
|
content: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #header-extra>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
quaternary
|
||||||
|
>
|
||||||
|
更多
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
<n-table
|
||||||
|
:bordered="false"
|
||||||
|
:single-line="false"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>交易名称</th>
|
||||||
|
<th>开始时间</th>
|
||||||
|
<th>结束时间</th>
|
||||||
|
<th>进度</th>
|
||||||
|
<th>状态</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="item in tableData"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>{{ item.start }}</td>
|
||||||
|
<td>{{ item.end }}</td>
|
||||||
|
<td>{{ item.prograss }}%</td>
|
||||||
|
<td>
|
||||||
|
<n-tag
|
||||||
|
:bordered="false"
|
||||||
|
type="info"
|
||||||
|
>
|
||||||
|
{{ item.status }}
|
||||||
|
</n-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</n-table>
|
||||||
|
</n-card>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
@ -9,102 +9,94 @@ const { userInfo } = useAuthStore()
|
|||||||
<n-grid
|
<n-grid
|
||||||
:x-gap="16"
|
:x-gap="16"
|
||||||
:y-gap="16"
|
:y-gap="16"
|
||||||
:cols="3"
|
|
||||||
item-responsive
|
|
||||||
responsive="screen"
|
|
||||||
>
|
>
|
||||||
<!-- 左侧主要内容区 - 移动端全宽,桌面端2/3宽 -->
|
<n-gi :span="16">
|
||||||
<n-gi span="3 m:2">
|
|
||||||
<n-space
|
<n-space
|
||||||
vertical
|
vertical
|
||||||
:size="16"
|
:size="16"
|
||||||
>
|
>
|
||||||
<!-- 图表区域 -->
|
|
||||||
<n-card style="--n-padding-left: 0;">
|
<n-card style="--n-padding-left: 0;">
|
||||||
<Chart />
|
<Chart />
|
||||||
</n-card>
|
</n-card>
|
||||||
|
<n-card>
|
||||||
<!-- 统计卡片区域 -->
|
<n-grid
|
||||||
<n-grid
|
:x-gap="8"
|
||||||
:x-gap="16"
|
:y-gap="8"
|
||||||
:y-gap="16"
|
>
|
||||||
:cols="4"
|
<n-gi :span="6">
|
||||||
item-responsive
|
<n-card>
|
||||||
responsive="screen"
|
<n-thing>
|
||||||
>
|
<template #avatar>
|
||||||
<n-gi span="2 l:1">
|
<n-el>
|
||||||
<n-card>
|
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
||||||
<n-thing>
|
<nova-icon :size="26" icon="icon-park-outline:user" />
|
||||||
<template #avatar>
|
</n-icon-wrapper>
|
||||||
<n-el>
|
</n-el>
|
||||||
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
</template>
|
||||||
<nova-icon :size="26" icon="icon-park-outline:user" />
|
<template #header>
|
||||||
</n-icon-wrapper>
|
<n-statistic label="活跃用户">
|
||||||
</n-el>
|
<n-number-animation show-separator :from="0" :to="12039" />
|
||||||
</template>
|
</n-statistic>
|
||||||
<template #header>
|
</template>
|
||||||
<n-statistic label="活跃用户">
|
</n-thing>
|
||||||
<n-number-animation show-separator :from="0" :to="12039" />
|
</n-card>
|
||||||
</n-statistic>
|
</n-gi>
|
||||||
</template>
|
<n-gi :span="6">
|
||||||
</n-thing>
|
<n-card>
|
||||||
</n-card>
|
<n-thing>
|
||||||
</n-gi>
|
<template #avatar>
|
||||||
<n-gi span="2 l:1">
|
<n-el>
|
||||||
<n-card>
|
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
||||||
<n-thing>
|
<nova-icon :size="26" icon="icon-park-outline:every-user" />
|
||||||
<template #avatar>
|
</n-icon-wrapper>
|
||||||
<n-el>
|
</n-el>
|
||||||
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
</template>
|
||||||
<nova-icon :size="26" icon="icon-park-outline:every-user" />
|
<template #header>
|
||||||
</n-icon-wrapper>
|
<n-statistic label="用户">
|
||||||
</n-el>
|
<n-number-animation show-separator :from="0" :to="44039" />
|
||||||
</template>
|
</n-statistic>
|
||||||
<template #header>
|
</template>
|
||||||
<n-statistic label="用户">
|
</n-thing>
|
||||||
<n-number-animation show-separator :from="0" :to="44039" />
|
</n-card>
|
||||||
</n-statistic>
|
</n-gi>
|
||||||
</template>
|
<n-gi :span="6">
|
||||||
</n-thing>
|
<n-card>
|
||||||
</n-card>
|
<n-thing>
|
||||||
</n-gi>
|
<template #avatar>
|
||||||
<n-gi span="2 l:1">
|
<n-el>
|
||||||
<n-card>
|
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
||||||
<n-thing>
|
<nova-icon :size="26" icon="icon-park-outline:preview-open" />
|
||||||
<template #avatar>
|
</n-icon-wrapper>
|
||||||
<n-el>
|
</n-el>
|
||||||
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
</template>
|
||||||
<nova-icon :size="26" icon="icon-park-outline:preview-open" />
|
<template #header>
|
||||||
</n-icon-wrapper>
|
<n-statistic label="浏览量">
|
||||||
</n-el>
|
<n-number-animation show-separator :from="0" :to="551039" />
|
||||||
</template>
|
</n-statistic>
|
||||||
<template #header>
|
</template>
|
||||||
<n-statistic label="浏览量">
|
</n-thing>
|
||||||
<n-number-animation show-separator :from="0" :to="551039" />
|
</n-card>
|
||||||
</n-statistic>
|
</n-gi>
|
||||||
</template>
|
<n-gi :span="6">
|
||||||
</n-thing>
|
<n-card>
|
||||||
</n-card>
|
<n-thing>
|
||||||
</n-gi>
|
<template #avatar>
|
||||||
<n-gi span="2 l:1">
|
<n-el>
|
||||||
<n-card>
|
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
||||||
<n-thing>
|
<nova-icon :size="26" icon="icon-park-outline:star" />
|
||||||
<template #avatar>
|
</n-icon-wrapper>
|
||||||
<n-el>
|
</n-el>
|
||||||
<n-icon-wrapper :size="46" color="var(--success-color)" :border-radius="999">
|
</template>
|
||||||
<nova-icon :size="26" icon="icon-park-outline:star" />
|
<template #header>
|
||||||
</n-icon-wrapper>
|
<n-statistic label="收藏数">
|
||||||
</n-el>
|
<n-number-animation show-separator :from="0" :to="7739" />
|
||||||
</template>
|
</n-statistic>
|
||||||
<template #header>
|
</template>
|
||||||
<n-statistic label="收藏数">
|
</n-thing>
|
||||||
<n-number-animation show-separator :from="0" :to="7739" />
|
</n-card>
|
||||||
</n-statistic>
|
</n-gi>
|
||||||
</template>
|
</n-grid>
|
||||||
</n-thing>
|
</n-card>
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<n-card title="动态">
|
<n-card title="动态">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button
|
<n-button
|
||||||
@ -175,9 +167,7 @@ const { userInfo } = useAuthStore()
|
|||||||
</n-card>
|
</n-card>
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
|
<n-gi :span="8">
|
||||||
<!-- 右侧边栏 - 移动端全宽,桌面端1/3宽 -->
|
|
||||||
<n-gi span="3 m:1">
|
|
||||||
<n-space
|
<n-space
|
||||||
vertical
|
vertical
|
||||||
:size="16"
|
:size="16"
|
||||||
@ -236,14 +226,11 @@ const { userInfo } = useAuthStore()
|
|||||||
</n-list-item>
|
</n-list-item>
|
||||||
</n-list>
|
</n-list>
|
||||||
</n-card>
|
</n-card>
|
||||||
<!-- 订单和待办统计 -->
|
|
||||||
<n-grid
|
<n-grid
|
||||||
:x-gap="16"
|
:x-gap="8"
|
||||||
:y-gap="16"
|
:y-gap="8"
|
||||||
:cols="2"
|
|
||||||
>
|
>
|
||||||
<!-- 移动端和桌面端都是每行2个 -->
|
<n-gi :span="12">
|
||||||
<n-gi :span="1">
|
|
||||||
<n-card>
|
<n-card>
|
||||||
<n-flex vertical align="center">
|
<n-flex vertical align="center">
|
||||||
<n-text depth="3">
|
<n-text depth="3">
|
||||||
@ -258,7 +245,7 @@ const { userInfo } = useAuthStore()
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
<n-gi :span="1">
|
<n-gi :span="12">
|
||||||
<n-card>
|
<n-card>
|
||||||
<n-flex vertical align="center">
|
<n-flex vertical align="center">
|
||||||
<n-text depth="3">
|
<n-text depth="3">
|
||||||
|
@ -122,7 +122,7 @@ function handleResetSearch() {
|
|||||||
model.value = { ...initialModel }
|
model.value = { ...initialModel }
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModalType = 'add' | 'edit'
|
type ModalType = 'add' | 'edit'
|
||||||
const modalType = ref<ModalType>('add')
|
const modalType = ref<ModalType>('add')
|
||||||
function setModalType(type: ModalType) {
|
function setModalType(type: ModalType) {
|
||||||
modalType.value = type
|
modalType.value = type
|
7
src/views/error/403/index.vue
Normal file
7
src/views/error/403/index.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ErrorTip type="403" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
7
src/views/error/404/index.vue
Normal file
7
src/views/error/404/index.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ErrorTip type="404" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
7
src/views/error/500/index.vue
Normal file
7
src/views/error/500/index.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ErrorTip type="500" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Login, Register, ResetPwd } from './components'
|
import { Login, Register, ResetPwd } from './components'
|
||||||
|
|
||||||
type IformType = 'login' | 'register' | 'resetPwd'
|
type IformType = 'login' | 'register' | 'resetPwd'
|
||||||
const formType: Ref<IformType> = ref('login')
|
const formType: Ref<IformType> = ref('login')
|
||||||
const formComponets = {
|
const formComponets = {
|
||||||
login: Login,
|
login: Login,
|
||||||
@ -18,7 +18,7 @@ const appName = import.meta.env.VITE_APP_NAME
|
|||||||
<DarkModeSwitch />
|
<DarkModeSwitch />
|
||||||
<LangsSwitch />
|
<LangsSwitch />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<n-el
|
||||||
class="p-4xl h-full w-full sm:w-450px sm:h-unset"
|
class="p-4xl h-full w-full sm:w-450px sm:h-unset"
|
||||||
style="background: var(--card-color);box-shadow: var(--box-shadow-1);"
|
style="background: var(--card-color);box-shadow: var(--box-shadow-1);"
|
||||||
>
|
>
|
||||||
@ -36,7 +36,7 @@ const appName = import.meta.env.VITE_APP_NAME
|
|||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</n-el>
|
||||||
|
|
||||||
<div />
|
<div />
|
||||||
</n-el>
|
</n-el>
|
@ -1,10 +1,10 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import type { DataTableColumns } from 'naive-ui'
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
import CopyText from '@/components/custom/CopyText.vue'
|
||||||
import { useBoolean } from '@/hooks'
|
import { useBoolean } from '@/hooks'
|
||||||
import { fetchAllRoutes } from '@/service'
|
import { fetchAllRoutes } from '@/service'
|
||||||
import { arrayToTree, createIcon } from '@/utils'
|
import { arrayToTree, createIcon } from '@/utils'
|
||||||
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||||
import { renderProCopyableText } from 'pro-naive-ui'
|
|
||||||
import TableModal from './components/TableModal.vue'
|
import TableModal from './components/TableModal.vue'
|
||||||
|
|
||||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||||
@ -45,7 +45,11 @@ const columns: DataTableColumns<AppRoute.RowRoute> = [
|
|||||||
{
|
{
|
||||||
title: '路径',
|
title: '路径',
|
||||||
key: 'path',
|
key: 'path',
|
||||||
render: row => renderProCopyableText(row.path),
|
render: (row) => {
|
||||||
|
return (
|
||||||
|
<CopyText value={row.path} />
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '组件路径',
|
title: '组件路径',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user