1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2025-12-28 12:10:20 +08:00

22 Commits

Author SHA1 Message Date
张传龙
3f7ed95fdb style: 增加vscode插件推荐列表 2022-04-09 19:05:58 +08:00
张传龙
7478f193f9 Merge branch 'main' of https://github.com/zclzone/vue-naive-admin 2022-04-08 19:39:35 +08:00
张传龙
ba49d94bf4 docs: 完善插件使用注释 2022-04-08 19:35:22 +08:00
Ronnie Zhang
ea9851ccd3 Merge pull request #7 from liulinboyi/fix/dialog
fix: $dialog对话框可以异步
2022-04-08 18:34:23 +08:00
张传龙
ec55f33655 feat: 配合unplugin-icons集成iconify图标解决方案 2022-04-08 17:21:48 +08:00
liulinboyi
7a85c714cb fix: $dialog对话框可以异步 2022-04-08 16:32:33 +08:00
张传龙
7b90d7f8de style: 修改html代码风格 2022-04-08 11:25:11 +08:00
张传龙
16f580c96d mod: 修改外链中文档地址 2022-04-05 17:40:32 +08:00
张传龙
f1329a46e4 docs: update readme 2022-04-05 17:35:05 +08:00
张传龙
ef6df57dc5 style: 修改404图片 2022-04-05 11:33:35 +08:00
张传龙
95e5cd7134 style: 修改favicon 2022-04-05 11:33:18 +08:00
张传龙
9db7aa50a1 fix: 修复更新Naive UI版本后菜单高亮样式失效问题 2022-04-05 00:37:30 +08:00
张传龙
a9997984d5 refactor: 重构登录页UI 2022-04-05 00:36:24 +08:00
张传龙
acb47a17b4 refactor: 规范化调整.vue文件结构及命名 2022-04-03 19:45:39 +08:00
张传龙
9c5f4eaa3d chore: 依赖更新 2022-04-02 09:39:16 +08:00
张传龙
361fb52345 mod: 修改配置,打包默认不生成CNAME文件 2022-04-01 17:59:04 +08:00
张传龙
5993e8d7d0 refactor: 全局规范化调整文件夹和文件命名(代码无改动) 2022-04-01 17:41:07 +08:00
张传龙
8648f16ed8 mod: remove router.isReady 2022-04-01 16:39:26 +08:00
张传龙
33aaadba60 fix: 修复挂载路由时使用$loadingBar出错问题 2022-03-29 09:28:36 +08:00
张传龙
437d87f19e mod: 调整setupApp相关写法 2022-03-28 18:31:32 +08:00
张传龙
dfcc8c2158 mod: 修改失效图片链接 2022-03-26 17:50:29 +08:00
张传龙
51a583fc1e feat: 添加pageTitle路由守卫,支持动态修改页面title 2022-03-24 20:47:05 +08:00
47 changed files with 840 additions and 756 deletions

2
.env
View File

@@ -3,4 +3,4 @@ VITE_APP_TITLE = 'Vue Naive Admin'
VITE_PORT = 3100 VITE_PORT = 3100
# 打包时自动生成CNAME文件用于配置github pages自定义域名如不需要可注释或者直接删除 # 打包时自动生成CNAME文件用于配置github pages自定义域名如不需要可注释或者直接删除
VITE_APP_GLOB_CNAME = 'template.qszone.com' # VITE_APP_GLOB_CNAME = 'template.qszone.com'

View File

@@ -1,11 +1,13 @@
{ {
"recommendations": [ "recommendations": [
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"johnsoncodehk.volar", "johnsoncodehk.volar",
"hollowtree.vue-snippets", "hollowtree.vue-snippets",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"mikestead.dotenv", "mikestead.dotenv",
"wayou.vscode-todo-highlight", "wayou.vscode-todo-highlight",
"aaron-bond.better-comments" "aaron-bond.better-comments",
"antfu.iconify"
] ]
} }

View File

@@ -8,7 +8,7 @@
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[html]": { "[html]": {
"editor.defaultFormatter": "vscode.html-language-features" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -19,8 +19,12 @@
"[vue]": { "[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
},
"eslint.validate": ["javascript", "javascriptreact", "typescript"],
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"eslint.validate": ["javascript", "javascriptreact", "typescript"] "editor.formatOnSave": true
} }

View File

