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

26 Commits

Author SHA1 Message Date
张传龙
40d5106c6b release: v0.3.2 2022-04-15 22:16:10 +08:00
张传龙
094a9dcb3b fix: 修复二次登录后标签页会多出登录标签问题 2022-04-14 15:30:34 +08:00
张传龙
db5089d92e chore: update jsconfig.json 2022-04-13 17:24:27 +08:00
张传龙
a41ccad2d0 style: 修改naive ui主题颜色配置 2022-04-12 17:18:21 +08:00
张传龙
8973e39566 feat: 多标签增加sessionStorage缓存 2022-04-11 22:13:17 +08:00
张传龙
8c1191ece2 mod: 文件名模块修改 2022-04-11 22:12:00 +08:00
张传龙
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
24 changed files with 302 additions and 88 deletions

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

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,4 +1,16 @@
## 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>
### 简介 ### 简介
@@ -13,26 +25,30 @@
- 🍒 集成 Naive UI尤大推荐的 UI 组件库,[https://www.naiveui.com](https://www.naiveui.com) - 🍒 集成 Naive UI尤大推荐的 UI 组件库,[https://www.naiveui.com](https://www.naiveui.com)
- 🍑 集成登陆、注销及权限验证 - 🍑 集成登陆、注销及权限验证
- 🍐 集成多环境配置dev、测试、预发布和生产 - 🍐 集成多环境配置dev、测试、生产和github pages环境
- 🍎 集成 Eslint + Prettier代码约束和格式化统一 - 🍎 集成 Eslint + Prettier代码约束和格式化统一
- 🍉 集成 Mock 接口服务dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积 - 🍉 集成 Mock 接口服务dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积
- 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的 - 🍇 集成 unocssantfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的
- 🍍 集成 PiniaVuex 的替代方案轻量、简单、易用尤大已表示不会有Vuex5或者说pinia就是Vuex5 - 🍍 集成 PiniaVuex 的替代方案轻量、简单、易用尤大已表示不会有Vuex5或者说pinia就是Vuex5
- 📦 集成 Vite 自动导入插件unplugin-vue-components解放双手开发效率直接起飞 - 📦 集成 Vite 自动导入插件unplugin-vue-components解放双手开发效率直接起飞
- 🤹 集成 unplugin-icons插件优雅使用iconify图标
- 🍏 二次封装 Axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL - 🍏 二次封装 Axios支持多 axios 实例,支持线上环境免重新打包修改 baseURL
- 🍌 二次封装全局 Dialog、Message、LoadingBar 组件 - 🍌 二次封装全局 Dialog、Message、LoadingBar 组件
- 🍋 二次封装 localStorage 和 sessionStorage支持设置过期时间 - 🍋 二次封装 localStorage 和 sessionStorage支持设置过期时间
## 预览 ### 预览
[template.qszone.com](https://template.qszone.com) [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 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)
## 构建步骤 ### 构建
```shell ```shell
# 推荐配置git autocrlf 为 false本项目规范使用lf换行符此配置是为防止git自动将源文件转换为crlf # 推荐配置git autocrlf 为 false本项目规范使用lf换行符此配置是为防止git自动将源文件转换为crlf
@@ -52,20 +68,20 @@ pnpm i # 或者 npm i
npm run dev npm run dev
``` ```
## 发布 ### 发布
```shell ```shell
# 构建测试环境 # 构建测试环境
npm run build:test npm run build:test
# 构建预发布环境 # 构建github pages环境
npm run build:staging npm run build:github
# 构建生产环境 # 构建生产环境
npm run build npm run build
``` ```
## 其他指令 ### 其他指令
```shell ```shell
# eslint代码格式检查 # eslint代码格式检查
@@ -78,7 +94,9 @@ npm run lint:fix
npm run preview npm run preview
``` ```
## Git 提交规范 ### 规范
#### git commit 规范
- `feat` 增加新功能 - `feat` 增加新功能
- `fix` 修复问题/BUG - `fix` 修复问题/BUG
@@ -94,3 +112,4 @@ npm run preview
- `types` 类型定义文件更改 - `types` 类型定义文件更改
- `wip` 开发中 - `wip` 开发中
- `mod` 不确定分类的修改 - `mod` 不确定分类的修改

View File

@@ -3,7 +3,8 @@
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"]
} },
"jsx": "preserve"
}, },
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }

View File

