mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 12:10:20 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3aa8147b1 | ||
|
|
0d240f083a | ||
|
|
c180cf54a8 | ||
|
|
2541706ac3 | ||
|
|
44b935e8f6 | ||
|
|
ea1ce0601a | ||
|
|
2989ecf126 | ||
|
|
ce94bf38d1 | ||
|
|
13bc185926 | ||
|
|
51cfd3e2eb | ||
|
|
c22cb3b35c | ||
|
|
621a2304e7 | ||
|
|
729337cdc5 | ||
|
|
4ef58b612f | ||
|
|
ba5d32244f | ||
|
|
efc2a194a3 | ||
|
|
6160c2e664 | ||
|
|
fbd1e9a38a | ||
|
|
17928cbc57 | ||
|
|
e7fc403c77 |
16
.env.github
Normal file
16
.env.github
Normal 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'
|
||||
11
.env.staging
11
.env.staging
@@ -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
38
.github/workflows/deploy.yml
vendored
Normal 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
21
LICENSE
Normal 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.
|
||||
39
README.md
39
README.md
@@ -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)
|
||||
- 🍑 集成登陆、注销及权限验证
|
||||
- 🍐 集成多环境配置,dev、测试、预发布和生产
|
||||
- 🍐 集成多环境配置,dev、测试、生产和github pages环境
|
||||
- 🍎 集成 Eslint + Prettier,代码约束和格式化统一
|
||||
- 🍉 集成 Mock 接口服务,dev 环境和发布环境都支持,可动态配置是否启用 mock 服务,不启用时不会加载 mock 包,减少打包体积
|
||||
- 🍇 集成 unocss,antfu 大神开源的原子化 css 解决方案,非常轻量,目前我是自己写 scss 样式搭配着 unocss 使用的
|
||||
- 🍍 集成 Pinia,Vuex 的替代方案,轻量、简单、易用(尤大已表示不会有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)
|
||||
@@ -52,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代码格式检查
|
||||
@@ -78,7 +94,9 @@ npm run lint:fix
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Git 提交规范
|
||||
### 规范
|
||||
|
||||
#### git commit 规范
|
||||
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
@@ -94,3 +112,4 @@ npm run preview
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
- `mod` 不确定分类的修改
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"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": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<n-config-provider :theme-overrides="appStore.themeOverrides">
|
||||
<n-config-provider :theme-overrides="useTheme.naiveThemeOverrides">
|
||||
<n-loading-bar-provider>
|
||||
<LoadingBar />
|
||||
<n-dialog-provider>
|
||||
@@ -17,7 +17,7 @@
|
||||
import MessageContent from './MessageContent.vue'
|
||||
import DialogContent from './DialogContent.vue'
|
||||
import LoadingBar from './LoadingBar.vue'
|
||||
import { useThemeStore } from '@/store/modules/theme'
|
||||
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
const appStore = useAppStore()
|
||||
const useTheme = useThemeStore()
|
||||
</script>
|
||||
|
||||
87
src/layout/components/tags/index.vue
Normal file
87
src/layout/components/tags/index.vue
Normal 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>
|
||||
@@ -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>
|
||||
<div class="layout">
|
||||
<n-layout has-sider position="absolute">
|
||||
<n-layout-sider :width="200" :collapsed-width="0" :native-scrollbar="false">
|
||||
<SideMenu />
|
||||
<n-layout-sider bordered :width="200" :collapsed-width="0" :native-scrollbar="false">
|
||||
<SideBar />
|
||||
</n-layout-sider>
|
||||
<n-layout>
|
||||
<n-layout-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">
|
||||
|
||||
<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>
|
||||
@@ -22,6 +23,16 @@ import AppMain from './components/AppMain.vue'
|
||||
</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;
|
||||
|
||||
@@ -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 }),
|
||||
})
|
||||
|
||||
1
src/settings/index.js
Normal file
1
src/settings/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as themeSettings } from './theme.json'
|
||||
14
src/settings/theme.json
Normal file
14
src/settings/theme.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"tags": {
|
||||
"visible": true,
|
||||
"height": 50
|
||||
},
|
||||
"header": {
|
||||
"height": 60
|
||||
},
|
||||
"naiveThemeOverrides": {
|
||||
"common": {
|
||||
"primaryColor": "#316c72"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
22
src/store/modules/tag.js
Normal 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)
|
||||
},
|
||||
},
|
||||
})
|
||||
13
src/store/modules/theme.js
Normal file
13
src/store/modules/theme.js
Normal 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
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="page-404">
|
||||
<n-result status="404" description="抱歉,您访问的页面不存在。">
|
||||
<template #icon>
|
||||
<img src="@/assets/images/404.png" width="500" />
|
||||
<img src="@/assets/images/404.png" width="300" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button>
|
||||
@@ -18,10 +18,14 @@ const { replace } = useRouter()
|
||||
|
||||
<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>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
:row-key="(row) => row.id"
|
||||
max-height="calc(100vh - 250px)"
|
||||
@update:checked-row-keys="handleCheck"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
<MdEditor v-model="post.content" style="height: calc(100vh - 140px)" />
|
||||
<MdEditor v-model="post.content" style="height: calc(100vh - 200px)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -34,31 +34,6 @@
|
||||
</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>
|
||||
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
|
||||
Reference in New Issue
Block a user