@@ -1,25 +1,25 @@
# VUE NAIVE ADMIN ## VUE NAIVE ADMIN
## 简介 ### 简介
Vue Naive Admin一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理模板,没有集成 TypeScript没有集成国际化没有集成复杂的主题配置上手成本非常低对新手极其友好。不过麻雀虽小五脏俱全权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置以及一些经常用的基础组件封装等等这些该有的都有参考多个 vue3 后台管理模板后以最简洁优雅的方式实现,非常适用于中小型项目或者个人项目。 [Vue Naive Admin](https://github.com/zclzone/vue-naive-admin),一个基于 Vue3.0、Vite、Naive UI 的后台管理模板,相较于其他比较流行的后台管理模板,此项目相对简洁、轻量,学习成本非常低对新手极其友好。不过麻雀虽小五脏俱全权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置以及一些经常用的基础组件封装等等这些该有的都有非常适用于中小型项目或者个人项目,也可此模板进行二次封装改造用于大型项目。
## 为什么要开发这个模板 ### 为什么要开发这个模板
1. Vue3 和 Vite 已经趋于成熟,学习 vite 和 vue3 非常有必要,通过开发模板进行学习是一个很好的方式,事实也证明我确实从中获益良多 - Vue3 和 Vite 已经趋于成熟,学习 vite 和 vue3 非常有必要,通过开发模板进行学习是一个很好的方式,事实也证明我确实从中获益良多
2. 目前主流的 Vue3+Vite 后台管理模板都相对复杂,甚至感觉有点花里胡哨(没有贬低的意思,大部分的架构设计都很优秀,只是觉得集成了太多不实用的东西) - 目前主流的 Vue3+Vite 后台管理模板都相对复杂,甚至感觉有点花里胡哨(没有贬低的意思,大部分的架构设计都很优秀,只是觉得集成了太多不实用的东西)
3. 自己搭的模板开发起来才最顺手。本人很反感拿别人的模板直接上手开发,如果非要拿别人的模板开发也会尽量先吃透再用,不吃透就没有代码的掌控感和安全感
## 功能 ### 功能
- 🍒 集成 Naive UI尤大推荐的 UI 组件库,很香,https://www.naiveui.com - 🍒 集成 Naive UI尤大推荐的 UI 组件库,[https://www.naiveui.com](https://www.naiveui.com)
- 🍑 集成登陆、注销及权限验证(暂只支持角色页面权限,后续考虑添加按钮权限) - 🍑 集成登陆、注销及权限验证
- 🍐 集成多环境配置dev、测试、预发布和生产 - 🍐 集成多环境配置dev、测试、预发布和生产
- 🍎 集成 eslint + prettier代码约束和格式化统一 - 🍎 集成 Eslint + Prettier代码约束和格式化统一
- 🍉 集成 Mock 接口服务dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积 - 🍉 集成 Mock 接口服务dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积
- 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的,很香 - 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的
- 🍍 集成 piniaVuex 的替代方案,轻量、简单、易用,很香 - 🍍 集成 PiniaVuex 的替代方案,轻量、简单、易用尤大已表示不会有Vuex5或者说pinia就是Vuex5
- 🍏 集成 axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL - 📦 集成 Vite 自动导入插件unplugin-vue-components解放双手开发效率直接起飞
- 🍏 二次封装 Axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL
- 🍌 二次封装全局 Dialog、Message、LoadingBar 组件 - 🍌 二次封装全局 Dialog、Message、LoadingBar 组件
- 🍋 二次封装 localStorage 和 sessionStorage支持设置过期时间 - 🍋 二次封装 localStorage 和 sessionStorage支持设置过期时间
@@ -29,6 +29,7 @@ Vue Naive Admin一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理
## 文档 ## 文档
[Vue Naive Admin Docs](https://zclzone.github.io/vue-naive-admin-docs)
[羽雀文档Vue Naive Admin](https://www.yuque.com/qszone/vue-naive-admin) [羽雀文档Vue Naive Admin](https://www.yuque.com/qszone/vue-naive-admin)
## 构建步骤 ## 构建步骤

View File

@@ -1,9 +1,24 @@
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
/**
* * 扩展setup插件支持在script标签中使用name属性
* usage: <script setup name="MyComp"></script>
*/
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
/**
* * 组件库按需引入插件
* usage: 直接使用组件,无需在任何地方导入组件
*/
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import VueSetupExtend from 'vite-plugin-vue-setup-extend' /**
* * unplugin-icons插件自动引入iconify图标
* usage: https://github.com/antfu/unplugin-icons
* 图标库: https://icones.js.org/
*/
import Icons from 'unplugin-icons/vite'
import { unocss } from './unocss' import { unocss } from './unocss'
import { configHtmlPlugin } from './html' import { configHtmlPlugin } from './html'
@@ -12,10 +27,11 @@ import { configMockPlugin } from './mock'
export function createVitePlugins(viteEnv, isBuild) { export function createVitePlugins(viteEnv, isBuild) {
const plugins = [ const plugins = [
vue(), vue(),
VueSetupExtend(),
Components({ Components({
resolvers: [NaiveUiResolver()], resolvers: [NaiveUiResolver()],
}), }),
VueSetupExtend(), Icons({ compiler: 'vue3', autoInstall: true }),
unocss(), unocss(),
configHtmlPlugin(viteEnv, isBuild), configHtmlPlugin(viteEnv, isBuild),
] ]

View File

@@ -7,7 +7,7 @@ export function configMockPlugin(isBuild) {
localEnabled: !isBuild, localEnabled: !isBuild,
prodEnabled: isBuild, prodEnabled: isBuild,
injectCode: ` injectCode: `
import { setupProdMockServer } from '../mock/_createProdServer'; import { setupProdMockServer } from '../mock/_create-prod-server';
setupProdMockServer(); setupProdMockServer();
`, `,
}) })

View File

@@ -1,22 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="cn"> <html lang="cn">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Expires" content="0" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-control" content="no-cache" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.svg" />
<title><%= title %></title>
</head>
<head> <body>
<meta charset="UTF-8" /> <div id="app"></div>
<meta http-equiv="Expires" content="0" /> <script type="module" src="/src/main.js"></script>
<meta http-equiv="Pragma" content="no-cache" /> </body>
<meta http-equiv="Cache-control" content="no-cache" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>
<%= title %>
</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html> </html>

View File

@@ -4,21 +4,21 @@ const users = {
admin: { admin: {
id: 1, id: 1,
name: '大脸怪(admin)', name: '大脸怪(admin)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com', email: 'Ronnie@123.com',
role: ['admin'], role: ['admin'],
}, },
editor: { editor: {
id: 2, id: 2,
name: '大脸怪(editor)', name: '大脸怪(editor)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com', email: 'Ronnie@123.com',
role: ['editor'], role: ['editor'],
}, },
guest: { guest: {
id: 3, id: 3,
name: '访客(guest)', name: '访客(guest)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
role: [], role: [],
}, },
} }

View File

@@ -11,37 +11,39 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@vicons/fa": "^0.11.0",
"axios": "^0.21.4", "axios": "^0.21.4",
"dayjs": "^1.10.7", "dayjs": "^1.11.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"md-editor-v3": "^1.10.2", "md-editor-v3": "^1.11.4",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"pinia": "^2.0.11", "pinia": "^2.0.13",
"vue": "^3.2.30", "vue": "^3.2.31",
"vue-router": "^4.0.12" "vue-router": "^4.0.14"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/mdi": "^1.1.9",
"@iconify-json/simple-icons": "^1.1.7",
"@unocss/preset-attributify": "^0.16.4", "@unocss/preset-attributify": "^0.16.4",
"@unocss/preset-icons": "^0.16.4", "@unocss/preset-icons": "^0.16.4",
"@unocss/preset-uno": "^0.16.4", "@unocss/preset-uno": "^0.16.4",
"@vitejs/plugin-vue": "^1.10.2", "@vitejs/plugin-vue": "^1.10.2",
"@vue/compiler-sfc": "^3.2.30", "@vue/compiler-sfc": "^3.2.31",
"chalk": "^5.0.0", "chalk": "^5.0.1",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"eslint": "^8.6.0", "eslint": "^8.12.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0", "eslint-plugin-vue": "^8.5.0",
"esno": "^0.13.0", "esno": "^0.13.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.1",
"naive-ui": "^2.25.2", "naive-ui": "^2.27.0",
"prettier": "^2.5.1", "prettier": "^2.6.1",
"sass": "^1.38.1", "sass": "^1.49.10",
"unocss": "^0.16.4", "unocss": "^0.16.4",
"unplugin-vue-components": "^0.17.18", "unplugin-icons": "^0.14.1",
"vite": "^2.8.0", "unplugin-vue-components": "^0.17.21",
"vite-plugin-html": "^2.1.1", "vite": "^2.9.1",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-vue-setup-extend": "^0.3.0" "vite-plugin-vue-setup-extend": "^0.3.0"
} }

