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

41 Commits

Author SHA1 Message Date
张传龙
b3aa8147b1 refactor: 重构主题色配置,多标签配置化处理 2022-04-10 23:20:28 +08:00
张传龙
0d240f083a feat: 集成tags多标签功能 2022-04-10 21:41:06 +08:00
张传龙
c180cf54a8 Merge branch 'main' of https://github.com/zclzone/vue-naive-admin 2022-04-10 14:08:03 +08:00
张传龙
2541706ac3 docs: update readme 2022-04-10 14:06:06 +08:00
Ronnie Zhang
44b935e8f6 Create LICENSE 2022-04-10 12:57:19 +08:00
张传龙
ea1ce0601a ci: 修改 github actions 配置文件 2022-04-10 00:03:28 +08:00
张传龙
2989ecf126 ci: 修改 github actions 配置文件 2022-04-09 23:43:38 +08:00
张传龙
ce94bf38d1 docs: update readme 2022-04-09 23:38:43 +08:00
张传龙
13bc185926 ci: 修改 github actions 配置文件 2022-04-09 23:36:19 +08:00
张传龙
51cfd3e2eb ci: 修改 github actions 配置文件 2022-04-09 23:29:54 +08:00
张传龙
c22cb3b35c ci: 修改 github actions 配置文件 2022-04-09 23:14:02 +08:00
张传龙
621a2304e7 ci: 修改 github actions 配置文件 2022-04-09 23:03:09 +08:00
张传龙
729337cdc5 ci: 修改 github actions 配置文件 2022-04-09 22:59:05 +08:00
张传龙
4ef58b612f ci: 修改 github actions 配置文件 2022-04-09 22:43:58 +08:00
张传龙
ba5d32244f ci: 修改 github actions 配置文件 2022-04-09 22:42:25 +08:00
张传龙
efc2a194a3 ci: 修改 github actions 配置文件 2022-04-09 22:37:44 +08:00
张传龙
6160c2e664 chore: 修改github actions配置文件 2022-04-09 22:33:13 +08:00
张传龙
fbd1e9a38a ci: 集成github actions自动发布github pages 2022-04-09 22:30:21 +08:00
张传龙
17928cbc57 chore: 集成github pages发布环境 2022-04-09 22:29:11 +08:00
张传龙
e7fc403c77 style: 删除无用注释代码 2022-04-09 19:32:15 +08:00
张传龙
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
60 changed files with 1073 additions and 808 deletions

2
.env
View File

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

16
.env.github Normal file
View File

@@ -0,0 +1,16 @@
# 自定义域名CNAME
# VITE_APP_GLOB_CNAME = 'template.qszone.com'
# 资源公共路径,需要以 /开头和结尾
VITE_PUBLIC_PATH = '/vue-naive-admin/'
VITE_APP_USE_HASH = true
# 是否启用MOCK
VITE_APP_USE_MOCK = true
# base api
VITE_APP_GLOB_BASE_API = '/api'
# test base api
VITE_APP_GLOB_BASE_API_TEST = '/api-test'

View File

@@ -1,11 +0,0 @@
# 资源公共路径,需要以 /开头和结尾
VITE_PUBLIC_PATH = '/'
# 是否启用MOCK
VITE_APP_USE_MOCK = false
# base api
VITE_APP_GLOB_BASE_API = 'http://localhost:8080/api'
# test base api
VITE_APP_GLOB_BASE_API_TEST = 'http://localhost:8080/api-test'

38
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: deploy
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: use Node.js 16
uses: actions/setup-node@v2.1.2
with:
node-version: '16.x'
- name: use pnpm 6.32.2
uses: pnpm/action-setup@v2.2.1
with:
version: 6.32.2
- name: Build
run: |
pnpm install
pnpm run build:github
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
publish_dir: ./dist
github_token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
user_name: ${{ secrets.USER_NAME }}
user_email: ${{ secrets.USER_EMAIL }}
force_orphan: true
commit_message: deploy gh-pages

View File