@@ -1,13 +1,13 @@
{ {
"name": "vue-naive-admin", "name": "vue-naive-admin",
"version": "0.0.1", "version": "0.3.2",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"lint": "eslint --ext .js,.vue .", "lint": "eslint --ext .js,.vue .",
"lint:fix": "eslint --fix --ext .js,.vue .", "lint:fix": "eslint --fix --ext .js,.vue .",
"build": "vite build && esno ./build/script", "build": "vite build && esno ./build/script",
"build:test": "vite build --mode test && 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" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

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

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 useTags.tags"
:key="tag.path"
:type="useTags.activeTag === tag.path ? 'primary' : 'default'"
:closable="useTags.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 { useTagsStore } from '@/store/modules/tags'
import { useThemeStore } from '@/store/modules/theme'
const route = useRoute()
const router = useRouter()
const useTags = useTagsStore()
const useTheme = useThemeStore()
watch(
() => route.path,
() => {
const { name, path } = route
const title = route.meta?.title
useTags.addTag({ name, path, title })
useTags.setActiveTag(path)
},
{ immediate: true }
)
const handleTagClick = (path) => {
useTags.setActiveTag(path)
router.push(path)
}
const handleClose = (path) => {
if (path === useTags.activeTag) {
const activeIndex = useTags.tags.findIndex((item) => item.path === path)
if (activeIndex > 0) {
router.push(useTags.tags[activeIndex - 1].path)
} else {
router.push(useTags.tags[activeIndex + 1].path)
}
}
useTags.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,20 +1,21 @@
<script setup>
import AppHeader from './components/header/index.vue'
import SideMenu from './components/sidebar/index.vue'
import AppMain from './components/AppMain.vue'
</script>
<template> <template>
<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 bordered :width="200" :collapsed-width="0" :native-scrollbar="false">
<SideMenu /> <SideBar />
</n-layout-sider> </n-layout-sider>
<n-layout> <n-layout>
<n-layout-header> <n-layout-header :style="{ height: useTheme.header.height + 'px' }" style="border-left: none">
<AppHeader /> <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="background-color: #f5f6fb"
:style="{ top: useTheme.header.height + 'px' }"
:native-scrollbar="false"
>
<AppTags v-if="useTheme.tags.visible" />
<AppMain /> <AppMain />
</n-layout> </n-layout>
</n-layout> </n-layout>
@@ -22,6 +23,16 @@ import AppMain from './components/AppMain.vue'
</div> </div>
</template> </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> <style lang="scss" scoped>
.n-layout-header { .n-layout-header {
height: 60px; height: 60px;

View File

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

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

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

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

@@ -0,0 +1,21 @@
{
"tags": {
"visible": true,
"height": 50
},
"header": {
"height": 60
},
"naiveThemeOverrides": {
"common": {
"primaryColor": "#316C72FF",
"primaryColorHover": "#316C72E3",
"primaryColorPressed": "#2B4C59FF",
"primaryColorSuppl": "#316C7263",
"successColor": "#316C72FF",
"successColorHover": "#316C72E3",
"successColorPressed": "#2B4C59FF",
"successColorSuppl": "#316C7263"
}
}
}

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

View File

@@ -0,0 +1,7 @@
import { createSessionStorage } from '@/utils/cache'
export const tagsSS = createSessionStorage({ prefixKey: 'tag_' })
export const activeTag = tagsSS.get('activeTag')
export const tags = tagsSS.get('tags')
export const WITHOUT_TAG_PATHS = ['/404', '/login', '/redirect']

View File

@@ -0,0 +1,26 @@
import { defineStore } from 'pinia'
import { tagsSS, activeTag, tags, WITHOUT_TAG_PATHS } from './helpers'
export const useTagsStore = defineStore('tag', {
state() {
return {
tags: tags || [],
activeTag: activeTag || '',
}
},
actions: {
setActiveTag(path) {
this.activeTag = path
tagsSS.set('activeTag', path)
},
addTag(tag = {}) {
if (WITHOUT_TAG_PATHS.includes(tag.path) || this.tags.some((item) => item.path === tag.path)) return
this.tags.push(tag)
tagsSS.set('tags', this.tags)
},
removeTag(path) {
this.tags = this.tags.filter((tag) => tag.path !== path)
tagsSS.set('tags', this.tags)
},
},
})

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,6 +1,6 @@
import { router } from '@/router' import { router } from '@/router'
import { getToken, removeToken } from '@/utils/token' import { getToken, removeToken } from '@/utils/token'
import { isWithoutToken } from './help' import { isWithoutToken } from './helpers'
export function setupInterceptor(service) { export function setupInterceptor(service) {
service.interceptors.request.use( service.interceptors.request.use(

View File

@@ -2,7 +2,7 @@
<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/images/404.png" width="500" /> <img src="@/assets/images/404.png" width="300" />
</template> </template>
<template #footer> <template #footer>
<n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button> <n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button>
@@ -18,10 +18,14 @@ const { replace } = useRouter()
<style lang="scss" scoped> <style lang="scss" scoped>
.page-404 { .page-404 {
height: 100%;
min-height: calc(100vh - 60px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: absolute;
top: 50px;
bottom: 50px;
left: 0;
right: 0;
} }
</style> </style>

View File

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

View File

@@ -1,10 +1,10 @@
<template> <template>
<div> <div pb-20>
<div class="header"> <div class="header">
<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>
<MdEditor v-model="post.content" style="height: calc(100vh - 140px)" /> <MdEditor v-model="post.content" style="height: calc(100vh - 200px)" />
</div> </div>
</template> </template>

View File

@@ -34,31 +34,6 @@
</div> </div>
</div> </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> </div>
</template> </template>

View File

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