916
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

1
public/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512" data-v-fba6e5d0=""><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-92.2 312.9c-63.4 0-85.4-28.6-97.1-64.1c-16.3-51-21.5-84.3-63-84.3c-22.4 0-45.1 16.1-45.1 61.2c0 35.2 18 57.2 43.3 57.2c28.6 0 47.6-21.3 47.6-21.3l11.7 31.9s-19.8 19.4-61.2 19.4c-51.3 0-79.9-30.1-79.9-85.8c0-57.9 28.6-92 82.5-92c73.5 0 80.8 41.4 100.8 101.9c8.8 26.8 24.2 46.2 61.2 46.2c24.9 0 38.1-5.5 38.1-19.1c0-19.9-21.8-22-49.9-28.6c-30.4-7.3-42.5-23.1-42.5-48c0-40 32.3-52.4 65.2-52.4c37.4 0 60.1 13.6 63 46.6l-36.7 4.4c-1.5-15.8-11-22.4-28.6-22.4c-16.1 0-26 7.3-26 19.8c0 11 4.8 17.6 20.9 21.3c32.7 7.1 71.8 12 71.8 57.5c.1 36.7-30.7 50.6-76.1 50.6z" fill="#316c72"></path></svg>

After

Width:  |  Height:  |  Size: 825 B

View File

@@ -1,15 +1,15 @@
<script setup>
import AppProvider from '@/components/AppProvider/index.vue'
</script>
<template> <template>
<app-provider> <AppProvider>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<component :is="Component" /> <component :is="Component" />
</router-view> </router-view>
</app-provider> </AppProvider>
</template> </template>
<script setup>
import AppProvider from '@/components/AppProvider/index.vue'
</script>
<style lang="scss"> <style lang="scss">
#app { #app {
height: 100%; height: 100%;

BIN
src/assets/images/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512" data-v-fba6e5d0=""><path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-92.2 312.9c-63.4 0-85.4-28.6-97.1-64.1c-16.3-51-21.5-84.3-63-84.3c-22.4 0-45.1 16.1-45.1 61.2c0 35.2 18 57.2 43.3 57.2c28.6 0 47.6-21.3 47.6-21.3l11.7 31.9s-19.8 19.4-61.2 19.4c-51.3 0-79.9-30.1-79.9-85.8c0-57.9 28.6-92 82.5-92c73.5 0 80.8 41.4 100.8 101.9c8.8 26.8 24.2 46.2 61.2 46.2c24.9 0 38.1-5.5 38.1-19.1c0-19.9-21.8-22-49.9-28.6c-30.4-7.3-42.5-23.1-42.5-48c0-40 32.3-52.4 65.2-52.4c37.4 0 60.1 13.6 63 46.6l-36.7 4.4c-1.5-15.8-11-22.4-28.6-22.4c-16.1 0-26 7.3-26 19.8c0 11 4.8 17.6 20.9 21.3c32.7 7.1 71.8 12 71.8 57.5c.1 36.7-30.7 50.6-76.1 50.6z" fill="#316c72"></path></svg>