@@ -1,11 +1,13 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"johnsoncodehk.volar",
"hollowtree.vue-snippets",
"esbenp.prettier-vscode",
"mikestead.dotenv",
"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"
},
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -19,8 +19,12 @@
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
},
"eslint.validate": ["javascript", "javascriptreact", "typescript"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["javascript", "javascriptreact", "typescript"]
"editor.formatOnSave": true
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Ronnie Zhang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,37 +1,54 @@
# VUE NAIVE ADMIN
<p align="center">
<a href="https://github.com/zclzone/vue-naive-admin">
<img alt="Vue Naive Admin Logo" width="200" src="https://assets.qszone.com/images/logo_qs.svg">
</a>
</p>
<p align="center">
<a href="https://github.com/zclzone/vue-naive-admin/actions"><img allt="checks" src="https://badgen.net/github/checks/zclzone/vue-naive-admin"/></a>
<a href="https://github.com/zclzone/vue-naive-admin"><img allt="stars" src="https://badgen.net/github/stars/zclzone/vue-naive-admin"/></a>
<a href="https://github.com/zclzone/vue-naive-admin"><img allt="forks" src="https://badgen.net/github/forks/zclzone/vue-naive-admin"/></a>
<a href="https://github.com/zclzone/vue-naive-admin/releases"><img allt="releases" src="https://badgen.net/github/releases/zclzone/vue-naive-admin"/></a>
<a href="./LICENSE"><img allt="MIT License" src="https://badgen.net/github/license/zclzone/vue-naive-admin"/></a>
</p>
## 简介
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 非常有必要,通过开发模板进行学习是一个很好的方式,事实也证明我确实从中获益良多
2. 目前主流的 Vue3+Vite 后台管理模板都相对复杂,甚至感觉有点花里胡哨(没有贬低的意思,大部分的架构设计都很优秀,只是觉得集成了太多不实用的东西)
3. 自己搭的模板开发起来才最顺手。本人很反感拿别人的模板直接上手开发,如果非要拿别人的模板开发也会尽量先吃透再用,不吃透就没有代码的掌控感和安全感
### 为什么要开发这个模板
## 功能
- Vue3 和 Vite 已经趋于成熟,学习 vite 和 vue3 非常有必要,通过开发模板进行学习是一个很好的方式,事实也证明我确实从中获益良多
- 目前主流的 Vue3+Vite 后台管理模板都相对复杂,甚至感觉有点花里胡哨(没有贬低的意思,大部分的架构设计都很优秀,只是觉得集成了太多不实用的东西)
- 🍒 集成 Naive UI尤大推荐的 UI 组件库很香https://www.naiveui.com
- 🍑 集成登陆、注销及权限验证(暂只支持角色页面权限,后续考虑添加按钮权限)
- 🍐 集成多环境配置dev、测试、预发布和生产
- 🍎 集成 eslint + prettier代码约束和格式化统一
### 功能
- 🍒 集成 Naive UI尤大推荐的 UI 组件库,[https://www.naiveui.com](https://www.naiveui.com)
- 🍑 集成登陆、注销及权限验证
- 🍐 集成多环境配置dev、测试、生产和github pages环境
- 🍎 集成 Eslint + Prettier代码约束和格式化统一
- 🍉 集成 Mock 接口服务dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积
- 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的,很香
- 🍍 集成 piniaVuex 的替代方案,轻量、简单、易用,很香
- 🍏 集成 axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL
- 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的
- 🍍 集成 PiniaVuex 的替代方案,轻量、简单、易用尤大已表示不会有Vuex5或者说pinia就是Vuex5
- 📦 集成 Vite 自动导入插件unplugin-vue-components解放双手开发效率直接起飞
- 🤹 集成 unplugin-icons插件优雅使用iconify图标
- 🍏 二次封装 Axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL
- 🍌 二次封装全局 Dialog、Message、LoadingBar 组件
- 🍋 二次封装 localStorage 和 sessionStorage支持设置过期时间
## 预览
### 预览
[template.qszone.com](https://template.qszone.com)
## 文档
[github pages](https://zclzone.github.io/vue-naive-admin)
### 文档
[Vue Naive Admin Docs](https://zclzone.github.io/vue-naive-admin-docs)
[羽雀文档Vue Naive Admin](https://www.yuque.com/qszone/vue-naive-admin)
## 构建步骤
### 构建
```shell
# 推荐配置git autocrlf 为 false本项目规范使用lf换行符此配置是为防止git自动将源文件转换为crlf
@@ -51,20 +68,20 @@ pnpm i # 或者 npm i
npm run dev
```
## 发布
### 发布
```shell
# 构建测试环境
npm run build:test
# 构建预发布环境
npm run build:staging
# 构建github pages环境
npm run build:github
# 构建生产环境
npm run build
```
## 其他指令
### 其他指令
```shell
# eslint代码格式检查
@@ -77,7 +94,9 @@ npm run lint:fix
npm run preview
```
## Git 提交规范
### 规范
#### git commit 规范
- `feat` 增加新功能
- `fix` 修复问题/BUG
@@ -93,3 +112,4 @@ npm run preview
- `types` 类型定义文件更改
- `wip` 开发中
- `mod` 不确定分类的修改

View File

@@ -1,9 +1,24 @@
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 { 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 { configHtmlPlugin } from './html'
@@ -12,10 +27,11 @@ import { configMockPlugin } from './mock'
export function createVitePlugins(viteEnv, isBuild) {
const plugins = [
vue(),
VueSetupExtend(),
Components({
resolvers: [NaiveUiResolver()],
}),
VueSetupExtend(),
Icons({ compiler: 'vue3', autoInstall: true }),
unocss(),
configHtmlPlugin(viteEnv, isBuild),
]

View File

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

View File

@@ -1,22 +1,18 @@
<!DOCTYPE html>
<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>
<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.ico" />
<title>
<%= title %>
</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -4,21 +4,21 @@ const users = {
admin: {
id: 1,
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',
role: ['admin'],
},
editor: {
id: 2,
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',
role: ['editor'],
},
guest: {
id: 3,
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: [],
},
}

View File

@@ -7,41 +7,43 @@
"lint:fix": "eslint --fix --ext .js,.vue .",
"build": "vite build && esno ./build/script",
"build:test": "vite build --mode test && esno ./build/script",
"build:staging": "vite build --mode staging && esno ./build/script",
"build:github": "vite build --mode github && esno ./build/script",
"preview": "vite preview"
},
"dependencies": {
"@vicons/fa": "^0.11.0",
"axios": "^0.21.4",
"dayjs": "^1.10.7",
"dayjs": "^1.11.0",
"lodash-es": "^4.17.21",
"md-editor-v3": "^1.10.2",
"md-editor-v3": "^1.11.4",
"mockjs": "^1.1.0",
"pinia": "^2.0.11",
"vue": "^3.2.30",
"vue-router": "^4.0.12"
"pinia": "^2.0.13",
"vue": "^3.2.31",
"vue-router": "^4.0.14"
},
"devDependencies": {
"@iconify-json/mdi": "^1.1.9",
"@iconify-json/simple-icons": "^1.1.7",
"@unocss/preset-attributify": "^0.16.4",
"@unocss/preset-icons": "^0.16.4",
"@unocss/preset-uno": "^0.16.4",
"@vitejs/plugin-vue": "^1.10.2",
"@vue/compiler-sfc": "^3.2.30",
"chalk": "^5.0.0",
"@vue/compiler-sfc": "^3.2.31",
"chalk": "^5.0.1",
"dotenv": "^10.0.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint": "^8.12.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0",
"eslint-plugin-vue": "^8.5.0",
"esno": "^0.13.0",
"fs-extra": "^10.0.0",
"naive-ui": "^2.25.2",
"prettier": "^2.5.1",
"sass": "^1.38.1",
"fs-extra": "^10.0.1",
"naive-ui": "^2.27.0",
"prettier": "^2.6.1",
"sass": "^1.49.10",
"unocss": "^0.16.4",
"unplugin-vue-components": "^0.17.18",
"vite": "^2.8.0",
"vite-plugin-html": "^2.1.1",
"unplugin-icons": "^0.14.1",
"unplugin-vue-components": "^0.17.21",
"vite": "^2.9.1",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6",
"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>
<app-provider>
<AppProvider>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</app-provider>
</AppProvider>
</template>
<script setup>
import AppProvider from '@/components/AppProvider/index.vue'
</script>
<style lang="scss">
#app {
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>
import { isNullOrUndef } from '@/utils/is'
import { useDialog } from 'naive-ui'
@@ -6,15 +8,15 @@ const NDialog = useDialog()
class Dialog {
success(title, option) {
this.showDialog('success', { title, ...option })
return this.showDialog('success', { title, ...option })
}
warning(title, option) {
this.showDialog('warning', { title, ...option })
return this.showDialog('warning', { title, ...option })
}
error(title, option) {
this.showDialog('error', { title, ...option })
return this.showDialog('error', { title, ...option })
}
showDialog(type = 'success', option) {
@@ -22,7 +24,7 @@ class Dialog {
// ! 没有title的情况
option.showIcon = false
}
NDialog[type]({
return NDialog[type]({
positiveText: 'OK',
closable: false,
...option,
@@ -30,7 +32,7 @@ class Dialog {
}
confirm(option = {}) {
this.showDialog(option.type || 'error', {
return this.showDialog(option.type || 'error', {
positiveText: '确定',
negativeText: '取消',
onPositiveClick: option.confirm,
@@ -48,5 +50,3 @@ Object.defineProperty(window, '$dialog', {
writable: false,
})
</script>
<template></template>

View File

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

View File

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

View File

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

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>
import { useRouter } from 'vue-router'
const router = useRouter()
@@ -8,11 +16,3 @@ function handleBreadClick(path) {
router.push(path)
}
</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>
import { useUserStore } from '@/store/modules/user'
import { useRouter } from 'vue-router'
@@ -40,21 +49,21 @@ function switchRole() {
{
id: 1,
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',
role: ['admin'],
},
{
id: 2,
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',
role: ['editor'],
},
{
id: 3,
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: [],
},
]
@@ -71,15 +80,6 @@ function switchRole() {
}
</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>
.avatar {
display: flex;

View File

@@ -1,15 +1,15 @@
<template>
<header class="header">
<BreadCrumb />
<HeaderAction />
</header>
</template>
<script setup>
import BreadCrumb from './BreadCrumb.vue'
import HeaderAction from './HeaderAction.vue'
</script>
<template>
<header class="header">
<bread-crumb />
<header-action />
</header>
</template>
<style lang="scss" scoped>
.header {
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>
<div class="logo">
<n-icon size="36" color="#316c72">
<lastfm-square />
<IconLogo />
</n-icon>
<router-link to="/">
<n-gradient-text type="primary">{{ title }}</n-gradient-text>
@@ -14,6 +9,11 @@ const title = import.meta.env.VITE_APP_TITLE
</div>
</template>
<script setup>
import { IconLogo } from '@/components/AppIcons'
const title = import.meta.env.VITE_APP_TITLE
</script>
<style lang="scss" scoped>
.logo {
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>
import { useRouter } from 'vue-router'
import { computed, h } from 'vue'
import { usePermissionStore } from '@/store/modules/permission'
import { NIcon } from 'naive-ui'
import { ListAlt, CircleRegular } from '@vicons/fa'
import { IconCircle, IconMenu } from '@/components/AppIcons'
import { isExternal } from '@/utils/is'
@@ -56,10 +68,10 @@ function generateOptions(routes, basePath) {
path: resolvePath(basePath, route.path),
}
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))
} 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)
}
@@ -82,34 +94,19 @@ function handleMenuSelect(key, item) {
}
</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">
.n-menu {
margin-top: 10px;
padding-left: 10px;
.n-menu-item {
margin-top: 0;
position: relative;
.n-menu-item-content {
&::before {
left: 0;
right: 0;
border-radius: 0;
background-color: unset !important;
}
&:hover,
&.n-menu-item--selected {
&.n-menu-item-content--selected {
border-radius: 0 !important;
&::before {

View File

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

View File

@@ -0,0 +1,87 @@
<template>
<div class="tags-wrapper" :style="{ height: useTheme.tags.height + 'px' }">
<n-space>
<n-tag
v-for="tag in useTag.tags"
:key="tag.path"
:type="useTag.activeTag === tag.path ? 'primary' : 'default'"
:closable="useTag.tags.length > 1"
@click="handleTagClick(tag.path)"
@close.stop="handleClose(tag.path)"
>
{{ tag.title }}
</n-tag>
</n-space>
</div>
</template>
<script setup name="Tags">
import { watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTagStore } from '@/store/modules/tag'
import { useThemeStore } from '@/store/modules/theme'
const route = useRoute()
const router = useRouter()
const useTag = useTagStore()
const useTheme = useThemeStore()
watch(
() => route.path,
() => {
const { name, path } = route
const title = route.meta?.title
useTag.addTag({ name, path, title })
useTag.setActiveTag(path)
},
{ immediate: true }
)
const handleTagClick = (path) => {
useTag.setActiveTag(path)
router.push(path)
}
const handleClose = (path) => {
if (path === useTag.activeTag) {
const activeIndex = useTag.tags.findIndex((item) => item.path === path)
if (activeIndex > 0) {
router.push(useTag.tags[activeIndex - 1].path)
} else {
router.push(useTag.tags[activeIndex + 1].path)
}
}
useTag.removeTag(path)
}
</script>
<style lang="scss">
.tags-wrapper {
display: flex;
align-items: center;
background-color: #fff;
padding: 0 10px;
position: sticky;
top: 0;
z-index: 9;
.n-tag {
padding: 0 15px;
cursor: pointer;
.n-tag__close {
margin-left: 5px;
box-sizing: content-box;
font-size: 12px;
padding: 2px;
border-radius: 50%;
transition: all 0.7s;
&:hover {
color: #fff;
background-color: $primaryColor;
}
}
&:hover {
color: $primaryColor;
}
}
}
</style>

View File

@@ -1,27 +1,38 @@
<script setup>
import AppHeader from './components/header/index.vue'
import SideMenu from './components/sidebar/index.vue'
import AppMain from './components/AppMain.vue'
</script>
<template>
<div class="layout">
<n-layout has-sider position="absolute">
<n-layout-sider :width="200" :collapsed-width="0" :native-scrollbar="false">
<side-menu />
<n-layout-sider bordered :width="200" :collapsed-width="0" :native-scrollbar="false">
<SideBar />
</n-layout-sider>
<n-layout>
<n-layout-header>
<app-header />
<n-layout-header :style="{ height: useTheme.header.height + 'px' }" style="border-left: none">
<AppHeader />
</n-layout-header>
<n-layout position="absolute" style="top: 60px; background-color: #f5f6fb" :native-scrollbar="false">
<app-main />
<n-layout
position="absolute"
style="background-color: #f5f6fb"
:style="{ top: useTheme.header.height + 'px' }"
:native-scrollbar="false"
>
<AppTags v-if="useTheme.tags.visible" />
<AppMain />
</n-layout>
</n-layout>
</n-layout>
</div>
</template>
<script setup>
import AppHeader from './components/header/index.vue'
import SideBar from './components/sidebar/index.vue'
import AppMain from './components/AppMain.vue'
import AppTags from './components/tags/index.vue'
import { useThemeStore } from '@/store/modules/theme'
const useTheme = useThemeStore()
</script>
<style lang="scss" scoped>
.n-layout-header {
height: 60px;

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import { setupRouterGuard } from './guard'
import { basicRoutes } from './routes'
const isHash = !!import.meta.env.VITE_APP_USE_HASH
export const router = createRouter({
history: createWebHistory('/'),
history: isHash ? createWebHashHistory('/') : createWebHistory('/'),
routes: basicRoutes,
scrollBehavior: () => ({ left: 0, top: 0 }),
})

View File

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

1
src/settings/index.js Normal file
View File

@@ -0,0 +1 @@
export { default as themeSettings } from './theme.json'

14
src/settings/theme.json Normal file
View File

@@ -0,0 +1,14 @@
{
"tags": {
"visible": true,
"height": 50
},
"header": {
"height": 60
},
"naiveThemeOverrides": {
"common": {
"primaryColor": "#316c72"
}
}
}

View File

@@ -1,17 +0,0 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state() {
return {
themeOverrides: {
common: {
primaryColor: '#316c72',
primaryColorSuppl: '#316c72',
primaryColorHover: '#316c72',
successColorHover: '#316c72',
successColorSuppl: '#316c72',
},
},
}
},
})

22
src/store/modules/tag.js Normal file
View File

@@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
export const useTagStore = defineStore('tag', {
state() {
return {
tags: [],
activeTag: '',
}
},
actions: {
setActiveTag(path) {
this.activeTag = path
},
addTag(tag = {}) {
if (this.tags.some((item) => item.path === tag.path)) return
this.tags.push(tag)
},
removeTag(path) {
this.tags = this.tags.filter((tag) => tag.path !== path)
},
},
})

View File

@@ -0,0 +1,13 @@
import { defineStore } from 'pinia'
import { themeSettings } from '@/settings'
export const useThemeStore = defineStore('theme', {
state() {
return themeSettings
},
getters: {},
actions: {
setTabVisible(visible) {
this.tags.visible = visible
},
},
})

View File

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

View File

@@ -1,27 +1,31 @@
<script setup>
import { useRouter } from 'vue-router'
const { replace } = useRouter()
</script>
<template>
<div class="page-404">
<n-result status="404" description="抱歉,您访问的页面不存在。">
<template #icon>
<img src="@/assets/imgs/404/404.png" width="500" />
<img src="@/assets/images/404.png" width="300" />
</template>
<template #footer>
<n-button color="#002d6f" @click="replace('/')">返回首页</n-button>
<n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button>
</template>
</n-result>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const { replace } = useRouter()
</script>
<style lang="scss" scoped>
.page-404 {
height: 100%;
min-height: calc(100vh - 60px);
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 50px;
bottom: 50px;
left: 0;
right: 0;
}
</style>

View File

@@ -11,7 +11,6 @@
:columns="columns"
:pagination="pagination"
:row-key="(row) => row.id"
max-height="calc(100vh - 250px)"
@update:checked-row-keys="handleCheck"
/>
</div>

View File

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

View File

@@ -1,3 +1,42 @@
<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>
</template>
<script setup>
import { ref, unref } from 'vue'
import { useRouter } from 'vue-router'
@@ -53,150 +92,75 @@ async function handleLogin() {
}
</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>
@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 {
height: 100%;
background-color: #e1e8ee;
background-image: url(@/assets/images/login_bg.jpg);
background-size: 100%;
display: flex;
align-items: 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 {
100% {
--perA: 85%;
--perB: 49%;
--perC: 45%;
--perD: 39%;
--perE: 70%;
--angle: 360deg;
}
.wrapper {
width: 100%;
max-width: 1020px;
box-shadow: 1.5px 3.99px 27px 0px rgb(0 0 0 / 10%);
background-color: rgba(255, 255, 255, 0.3);
display: flex;
.left {
padding: 40px;
border-right: 1px solid #cccccc5e;
}
}
.form-wrapper {
text-align: center;
padding: 40px 50px;
border-radius: 15px;
background-color: rgba(#fff, 0.2);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
.brand {
width: 100%;
padding: 15px;
color: #6a6a6a;
font-size: 24px;
font-weight: normal;
text-align: center;
.title {
font-size: 22px;
color: #f3f3f3;
display: flex;
align-items: center;
justify-content: center;
}
.form-item {
width: 240px;
width: 100%;
max-width: 360px;
height: 50px;
input {
width: 100%;
height: 40px;
padding: 0 15px;
height: 100%;
padding: 0 20px;
border: 1px solid #6a6a6a;
border-radius: 5px;
font-size: 14px;
color: #333;
transition: 0.3s;
color: $primaryColor;
font-size: 16px;
transition: 0.5s;
&:focus {
box-shadow: 0 0 5px #8759d7;
border-color: $primaryColor;
box-shadow: 0 0 5px $primaryColor;
}
}
button {
width: 100%;
height: 40px;
height: 100%;
color: #fff;
font-size: 14px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 5px;
background-color: #6683d2;
background-color: $primaryColor;
cursor: pointer;
transition: all 0.3s;

View File

@@ -1,3 +1,5 @@
<template></template>
<script setup>
import { useRouter } from 'vue-router'
@@ -19,5 +21,3 @@ replace({
query,
})
</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">
const handleDelete = function () {
$dialog.confirm({
@@ -11,9 +17,3 @@ const handleDelete = function () {
})
}
</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保持一致方便统一处理-->
<script setup name="TEST-KEEP-ALIVE">
import { onMounted, onActivated, onDeactivated } from 'vue'
@@ -14,9 +20,3 @@ onDeactivated(() => {
$message.success('触发onDeactivated')
})
</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>
function handleLogin() {
$message.loading('登陆中...')
@@ -10,9 +16,3 @@ function handleLogin() {
}, 2000)
}
</script>
<template>
<div p24>
<n-button type="primary" @click="handleLogin">点击登陆</n-button>
</div>
</template>

View File

@@ -13,3 +13,5 @@
</div>
</div>
</template>
<script setup></script>