mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 12:10:20 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7a61db339 | ||
|
|
39da2634d5 | ||
|
|
b569b58c9d | ||
|
|
525d379af3 | ||
|
|
59cf11be7a | ||
|
|
5eb6874754 | ||
|
|
96644f14f5 | ||
|
|
a240985ee7 | ||
|
|
741524ffac | ||
|
|
d94a7a5a05 | ||
|
|
9f2126835a |
10
README.md
10
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
## 简介
|
||||
|
||||
Vue Naive Admin,一个基于 Vue3.0、Vite、Naive UI 的后台管理模板,相较于其他比较流行的后台管理模板,此项目相对简洁、轻量,没有集成 TypeScript,没有集成国际化,没有集成复杂的主题配置,学习成本非常低,对新手极其友好。不过麻雀虽小五脏俱全,权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置,以及一些经常用的基础组件封装等等这些该有的都有,经过参考多个 vue3 后台管理模板后以最简洁优雅的方式实现,非常适用于中小型项目或者个人项目,当然,以此模板进行二次封装改造用于大型项目也未尝不可。
|
||||
Vue Naive Admin,一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理模板,没有集成 TypeScript,没有集成国际化,没有集成复杂的主题配置,上手成本非常低,对新手极其友好。不过麻雀虽小五脏俱全,权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置,以及一些经常用的基础组件封装等等这些该有的都有,参考多个 vue3 后台管理模板后以最简洁优雅的方式实现,非常适用于中小型项目或者个人项目。
|
||||
|
||||
## 为什么要开发这个模板
|
||||
|
||||
@@ -23,6 +23,10 @@ Vue Naive Admin,一个基于 Vue3.0、Vite、Naive UI 的后台管理模板,
|
||||
- 🍌 二次封装全局 Dialog、Message、LoadingBar 组件
|
||||
- 🍋 二次封装 localStorage 和 sessionStorage,支持设置过期时间
|
||||
|
||||
## 预览
|
||||
|
||||
[template.qszone.com](https://template.qszone.com)
|
||||
|
||||
## 文档
|
||||
|
||||
[羽雀文档:Vue Naive Admin](https://www.yuque.com/qszone/vue-naive-admin)
|
||||
@@ -40,8 +44,8 @@ git clone https://github.com/zclzone/vue-naive-admin.git
|
||||
# 进入项目目录
|
||||
cd vue-naive-admin
|
||||
|
||||
# 安装依赖
|
||||
npm i
|
||||
# 安装依赖(建议使用pnpm: https://pnpm.io/zh/installation)
|
||||
pnpm i # 或者 npm i
|
||||
|
||||
# 启动
|
||||
npm run dev
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
|
||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||
|
||||
import { unocss } from './unocss'
|
||||
import { configHtmlPlugin } from './html'
|
||||
import { configMockPlugin } from './mock'
|
||||
|
||||
export function createVitePlugins(viteEnv, isBuild) {
|
||||
const plugins = [vue(), VueSetupExtend(), unocss(), configHtmlPlugin(viteEnv, isBuild)]
|
||||
const plugins = [
|
||||
vue(),
|
||||
Components({
|
||||
resolvers: [NaiveUiResolver()],
|
||||
}),
|
||||
VueSetupExtend(),
|
||||
unocss(),
|
||||
configHtmlPlugin(viteEnv, isBuild),
|
||||
]
|
||||
|
||||
viteEnv?.VITE_APP_USE_MOCK && plugins.push(configMockPlugin(isBuild))
|
||||
|
||||
|
||||
2955
package-lock.json
generated
2955
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.38.1",
|
||||
"unocss": "^0.16.3",
|
||||
"unplugin-vue-components": "^0.17.18",
|
||||
"vite": "^2.7.6",
|
||||
"vite-plugin-html": "^2.1.1",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
|
||||
2429
pnpm-lock.yaml
generated
Normal file
2429
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,8 @@ let loadingMessage = null
|
||||
class Message {
|
||||
/**
|
||||
* 规则:
|
||||
* * 同一Message实例只显示一个loading message,如果需要显示多个可以创建多个Message实例
|
||||
* * 新的message会替换正在显示的loading message
|
||||
* * 默认已创建一个Message实例$message挂载到window,同时也将Message类挂载到了window
|
||||
* * loading message只显示一个,新的message会替换正在显示的loading message
|
||||
* * loading message不会自动清除,除非被替换成非loading message,非loading message默认2秒后自动清除
|
||||
*/
|
||||
|
||||
removeMessage(message, duration = 2000) {
|
||||
@@ -23,20 +22,20 @@ class Message {
|
||||
}
|
||||
|
||||
showMessage(type, content, option = {}) {
|
||||
if (this.loadingMessage && this.loadingMessage.type === 'loading') {
|
||||
if (loadingMessage && loadingMessage.type === 'loading') {
|
||||
// 如果存在则替换正在显示的loading message
|
||||
this.loadingMessage.type = type
|
||||
this.loadingMessage.content = content
|
||||
loadingMessage.type = type
|
||||
loadingMessage.content = content
|
||||
|
||||
if (type !== 'loading') {
|
||||
// 非loading message需设置自动清除
|
||||
this.removeMessage(this.loadingMessage, option.duration)
|
||||
this.removeMessage(loadingMessage, option.duration)
|
||||
}
|
||||
} else {
|
||||
// 不存在正在显示的loading则新建一个message,如果新建的message是loading message则将message赋值存储下来
|
||||
let message = NMessage[type](content, option)
|
||||
if (type === 'loading') {
|
||||
this.loadingMessage = message
|
||||
loadingMessage = message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import { NConfigProvider, NGlobalStyle, NLoadingBarProvider, NMessageProvider, NDialogProvider } from 'naive-ui'
|
||||
|
||||
import MessageContent from './MessageContent.vue'
|
||||
import DialogContent from './DialogContent.vue'
|
||||
import LoadingBar from './LoadingBar.vue'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup>
|
||||
import { NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const { currentRoute } = router
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
<script setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NDropdown } from 'naive-ui'
|
||||
import { resetRouter } from '@/router'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { NOT_FOUND_ROUTE } from '@/router/routes'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: '切换角色',
|
||||
key: 'switchRole',
|
||||
},
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
@@ -15,10 +21,53 @@ const options = [
|
||||
|
||||
function handleSelect(key) {
|
||||
if (key === 'logout') {
|
||||
logout()
|
||||
} else if (key === 'switchRole') {
|
||||
switchRole()
|
||||
}
|
||||
}
|
||||
|
||||
function logout() {
|
||||
userStore.logout()
|
||||
$message.success('已退出登录')
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
}
|
||||
|
||||
function switchRole() {
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: '大脸怪(admin)',
|
||||
avatar: 'https://gitee.com/zclzone/res/raw/master/qs-zone/blob/img/lADPDiQ3QDTwsz3NAarNAaw_428_426.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',
|
||||
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',
|
||||
role: [],
|
||||
},
|
||||
]
|
||||
|
||||
const switchUser = users[+userStore.userId % users.length]
|
||||
resetRouter()
|
||||
userStore.setUserInfo(switchUser)
|
||||
const accessRoutes = permissionStore.generateRoutes(switchUser.role)
|
||||
accessRoutes.forEach((route) => {
|
||||
!router.hasRoute(route.name) && router.addRoute(route)
|
||||
})
|
||||
router.addRoute(NOT_FOUND_ROUTE)
|
||||
$message.success(`${switchUser.name}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup>
|
||||
import { NGradientText, NIcon } from 'naive-ui'
|
||||
import { LastfmSquare } from '@vicons/fa'
|
||||
const title = import.meta.env.VITE_APP_TITLE
|
||||
</script>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<script setup>
|
||||
import { NMenu } from 'naive-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
|
||||
import { isExternal } from '@/utils/is'
|
||||
|
||||
const router = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const { currentRoute } = router
|
||||
const routes = permissionStore.routes
|
||||
|
||||
const menuOptions = computed(() => {
|
||||
return generateOptions(routes, '')
|
||||
return generateOptions(permissionStore.routes, '')
|
||||
})
|
||||
|
||||
function resolvePath(...pathes) {
|
||||
function resolvePath(basePath, path) {
|
||||
if (isExternal(path)) return path
|
||||
return (
|
||||
'/' +
|
||||
pathes
|
||||
[basePath, path]
|
||||
.filter((path) => !!path && path !== '/')
|
||||
.map((path) => path.replace(/(^\/)|(\/$)/g, ''))
|
||||
.join('/')
|
||||
@@ -49,7 +50,11 @@ function generateOptions(routes, basePath) {
|
||||
}
|
||||
|
||||
function handleMenuSelect(key, item) {
|
||||
if (isExternal(item.path)) {
|
||||
window.open(item.path)
|
||||
} else {
|
||||
router.push(item.path)
|
||||
}
|
||||
|
||||
// 通过path重定向
|
||||
// router.push({
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import { NLayout, NLayoutHeader, NLayoutSider } from 'naive-ui'
|
||||
|
||||
import AppHeader from './components/header/index.vue'
|
||||
import SideMenu from './components/sidebar/index.vue'
|
||||
import AppMain from './components/AppMain.vue'
|
||||
|
||||
@@ -11,9 +11,10 @@ export const router = createRouter({
|
||||
export function resetRouter() {
|
||||
router.getRoutes().forEach((route) => {
|
||||
const { name } = route
|
||||
if (name && !WHITE_NAME_LIST.includes(name)) {
|
||||
router.hasRoute(name) && router.removeRoute(name)
|
||||
}
|
||||
})
|
||||
basicRoutes.forEach((route) => {
|
||||
!router.hasRoute(route.name) && router.addRoute(route)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@ export const basicRoutes = [
|
||||
component: () => import('@/views/error-page/404.vue'),
|
||||
isHidden: true,
|
||||
},
|
||||
{
|
||||
name: '401',
|
||||
path: '/401',
|
||||
component: () => import('@/views/error-page/401.vue'),
|
||||
isHidden: true,
|
||||
},
|
||||
{
|
||||
name: 'REDIRECT',
|
||||
path: '/redirect',
|
||||
@@ -27,7 +21,6 @@ export const basicRoutes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'LOGIN',
|
||||
path: '/login',
|
||||
@@ -93,6 +86,38 @@ export const basicRoutes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'EXTERNAL-LINK',
|
||||
path: '/external-link',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '外链',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'LINK-GITHUB-SRC',
|
||||
path: 'https://github.com/zclzone/vue-naive-admin',
|
||||
meta: {
|
||||
title: '源码 - github',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'LINK-GITEE-SRC',
|
||||
path: 'https://gitee.com/zclzone/vue-naive-admin',
|
||||
meta: {
|
||||
title: '源码 - gitee',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'LINK-DOCS',
|
||||
path: 'https://www.yuque.com/qszone/vue-naive-admin',
|
||||
meta: {
|
||||
title: '文档 - 语雀',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const NOT_FOUND_ROUTE = {
|
||||
|
||||
@@ -42,5 +42,8 @@ export const useUserStore = defineStore('user', {
|
||||
removeToken()
|
||||
this.userInfo = {}
|
||||
},
|
||||
setUserInfo(userInfo = {}) {
|
||||
this.userInfo = { ...this.userInfo, ...userInfo }
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -97,6 +97,14 @@ export function isUrl(path) {
|
||||
return reg.test(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isExternal(path) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
|
||||
export const isServer = typeof window === 'undefined'
|
||||
|
||||
export const isClient = !isServer
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<h1>401</h1>
|
||||
</template>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup name="TestDialog">
|
||||
import { NButton } from 'naive-ui'
|
||||
const handleDelete = function () {
|
||||
$dialog.confirm({
|
||||
content: '确认删除?',
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script setup>
|
||||
import { NButton } from 'naive-ui'
|
||||
|
||||
function handleLogin() {
|
||||
$message.loading('登陆中...')
|
||||
setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user