After

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -0,0 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448 512">
<path
d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-92.2 312.9c-63.4 0-85.4-28.6-97.1-64.1c-16.3-51-21.5-84.3-63-84.3c-22.4 0-45.1 16.1-45.1 61.2c0 35.2 18 57.2 43.3 57.2c28.6 0 47.6-21.3 47.6-21.3l11.7 31.9s-19.8 19.4-61.2 19.4c-51.3 0-79.9-30.1-79.9-85.8c0-57.9 28.6-92 82.5-92c73.5 0 80.8 41.4 100.8 101.9c8.8 26.8 24.2 46.2 61.2 46.2c24.9 0 38.1-5.5 38.1-19.1c0-19.9-21.8-22-49.9-28.6c-30.4-7.3-42.5-23.1-42.5-48c0-40 32.3-52.4 65.2-52.4c37.4 0 60.1 13.6 63 46.6l-36.7 4.4c-1.5-15.8-11-22.4-28.6-22.4c-16.1 0-26 7.3-26 19.8c0 11 4.8 17.6 20.9 21.3c32.7 7.1 71.8 12 71.8 57.5c.1 36.7-30.7 50.6-76.1 50.6z"
></path>
</svg>
</template>
<script setup name="IconLogo"></script>

View File

@@ -0,0 +1,12 @@
export { default as IconGitee } from '~icons/simple-icons/gitee'
export { default as IconChart } from '~icons/mdi/chart-bar'
export { default as IconGithub } from '~icons/mdi/github'
export { default as IconVue } from '~icons/mdi/vuejs'
export { default as IconHome } from '~icons/mdi/home'
export { default as IconLink } from '~icons/mdi/link-variant'
export { default as IconAlert } from '~icons/mdi/alert-circle-outline'
export { default as IconCircle } from '~icons/mdi/circle-outline'
export { default as IconMenu } from '~icons/mdi/menu'
export { default as IconLogo } from './IconLogo.vue'

View File

@@ -1,3 +1,5 @@
<template></template>
<script setup> <script setup>
import { isNullOrUndef } from '@/utils/is' import { isNullOrUndef } from '@/utils/is'
import { useDialog } from 'naive-ui' import { useDialog } from 'naive-ui'
@@ -6,15 +8,15 @@ const NDialog = useDialog()
class Dialog { class Dialog {
success(title, option) { success(title, option) {
this.showDialog('success', { title, ...option }) return this.showDialog('success', { title, ...option })
} }
warning(title, option) { warning(title, option) {
this.showDialog('warning', { title, ...option }) return this.showDialog('warning', { title, ...option })
} }
error(title, option) { error(title, option) {
this.showDialog('error', { title, ...option }) return this.showDialog('error', { title, ...option })
} }
showDialog(type = 'success', option) { showDialog(type = 'success', option) {
@@ -22,7 +24,7 @@ class Dialog {
// ! 没有title的情况 // ! 没有title的情况
option.showIcon = false option.showIcon = false
} }
NDialog[type]({ return NDialog[type]({
positiveText: 'OK', positiveText: 'OK',
closable: false, closable: false,
...option, ...option,
@@ -30,7 +32,7 @@ class Dialog {
} }
confirm(option = {}) { confirm(option = {}) {
this.showDialog(option.type || 'error', { return this.showDialog(option.type || 'error', {
positiveText: '确定', positiveText: '确定',
negativeText: '取消', negativeText: '取消',
onPositiveClick: option.confirm, onPositiveClick: option.confirm,
@@ -48,5 +50,3 @@ Object.defineProperty(window, '$dialog', {
writable: false, writable: false,
}) })
</script> </script>
<template></template>

View File

@@ -1,3 +1,5 @@
<template></template>
<script setup> <script setup>
import { useLoadingBar } from 'naive-ui' import { useLoadingBar } from 'naive-ui'
window['$loadingBar'] = useLoadingBar() window['$loadingBar'] = useLoadingBar()
@@ -6,5 +8,3 @@ Object.defineProperty(window, '$loadingBar', {
writable: false, writable: false,
}) })
</script> </script>
<template></template>

View File

@@ -1,3 +1,5 @@
<template></template>
<script setup> <script setup>
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
@@ -68,5 +70,3 @@ Object.defineProperty(window, '$message', {
writable: false, writable: false,
}) })
</script> </script>
<template></template>

View File

@@ -1,3 +1,18 @@
<template>
<n-config-provider :theme-overrides="appStore.themeOverrides">
<n-loading-bar-provider>
<LoadingBar />
<n-dialog-provider>
<DialogContent />
<n-message-provider>
<MessageContent />
<slot></slot>
</n-message-provider>
</n-dialog-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup> <script setup>
import MessageContent from './MessageContent.vue' import MessageContent from './MessageContent.vue'
import DialogContent from './DialogContent.vue' import DialogContent from './DialogContent.vue'
@@ -6,18 +21,3 @@ import LoadingBar from './LoadingBar.vue'
import { useAppStore } from '@/store/modules/app' import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore() const appStore = useAppStore()
</script> </script>
<template>
<n-config-provider :theme-overrides="appStore.themeOverrides">
<n-loading-bar-provider>
<loading-bar />
<n-dialog-provider>
<dialog-content />
<n-message-provider>
<message-content />
<slot></slot>
</n-message-provider>
</n-dialog-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>

View File

@@ -1,3 +1,11 @@
<template>
<n-breadcrumb>
<n-breadcrumb-item v-for="item in currentRoute.matched" :key="item.path" @click="handleBreadClick(item.path)">
{{ item.meta.title }}
</n-breadcrumb-item>
</n-breadcrumb>
</template>
<script setup> <script setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
@@ -8,11 +16,3 @@ function handleBreadClick(path) {
router.push(path) router.push(path)
} }
</script> </script>
<template>
<n-breadcrumb>
<n-breadcrumb-item v-for="item in currentRoute.matched" :key="item.path" @click="handleBreadClick(item.path)">
{{ item.meta.title }}
</n-breadcrumb-item>
</n-breadcrumb>
</template>

View File

@@ -1,3 +1,12 @@
<template>
<n-dropdown :options="options" @select="handleSelect">
<div class="avatar">
<img :src="userStore.avatar" />
<span>{{ userStore.name }}</span>
</div>
</n-dropdown>
</template>
<script setup> <script setup>
import { useUserStore } from '@/store/modules/user' import { useUserStore } from '@/store/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -40,21 +49,21 @@ function switchRole() {
{ {
id: 1, id: 1,
name: '大脸怪(admin)', name: '大脸怪(admin)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com', email: 'Ronnie@123.com',
role: ['admin'], role: ['admin'],
}, },
{ {
id: 2, id: 2,
name: '大脸怪(editor)', name: '大脸怪(editor)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com', email: 'Ronnie@123.com',
role: ['editor'], role: ['editor'],
}, },
{ {
id: 3, id: 3,
name: '访客(guest)', name: '访客(guest)',
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.jpg', avatar: 'https://assets.qszone.com/images/avatar.jpg',
role: [], role: [],
}, },
] ]
@@ -71,15 +80,6 @@ function switchRole() {
} }
</script> </script>
<template>
<n-dropdown :options="options" @select="handleSelect">
<div class="avatar">
<img :src="userStore.avatar" />
<span>{{ userStore.name }}</span>
</div>
</n-dropdown>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
.avatar { .avatar {
display: flex; display: flex;

View File

@@ -1,15 +1,15 @@
<template>
<header class="header">
<BreadCrumb />
<HeaderAction />
</header>
</template>
<script setup> <script setup>
import BreadCrumb from './BreadCrumb.vue' import BreadCrumb from './BreadCrumb.vue'
import HeaderAction from './HeaderAction.vue' import HeaderAction from './HeaderAction.vue'
</script> </script>
<template>
<header class="header">
<bread-crumb />
<header-action />
</header>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
.header { .header {
padding: 0 24px; padding: 0 24px;

View File

@@ -1,12 +1,7 @@
<script setup>
import { LastfmSquare } from '@vicons/fa'
const title = import.meta.env.VITE_APP_TITLE
</script>
<template> <template>
<div class="logo"> <div class="logo">
<n-icon size="36" color="#316c72"> <n-icon size="36" color="#316c72">
<lastfm-square /> <IconLogo />
</n-icon> </n-icon>
<router-link to="/"> <router-link to="/">
<n-gradient-text type="primary">{{ title }}</n-gradient-text> <n-gradient-text type="primary">{{ title }}</n-gradient-text>
@@ -14,6 +9,11 @@ const title = import.meta.env.VITE_APP_TITLE
</div> </div>
</template> </template>
<script setup>
import { IconLogo } from '@/components/AppIcons'
const title = import.meta.env.VITE_APP_TITLE
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.logo { .logo {
height: 64px; height: 64px;

View File

@@ -1,10 +1,22 @@
<template>
<n-menu
class="side-menu"
accordion
:indent="12"
:root-indent="12"
:options="menuOptions"
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
@update:value="handleMenuSelect"
/>
</template>
<script setup> <script setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { computed, h } from 'vue' import { computed, h } from 'vue'
import { usePermissionStore } from '@/store/modules/permission' import { usePermissionStore } from '@/store/modules/permission'
import { NIcon } from 'naive-ui' import { NIcon } from 'naive-ui'
import { ListAlt, CircleRegular } from '@vicons/fa' import { IconCircle, IconMenu } from '@/components/AppIcons'
import { isExternal } from '@/utils/is' import { isExternal } from '@/utils/is'
@@ -56,10 +68,10 @@ function generateOptions(routes, basePath) {
path: resolvePath(basePath, route.path), path: resolvePath(basePath, route.path),
} }
if (route.children && route.children.length) { if (route.children && route.children.length) {
curOption.icon = renderIcon(route.meta?.icon || ListAlt, { size: 16 }) curOption.icon = renderIcon(route.meta?.icon || IconMenu, { size: 16 })
curOption.children = generateOptions(route.children, resolvePath(basePath, route.path)) curOption.children = generateOptions(route.children, resolvePath(basePath, route.path))
} else { } else {
curOption.icon = (route.meta?.icon && renderIcon(route.meta?.icon)) || renderIcon(CircleRegular, { size: 8 }) curOption.icon = (route.meta?.icon && renderIcon(route.meta?.icon)) || renderIcon(IconCircle, { size: 8 })
} }
options.push(curOption) options.push(curOption)
} }
@@ -82,34 +94,19 @@ function handleMenuSelect(key, item) {
} }
</script> </script>
<template>
<n-menu
class="side-menu"
accordion
:indent="12"
:root-indent="12"
:options="menuOptions"
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
@update:value="handleMenuSelect"
/>
</template>
<style lang="scss"> <style lang="scss">
.n-menu { .n-menu {
margin-top: 10px; margin-top: 10px;
padding-left: 10px; padding-left: 10px;
.n-menu-item { .n-menu-item-content {
margin-top: 0;
position: relative;
&::before { &::before {
left: 0; left: 0;
right: 0; right: 0;
border-radius: 0; border-radius: 0;
background-color: unset !important; background-color: unset !important;
} }
&:hover, &:hover,
&.n-menu-item--selected { &.n-menu-item-content--selected {
border-radius: 0 !important; border-radius: 0 !important;
&::before { &::before {

View File

@@ -4,6 +4,6 @@ import SideMenu from './SideMenu.vue'
</script> </script>
<template> <template>
<side-logo /> <SideLogo />
<side-menu /> <SideMenu />
</template> </template>

View File

@@ -8,14 +8,14 @@ import AppMain from './components/AppMain.vue'
<div class="layout"> <div class="layout">
<n-layout has-sider position="absolute"> <n-layout has-sider position="absolute">
<n-layout-sider :width="200" :collapsed-width="0" :native-scrollbar="false"> <n-layout-sider :width="200" :collapsed-width="0" :native-scrollbar="false">
<side-menu /> <SideMenu />
</n-layout-sider> </n-layout-sider>
<n-layout> <n-layout>
<n-layout-header> <n-layout-header>
<app-header /> <AppHeader />
</n-layout-header> </n-layout-header>
<n-layout position="absolute" style="top: 60px; background-color: #f5f6fb" :native-scrollbar="false"> <n-layout position="absolute" style="top: 60px; background-color: #f5f6fb" :native-scrollbar="false">
<app-main /> <AppMain />
</n-layout> </n-layout>
</n-layout> </n-layout>
</n-layout> </n-layout>

View File

@@ -2,18 +2,18 @@ import '@/styles/index.scss'
import 'uno.css' import 'uno.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from '@/router' import { setupRouter } from '@/router'
import { setupStore } from '@/store' import { setupStore } from '@/store'
import App from './App.vue'
async function bootstrap() { function setupApp() {
const app = createApp(App) const app = createApp(App)
setupStore(app) setupStore(app)
setupRouter(app) setupRouter(app)
app.mount('#app', true) app.mount('#app')
} }
bootstrap() setupApp()

View File

@@ -1,7 +1,9 @@
import { createPageLoadingGuard } from './pageLoadingGuard' import { createPageLoadingGuard } from './page-loading-guard'
import { createPermissionGuard } from './permissionGuard' import { createPageTitleGuard } from './page-title-guard'
import { createPermissionGuard } from './permission-guard'
export function setupRouterGuard(router) { export function setupRouterGuard(router) {
createPageLoadingGuard(router) createPageLoadingGuard(router)
createPermissionGuard(router) createPermissionGuard(router)
createPageTitleGuard(router)
} }

View File

@@ -1,15 +1,15 @@
export function createPageLoadingGuard(router) { export function createPageLoadingGuard(router) {
router.beforeEach(() => { router.beforeEach(() => {
$loadingBar.start() window.$loadingBar?.start()
}) })
router.afterEach(() => { router.afterEach(() => {
setTimeout(() => { setTimeout(() => {
$loadingBar.finish() window.$loadingBar?.finish()
}, 200) }, 200)
}) })
router.onError(() => { router.onError(() => {
$loadingBar.error() window.$loadingBar?.error()
}) })
} }

View File

@@ -0,0 +1,12 @@
const baseTitle = import.meta.env.VITE_APP_TITLE
export function createPageTitleGuard(router) {
router.afterEach((to) => {
const pageTitle = to.meta?.title
if (pageTitle) {
document.title = `${pageTitle} | ${baseTitle}`
} else {
document.title = baseTitle
}
})
}

View File

@@ -1,6 +1,7 @@
import Layout from '@/layout/index.vue' import Layout from '@/layout/index.vue'
import Home from '@/views/dashboard/index.vue' import Home from '@/views/dashboard/index.vue'
import { ChartBar, Dove, Github, HouseDamage, Link, TimesCircle } from '@vicons/fa'
import { IconAlert, IconChart, IconGitee, IconGithub, IconHome, IconLink, IconVue } from '@/components/AppIcons'
export const basicRoutes = [ export const basicRoutes = [
{ {
@@ -39,7 +40,7 @@ export const basicRoutes = [
redirect: '/home', redirect: '/home',
meta: { meta: {
title: 'Dashboard', title: 'Dashboard',
icon: ChartBar, icon: IconChart,
}, },
children: [ children: [
{ {
@@ -48,7 +49,7 @@ export const basicRoutes = [
component: Home, component: Home,
meta: { meta: {
title: '首页', title: '首页',
icon: HouseDamage, icon: IconHome,
}, },
}, },
], ],
@@ -106,7 +107,7 @@ export const basicRoutes = [
redirect: '/error-page/404', redirect: '/error-page/404',
meta: { meta: {
title: '错误页', title: '错误页',
icon: TimesCircle, icon: IconAlert,
}, },
children: [ children: [
{ {
@@ -126,7 +127,7 @@ export const basicRoutes = [
component: Layout, component: Layout,
meta: { meta: {
title: '外部链接', title: '外部链接',
icon: Link, icon: IconLink,
}, },
children: [ children: [
{ {
@@ -134,7 +135,7 @@ export const basicRoutes = [
path: 'https://github.com/zclzone/vue-naive-admin', path: 'https://github.com/zclzone/vue-naive-admin',
meta: { meta: {
title: '源码 - github', title: '源码 - github',
icon: Github, icon: IconGithub,
}, },
}, },
{ {
@@ -142,14 +143,15 @@ export const basicRoutes = [
path: 'https://gitee.com/zclzone/vue-naive-admin', path: 'https://gitee.com/zclzone/vue-naive-admin',
meta: { meta: {
title: '源码 - gitee', title: '源码 - gitee',
icon: IconGitee,
}, },
}, },
{ {
name: 'LINK-DOCS', name: 'LINK-DOCS',
path: 'https://www.yuque.com/qszone/vue-naive-admin', path: 'https://zclzone.github.io/vue-naive-admin-docs',
meta: { meta: {
title: '文档 - 语雀', title: '文档 - vuepress',
icon: Dove, icon: IconVue,
}, },
}, },
], ],

View File

@@ -1,4 +1,4 @@
import { createWebStorage } from './webStorage' import { createWebStorage } from './web-storage'
export const createLocalStorage = function (option = {}) { export const createLocalStorage = function (option = {}) {
return createWebStorage({ prefixKey: option.prefixKey || '', storage: localStorage }) return createWebStorage({ prefixKey: option.prefixKey || '', storage: localStorage })

View File

@@ -1,21 +1,21 @@
<script setup>
import { useRouter } from 'vue-router'
const { replace } = useRouter()
</script>
<template> <template>
<div class="page-404"> <div class="page-404">
<n-result status="404" description="抱歉,您访问的页面不存在。"> <n-result status="404" description="抱歉,您访问的页面不存在。">
<template #icon> <template #icon>
<img src="@/assets/imgs/404/404.png" width="500" /> <img src="@/assets/images/404.png" width="500" />
</template> </template>
<template #footer> <template #footer>
<n-button color="#002d6f" @click="replace('/')">返回首页</n-button> <n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button>
</template> </template>
</n-result> </n-result>
</div> </div>
</template> </template>
<script setup>
import { useRouter } from 'vue-router'
const { replace } = useRouter()
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-404 { .page-404 {
height: 100%; height: 100%;

View File

@@ -4,7 +4,7 @@
<input v-model="post.title" type="text" placeholder="输入文章标题..." class="title" /> <input v-model="post.title" type="text" placeholder="输入文章标题..." class="title" />
<n-button type="primary" style="width: 80px" :loading="btnLoading" @click="handleSavePost">保存</n-button> <n-button type="primary" style="width: 80px" :loading="btnLoading" @click="handleSavePost">保存</n-button>
</div> </div>
<md-editor v-model="post.content" style="height: calc(100vh - 140px)" /> <MdEditor v-model="post.content" style="height: calc(100vh - 140px)" />
</div> </div>
</template> </template>

View File

@@ -1,3 +1,67 @@
<template>
<div class="login-page">
<div class="wrapper">
<div class="left">
<img src="@/assets/images/login_banner.png" height="380" alt="login_banner" />
</div>
<div class="form-wrapper">
<h5 class="brand">
<img src="@/assets/images/logo.svg" width="45" mr-15 alt="logo" />
{{ title }}
</h5>
<div class="form-item" mt-35>
<input
v-model="loginInfo.name"
autofocus
type="text"
class="input"
placeholder="username"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-35>
<input
v-model="loginInfo.password"
type="password"
class="input"
placeholder="password"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-35>
<button class="submit-btn" @click="handleLogin">登录</button>
</div>
</div>
</div>
<!-- <div class="form-wrapper">
<h2 class="title">{{ title }}</h2>
<div class="form-item" mt-20>
<input
v-model="loginInfo.name"
autofocus
type="text"
class="input"
placeholder="username"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-20>
<input
v-model="loginInfo.password"
type="password"
class="input"
placeholder="password"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-20>
<button class="submit-btn" @click="handleLogin">登录</button>
</div>
</div> -->
</div>
</template>
<script setup> <script setup>
import { ref, unref } from 'vue' import { ref, unref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -53,150 +117,75 @@ async function handleLogin() {
} }
</script> </script>
<template>
<div class="login-page">
<div class="form-wrapper">
<h2 class="title">{{ title }}</h2>
<div class="form-item" mt-20>
<input
v-model="loginInfo.name"
autofocus
type="text"
class="input"
placeholder="username"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-20>
<input
v-model="loginInfo.password"
type="password"
class="input"
placeholder="password"
@keydown.enter="handleLogin"
/>
</div>
<div class="form-item" mt-20>
<button class="submit-btn" @click="handleLogin">登录</button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped> <style lang="scss" scoped>
@property --perA {
syntax: '<percentage>';
inherits: false;
initial-value: 75%;
}
@property --perB {
syntax: '<percentage>';
inherits: false;
initial-value: 99%;
}
@property --perC {
syntax: '<percentage>';
inherits: false;
initial-value: 15%;
}
@property --perD {
syntax: '<percentage>';
inherits: false;
initial-value: 16%;
}
@property --perE {
syntax: '<percentage>';
inherits: false;
initial-value: 86%;
}
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.login-page { .login-page {
height: 100%; height: 100%;
background-color: #e1e8ee; background-image: url(@/assets/images/login_bg.jpg);
background-size: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-image: radial-gradient( }
circle at var(--perE) 7%,
rgba(40, 40, 40, 0.04) 0%,
rgba(40, 40, 40, 0.04) 50%,
rgba(200, 200, 200, 0.04) 50%,
rgba(200, 200, 200, 0.04) 100%
),
radial-gradient(
circle at var(--perC) var(--perD),
rgba(99, 99, 99, 0.04) 0%,
rgba(99, 99, 99, 0.04) 50%,
rgba(45, 45, 45, 0.04) 50%,
rgba(45, 45, 45, 0.04) 100%
),
radial-gradient(
circle at var(--perA) var(--perB),
rgba(243, 243, 243, 0.04) 0%,
rgba(243, 243, 243, 0.04) 50%,
rgba(37, 37, 37, 0.04) 50%,
rgba(37, 37, 37, 0.04) 100%
),
linear-gradient(var(--angle), #22deed, #8759d7);
animation: move 30s infinite alternate linear;
@keyframes move { .wrapper {
100% { width: 100%;
--perA: 85%; max-width: 1020px;
--perB: 49%; box-shadow: 1.5px 3.99px 27px 0px rgb(0 0 0 / 10%);
--perC: 45%; background-color: rgba(255, 255, 255, 0.3);
--perD: 39%;
--perE: 70%; display: flex;
--angle: 360deg; .left {
} padding: 40px;
border-right: 1px solid #cccccc5e;
} }
} }
.form-wrapper { .form-wrapper {
text-align: center; display: flex;
padding: 40px 50px; flex-direction: column;
border-radius: 15px; justify-content: center;
background-color: rgba(#fff, 0.2); align-items: center;
width: 100%;
.brand {
width: 100%;
padding: 15px;
color: #6a6a6a;
font-size: 24px;
font-weight: normal;
text-align: center;
.title { display: flex;
font-size: 22px; align-items: center;
color: #f3f3f3; justify-content: center;
} }
.form-item { .form-item {
width: 240px; width: 100%;
max-width: 360px;
height: 50px;
input { input {
width: 100%; width: 100%;
height: 40px; height: 100%;
padding: 0 15px; padding: 0 20px;
border: 1px solid #6a6a6a;
border-radius: 5px; border-radius: 5px;
color: $primaryColor;
font-size: 14px; font-size: 16px;
color: #333; transition: 0.5s;
transition: 0.3s;
&:focus { &:focus {
box-shadow: 0 0 5px #8759d7; border-color: $primaryColor;
box-shadow: 0 0 5px $primaryColor;
} }
} }
button { button {
width: 100%; width: 100%;
height: 40px; height: 100%;
color: #fff; color: #fff;
font-size: 14px; font-size: 16px;
font-weight: bold; font-weight: bold;
border: none; border: none;
border-radius: 5px; border-radius: 5px;
background-color: #6683d2; background-color: $primaryColor;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;

View File

@@ -1,3 +1,5 @@
<template></template>
<script setup> <script setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -19,5 +21,3 @@ replace({
query, query,
}) })
</script> </script>
<template></template>

View File

@@ -1,3 +1,9 @@
<template>
<div p24>
<n-button type="error" @click="handleDelete">删除</n-button>
</div>
</template>
<script setup name="TestDialog"> <script setup name="TestDialog">
const handleDelete = function () { const handleDelete = function () {
$dialog.confirm({ $dialog.confirm({
@@ -11,9 +17,3 @@ const handleDelete = function () {
}) })
} }
</script> </script>
<template>
<div p24>
<n-button type="error" @click="handleDelete">删除</n-button>
</div>
</template>

View File

@@ -1,3 +1,9 @@
<template>
<div p24>
<n-gradient-text gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)"> 注意查看提示语 </n-gradient-text>
</div>
</template>
<!--使用keep-alive须设置name注意请与对应的路由的name保持一致方便统一处理--> <!--使用keep-alive须设置name注意请与对应的路由的name保持一致方便统一处理-->
<script setup name="TEST-KEEP-ALIVE"> <script setup name="TEST-KEEP-ALIVE">
import { onMounted, onActivated, onDeactivated } from 'vue' import { onMounted, onActivated, onDeactivated } from 'vue'
@@ -14,9 +20,3 @@ onDeactivated(() => {
$message.success('触发onDeactivated') $message.success('触发onDeactivated')
}) })
</script> </script>
<template>
<div p24>
<n-gradient-text gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)"> 注意查看提示语 </n-gradient-text>
</div>
</template>

View File

@@ -1,3 +1,9 @@
<template>
<div p24>
<n-button type="primary" @click="handleLogin">点击登陆</n-button>
</div>
</template>
<script setup> <script setup>
function handleLogin() { function handleLogin() {
$message.loading('登陆中...') $message.loading('登陆中...')
@@ -10,9 +16,3 @@ function handleLogin() {
}, 2000) }, 2000)
} }
</script> </script>
<template>
<div p24>
<n-button type="primary" @click="handleLogin">点击登陆</n-button>
</div>
</template>