mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 12:10:20 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
606334ffad | ||
|
|
a44be5aec0 | ||
|
|
28f0815211 | ||
|
|
2f592f9570 | ||
|
|
d0c5a805d8 | ||
|
|
4929bf7d03 | ||
|
|
5fcc47816c | ||
|
|
8bcbcac531 | ||
|
|
402c7db7ba | ||
|
|
d955f55f7c | ||
|
|
b5fde56177 | ||
|
|
c761501855 | ||
|
|
8e04bc7821 | ||
|
|
d7a61db339 | ||
|
|
39da2634d5 | ||
|
|
b569b58c9d | ||
|
|
525d379af3 | ||
|
|
59cf11be7a | ||
|
|
5eb6874754 | ||
|
|
96644f14f5 | ||
|
|
a240985ee7 | ||
|
|
741524ffac | ||
|
|
d94a7a5a05 | ||
|
|
9f2126835a |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -6,7 +6,6 @@
|
|||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
"wayou.vscode-todo-highlight",
|
"wayou.vscode-todo-highlight",
|
||||||
"wayou.vscode-todo-highlight",
|
|
||||||
"aaron-bond.better-comments"
|
"aaron-bond.better-comments"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
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 组件
|
- 🍌 二次封装全局 Dialog、Message、LoadingBar 组件
|
||||||
- 🍋 二次封装 localStorage 和 sessionStorage,支持设置过期时间
|
- 🍋 二次封装 localStorage 和 sessionStorage,支持设置过期时间
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
[template.qszone.com](https://template.qszone.com)
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
[羽雀文档:Vue Naive Admin](https://www.yuque.com/qszone/vue-naive-admin)
|
[羽雀文档: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
|
cd vue-naive-admin
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖(建议使用pnpm: https://pnpm.io/zh/installation)
|
||||||
npm i
|
pnpm i # 或者 npm i
|
||||||
|
|
||||||
# 启动
|
# 启动
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
import vue from '@vitejs/plugin-vue'
|
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 VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||||
|
|
||||||
import { unocss } from './unocss'
|
import { unocss } from './unocss'
|
||||||
import { configHtmlPlugin } from './html'
|
import { configHtmlPlugin } from './html'
|
||||||
import { configMockPlugin } from './mock'
|
import { configMockPlugin } from './mock'
|
||||||
|
|
||||||
export function createVitePlugins(viteEnv, isBuild) {
|
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))
|
viteEnv?.VITE_APP_USE_MOCK && plugins.push(configMockPlugin(isBuild))
|
||||||
|
|
||||||
|
|||||||
393
mock/post/index.js
Normal file
393
mock/post/index.js
Normal file
File diff suppressed because one or more lines are too long
2955
package-lock.json
generated
2955
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -14,19 +14,19 @@
|
|||||||
"@vicons/fa": "^0.11.0",
|
"@vicons/fa": "^0.11.0",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
"js-cookie": "^3.0.1",
|
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"md-editor-v3": "^1.10.2",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"pinia": "^2.0.9",
|
"pinia": "^2.0.11",
|
||||||
"vue": "^3.2.6",
|
"vue": "^3.2.30",
|
||||||
"vue-router": "^4.0.12"
|
"vue-router": "^4.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@unocss/preset-attributify": "^0.16.3",
|
"@unocss/preset-attributify": "^0.16.4",
|
||||||
"@unocss/preset-icons": "^0.16.3",
|
"@unocss/preset-icons": "^0.16.4",
|
||||||
"@unocss/preset-uno": "^0.16.3",
|
"@unocss/preset-uno": "^0.16.4",
|
||||||
"@vitejs/plugin-vue": "^1.6.0",
|
"@vitejs/plugin-vue": "^1.10.2",
|
||||||
"@vue/compiler-sfc": "^3.0.5",
|
"@vue/compiler-sfc": "^3.2.30",
|
||||||
"chalk": "^5.0.0",
|
"chalk": "^5.0.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.6.0",
|
||||||
@@ -35,11 +35,12 @@
|
|||||||
"eslint-plugin-vue": "^8.2.0",
|
"eslint-plugin-vue": "^8.2.0",
|
||||||
"esno": "^0.13.0",
|
"esno": "^0.13.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"naive-ui": "^2.19.1",
|
"naive-ui": "^2.25.2",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"sass": "^1.38.1",
|
"sass": "^1.38.1",
|
||||||
"unocss": "^0.16.3",
|
"unocss": "^0.16.4",
|
||||||
"vite": "^2.7.6",
|
"unplugin-vue-components": "^0.17.18",
|
||||||
|
"vite": "^2.8.0",
|
||||||
"vite-plugin-html": "^2.1.1",
|
"vite-plugin-html": "^2.1.1",
|
||||||
"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"
|
||||||
|
|||||||
2429
pnpm-lock.yaml
generated
Normal file
2429
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
src/App.vue
15
src/App.vue
@@ -3,16 +3,11 @@ import AppProvider from '@/components/AppProvider/index.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-view>
|
<app-provider>
|
||||||
<template #default="{ Component, route }">
|
<router-view v-slot="{ Component }">
|
||||||
<app-provider>
|
<component :is="Component" />
|
||||||
<keep-alive v-if="route.meta && route.meta.keepAlive">
|
</router-view>
|
||||||
<component :is="Component" :key="route.fullPath" />
|
</app-provider>
|
||||||
</keep-alive>
|
|
||||||
<component :is="Component" v-else :key="route.fullPath" />
|
|
||||||
</app-provider>
|
|
||||||
</template>
|
|
||||||
</router-view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defAxios } from '@/utils/http'
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
export const login = (data) => {
|
export const login = (data) => {
|
||||||
return defAxios({
|
return request({
|
||||||
url: '/auth/login',
|
url: '/auth/login',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
@@ -9,7 +9,7 @@ export const login = (data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const refreshToken = () => {
|
export const refreshToken = () => {
|
||||||
return defAxios({
|
return request({
|
||||||
url: '/auth/refreshToken',
|
url: '/auth/refreshToken',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
})
|
})
|
||||||
|
|||||||
39
src/api/post/index.js
Normal file
39
src/api/post/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
|
export function getPosts(data = {}) {
|
||||||
|
return request({
|
||||||
|
url: '/posts',
|
||||||
|
method: 'get',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPostById({ id }) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function savePost(id, data = {}) {
|
||||||
|
if (id) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return request({
|
||||||
|
url: '/post',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePost(id) {
|
||||||
|
return request({
|
||||||
|
url: `/post/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defAxios } from '@/utils/http'
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
export function getUsers(data = {}) {
|
export function getUsers(data = {}) {
|
||||||
return defAxios({
|
return request({
|
||||||
url: '/users',
|
url: '/users',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
data,
|
data,
|
||||||
@@ -10,12 +10,12 @@ export function getUsers(data = {}) {
|
|||||||
|
|
||||||
export function getUser(id) {
|
export function getUser(id) {
|
||||||
if (id) {
|
if (id) {
|
||||||
return defAxios({
|
return request({
|
||||||
url: `/user/${id}`,
|
url: `/user/${id}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return defAxios({
|
return request({
|
||||||
url: '/user',
|
url: '/user',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
@@ -23,14 +23,14 @@ export function getUser(id) {
|
|||||||
|
|
||||||
export function saveUser(data = {}, id) {
|
export function saveUser(data = {}, id) {
|
||||||
if (id) {
|
if (id) {
|
||||||
return defAxios({
|
return request({
|
||||||
url: '/user',
|
url: '/user',
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return defAxios({
|
return request({
|
||||||
url: `/user/${id}`,
|
url: `/user/${id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data,
|
data,
|
||||||
|
|||||||
BIN
src/assets/imgs/404/404.png
Normal file
BIN
src/assets/imgs/404/404.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -8,9 +8,8 @@ let loadingMessage = null
|
|||||||
class Message {
|
class Message {
|
||||||
/**
|
/**
|
||||||
* 规则:
|
* 规则:
|
||||||
* * 同一Message实例只显示一个loading message,如果需要显示多个可以创建多个Message实例
|
* * loading message只显示一个,新的message会替换正在显示的loading message
|
||||||
* * 新的message会替换正在显示的loading message
|
* * loading message不会自动清除,除非被替换成非loading message,非loading message默认2秒后自动清除
|
||||||
* * 默认已创建一个Message实例$message挂载到window,同时也将Message类挂载到了window
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
removeMessage(message, duration = 2000) {
|
removeMessage(message, duration = 2000) {
|
||||||
@@ -23,20 +22,20 @@ class Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showMessage(type, content, option = {}) {
|
showMessage(type, content, option = {}) {
|
||||||
if (this.loadingMessage && this.loadingMessage.type === 'loading') {
|
if (loadingMessage && loadingMessage.type === 'loading') {
|
||||||
// 如果存在则替换正在显示的loading message
|
// 如果存在则替换正在显示的loading message
|
||||||
this.loadingMessage.type = type
|
loadingMessage.type = type
|
||||||
this.loadingMessage.content = content
|
loadingMessage.content = content
|
||||||
|
|
||||||
if (type !== 'loading') {
|
if (type !== 'loading') {
|
||||||
// 非loading message需设置自动清除
|
// 非loading message需设置自动清除
|
||||||
this.removeMessage(this.loadingMessage, option.duration)
|
this.removeMessage(loadingMessage, option.duration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 不存在正在显示的loading则新建一个message,如果新建的message是loading message则将message赋值存储下来
|
// 不存在正在显示的loading则新建一个message,如果新建的message是loading message则将message赋值存储下来
|
||||||
let message = NMessage[type](content, option)
|
let message = NMessage[type](content, option)
|
||||||
if (type === 'loading') {
|
if (type === 'loading') {
|
||||||
this.loadingMessage = message
|
loadingMessage = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NConfigProvider, NGlobalStyle, NLoadingBarProvider, NMessageProvider, NDialogProvider } from 'naive-ui'
|
|
||||||
|
|
||||||
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'
|
||||||
@@ -11,14 +9,13 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-config-provider :theme-overrides="appStore.themeOverrides">
|
<n-config-provider :theme-overrides="appStore.themeOverrides">
|
||||||
<n-global-style />
|
|
||||||
<n-loading-bar-provider>
|
<n-loading-bar-provider>
|
||||||
<loading-bar />
|
<loading-bar />
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<dialog-content />
|
<dialog-content />
|
||||||
<n-message-provider>
|
<n-message-provider>
|
||||||
<message-content />
|
<message-content />
|
||||||
<slot name="default"></slot>
|
<slot></slot>
|
||||||
</n-message-provider>
|
</n-message-provider>
|
||||||
</n-dialog-provider>
|
</n-dialog-provider>
|
||||||
</n-loading-bar-provider>
|
</n-loading-bar-provider>
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view>
|
<router-view v-slot="{ Component, route }">
|
||||||
<template #default="{ Component, route }">
|
<transition name="fade-slide" mode="out-in" appear>
|
||||||
<transition name="fade-slide" mode="out-in" appear>
|
<keep-alive :include="keepAliveRouteNames">
|
||||||
<component :is="Component" :key="route.fullPath"></component>
|
<component :is="Component" :key="route.path" />
|
||||||
</transition>
|
</keep-alive>
|
||||||
</template>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const router = useRouter()
|
||||||
|
const allRoutes = router.getRoutes()
|
||||||
|
const keepAliveRouteNames = computed(() => {
|
||||||
|
return allRoutes.filter((route) => route.meta?.keepAlive).map((route) => route.name)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NBreadcrumb, NBreadcrumbItem } from 'naive-ui'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { currentRoute } = router
|
const { currentRoute } = router
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<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'
|
||||||
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 userStore = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
|
{
|
||||||
|
label: '切换角色',
|
||||||
|
key: 'switchRole',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '退出登录',
|
label: '退出登录',
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
@@ -15,11 +21,54 @@ const options = [
|
|||||||
|
|
||||||
function handleSelect(key) {
|
function handleSelect(key) {
|
||||||
if (key === 'logout') {
|
if (key === 'logout') {
|
||||||
userStore.logout()
|
logout()
|
||||||
$message.success('已退出登录')
|
} else if (key === 'switchRole') {
|
||||||
router.push({ path: '/login' })
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NGradientText, NIcon } from 'naive-ui'
|
|
||||||
import { LastfmSquare } from '@vicons/fa'
|
import { LastfmSquare } from '@vicons/fa'
|
||||||
const title = import.meta.env.VITE_APP_TITLE
|
const title = import.meta.env.VITE_APP_TITLE
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,29 +1,51 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NMenu } from 'naive-ui'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { computed } 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 { ListAlt, CircleRegular } from '@vicons/fa'
|
||||||
|
|
||||||
|
import { isExternal } from '@/utils/is'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
const { currentRoute } = router
|
const { currentRoute } = router
|
||||||
const routes = permissionStore.routes
|
|
||||||
|
|
||||||
const menuOptions = computed(() => {
|
const menuOptions = computed(() => {
|
||||||
return generateOptions(routes, '')
|
return generateOptions(permissionStore.routes, '')
|
||||||
})
|
})
|
||||||
|
|
||||||
function resolvePath(...pathes) {
|
function resolvePath(basePath, path) {
|
||||||
|
if (isExternal(path)) return path
|
||||||
return (
|
return (
|
||||||
'/' +
|
'/' +
|
||||||
pathes
|
[basePath, path]
|
||||||
.filter((path) => !!path && path !== '/')
|
.filter((path) => !!path && path !== '/')
|
||||||
.map((path) => path.replace(/(^\/)|(\/$)/g, ''))
|
.map((path) => path.replace(/(^\/)|(\/$)/g, ''))
|
||||||
.join('/')
|
.join('/')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderIcon(icon, props = { size: 12 }) {
|
||||||
|
return () => h(NIcon, { ...props }, { default: () => h(icon) })
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSingleRoute(route) {
|
||||||
|
let isSingle = true
|
||||||
|
let curRoute = route
|
||||||
|
while (curRoute.children && curRoute.children.length) {
|
||||||
|
if (curRoute.children.length > 1) {
|
||||||
|
isSingle = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (curRoute.children.length === 1) {
|
||||||
|
curRoute = curRoute.children[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isSingle
|
||||||
|
}
|
||||||
|
|
||||||
function generateOptions(routes, basePath) {
|
function generateOptions(routes, basePath) {
|
||||||
let options = []
|
let options = []
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
@@ -34,13 +56,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.children = generateOptions(route.children, resolvePath(basePath, route.path))
|
curOption.children = generateOptions(route.children, resolvePath(basePath, route.path))
|
||||||
}
|
} else {
|
||||||
if (curOption.children && curOption.children.length <= 1) {
|
curOption.icon = (route.meta?.icon && renderIcon(route.meta?.icon)) || renderIcon(CircleRegular, { size: 8 })
|
||||||
if (curOption.children.length === 1) {
|
|
||||||
curOption = { ...curOption.children[0] }
|
|
||||||
}
|
|
||||||
delete curOption.children
|
|
||||||
}
|
}
|
||||||
options.push(curOption)
|
options.push(curOption)
|
||||||
}
|
}
|
||||||
@@ -49,7 +68,11 @@ function generateOptions(routes, basePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleMenuSelect(key, item) {
|
function handleMenuSelect(key, item) {
|
||||||
router.push(item.path)
|
if (isExternal(item.path)) {
|
||||||
|
window.open(item.path)
|
||||||
|
} else {
|
||||||
|
router.push(item.path)
|
||||||
|
}
|
||||||
|
|
||||||
// 通过path重定向
|
// 通过path重定向
|
||||||
// router.push({
|
// router.push({
|
||||||
@@ -62,7 +85,9 @@ function handleMenuSelect(key, item) {
|
|||||||
<template>
|
<template>
|
||||||
<n-menu
|
<n-menu
|
||||||
class="side-menu"
|
class="side-menu"
|
||||||
:root-indent="20"
|
accordion
|
||||||
|
:indent="12"
|
||||||
|
:root-indent="12"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
||||||
@update:value="handleMenuSelect"
|
@update:value="handleMenuSelect"
|
||||||
@@ -106,55 +131,7 @@ function handleMenuSelect(key, item) {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: -15px;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin: auto;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid #333;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .side-menu {
|
|
||||||
// // padding-left: 15px;
|
|
||||||
// .n-menu-item-content-header {
|
|
||||||
// color: #fff !important;
|
|
||||||
// font-weight: bold;
|
|
||||||
// font-size: 14px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .n-submenu {
|
|
||||||
// .n-menu-item-content-header {
|
|
||||||
// color: #fff !important;
|
|
||||||
// font-weight: bold;
|
|
||||||
// font-size: 14px;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .n-submenu-children {
|
|
||||||
// .n-menu-item-content-header {
|
|
||||||
// color: #fff !important;
|
|
||||||
// font-weight: normal;
|
|
||||||
// font-size: 12px;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// .n-menu-item {
|
|
||||||
// border-top-left-radius: 5px;
|
|
||||||
// border-bottom-left-radius: 5px;
|
|
||||||
// &:hover,
|
|
||||||
// &.n-menu-item--selected::before {
|
|
||||||
// background-color: #16243a;
|
|
||||||
// right: 0;
|
|
||||||
// left: 0;
|
|
||||||
// border-right: 3px solid $primaryColor;
|
|
||||||
// background-color: unset !important;
|
|
||||||
// background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba($primaryColor, 0.3) 100%);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NLayout, NLayoutHeader, NLayoutSider } from 'naive-ui'
|
|
||||||
|
|
||||||
import AppHeader from './components/header/index.vue'
|
import AppHeader from './components/header/index.vue'
|
||||||
import SideMenu from './components/sidebar/index.vue'
|
import SideMenu from './components/sidebar/index.vue'
|
||||||
import AppMain from './components/AppMain.vue'
|
import AppMain from './components/AppMain.vue'
|
||||||
@@ -18,7 +16,7 @@ import AppMain from './components/AppMain.vue'
|
|||||||
</n-layout-header>
|
</n-layout-header>
|
||||||
<n-layout
|
<n-layout
|
||||||
position="absolute"
|
position="absolute"
|
||||||
content-style="padding: 0 35px 35px"
|
content-style="padding: 0 35px 35px;height: 100%;"
|
||||||
style="top: 100px; background-color: #f5f6fb"
|
style="top: 100px; background-color: #f5f6fb"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ export const router = createRouter({
|
|||||||
export function resetRouter() {
|
export function resetRouter() {
|
||||||
router.getRoutes().forEach((route) => {
|
router.getRoutes().forEach((route) => {
|
||||||
const { name } = route
|
const { name } = route
|
||||||
if (name && !WHITE_NAME_LIST.includes(name)) {
|
router.hasRoute(name) && router.removeRoute(name)
|
||||||
router.hasRoute(name) && router.removeRoute(name)
|
})
|
||||||
}
|
basicRoutes.forEach((route) => {
|
||||||
|
!router.hasRoute(route.name) && router.addRoute(route)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Layout from '@/layout/index.vue'
|
import Layout from '@/layout/index.vue'
|
||||||
import Dashboard from '@/views/dashboard/index.vue'
|
import Home from '@/views/dashboard/index.vue'
|
||||||
|
import { ChartBar, Dove, Github, HouseDamage, Link, TimesCircle } from '@vicons/fa'
|
||||||
|
|
||||||
export const basicRoutes = [
|
export const basicRoutes = [
|
||||||
{
|
{
|
||||||
@@ -8,12 +9,6 @@ export const basicRoutes = [
|
|||||||
component: () => import('@/views/error-page/404.vue'),
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: '401',
|
|
||||||
path: '/401',
|
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
|
||||||
isHidden: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'REDIRECT',
|
name: 'REDIRECT',
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
@@ -27,7 +22,6 @@ export const basicRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'LOGIN',
|
name: 'LOGIN',
|
||||||
path: '/login',
|
path: '/login',
|
||||||
@@ -39,20 +33,22 @@ export const basicRoutes = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'HOME',
|
name: 'DASHBOARD',
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/dashboard',
|
redirect: '/home',
|
||||||
meta: {
|
meta: {
|
||||||
title: '首页',
|
title: 'Dashboard',
|
||||||
|
icon: ChartBar,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'DASHBOARD',
|
name: 'HOME',
|
||||||
path: 'dashboard',
|
path: 'home',
|
||||||
component: Dashboard,
|
component: Home,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Dashboard',
|
title: '首页',
|
||||||
|
icon: HouseDamage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -64,7 +60,7 @@ export const basicRoutes = [
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/test/unocss',
|
redirect: '/test/unocss',
|
||||||
meta: {
|
meta: {
|
||||||
title: '测试',
|
title: '基础功能测试',
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -91,6 +87,71 @@ export const basicRoutes = [
|
|||||||
title: '测试Dialog',
|
title: '测试Dialog',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'TEST-KEEP-ALIVE',
|
||||||
|
path: 'keep-alive',
|
||||||
|
component: () => import('@/views/test-page/TestKeepAlive.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '测试Keep-Alive',
|
||||||
|
keepAlive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'ERROR-PAGE',
|
||||||
|
path: '/error-page',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/error-page/404',
|
||||||
|
meta: {
|
||||||
|
title: '错误页',
|
||||||
|
icon: TimesCircle,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'ERROR-404',
|
||||||
|
path: '404',
|
||||||
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '404',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'EXTERNAL-LINK',
|
||||||
|
path: '/external-link',
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
title: '外部链接',
|
||||||
|
icon: Link,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'LINK-GITHUB-SRC',
|
||||||
|
path: 'https://github.com/zclzone/vue-naive-admin',
|
||||||
|
meta: {
|
||||||
|
title: '源码 - github',
|
||||||
|
icon: 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: '文档 - 语雀',
|
||||||
|
icon: Dove,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
46
src/router/routes/modules/example.js
Normal file
46
src/router/routes/modules/example.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import Layout from '@/layout/index.vue'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'EXAMPLE',
|
||||||
|
path: '/example',
|
||||||
|
component: Layout,
|
||||||
|
redirect: '/example/table',
|
||||||
|
meta: {
|
||||||
|
title: '组件示例',
|
||||||
|
role: ['admin'],
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'EXAMPLE-TABLE',
|
||||||
|
path: 'table',
|
||||||
|
component: () => import('@/views/examples/table/index.vue'),
|
||||||
|
redirect: '/example/table/post',
|
||||||
|
meta: {
|
||||||
|
title: '表格',
|
||||||
|
role: ['admin'],
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'POST-LIST',
|
||||||
|
path: 'post',
|
||||||
|
component: () => import('@/views/examples/table/post/index.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '文章列表',
|
||||||
|
role: ['admin'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'POST-CREATE',
|
||||||
|
path: 'post-create',
|
||||||
|
component: () => import('@/views/examples/table/post/post-create.vue'),
|
||||||
|
meta: {
|
||||||
|
title: '创建文章',
|
||||||
|
role: ['admin'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import Layout from '@/layout/index.vue'
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
name: 'USER_MANAGER',
|
|
||||||
path: '/user',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/user/management',
|
|
||||||
meta: {
|
|
||||||
title: '用户中心',
|
|
||||||
role: ['admin'],
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'USER',
|
|
||||||
path: 'management',
|
|
||||||
component: () => import('@/views/user/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: '用户管理',
|
|
||||||
role: ['admin'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'PERMISSION',
|
|
||||||
path: 'permission',
|
|
||||||
component: () => import('@/views/user/UserPermission.vue'),
|
|
||||||
meta: {
|
|
||||||
title: '权限管理',
|
|
||||||
role: ['admin'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -42,5 +42,8 @@ export const useUserStore = defineStore('user', {
|
|||||||
removeToken()
|
removeToken()
|
||||||
this.userInfo = {}
|
this.userInfo = {}
|
||||||
},
|
},
|
||||||
|
setUserInfo(userInfo = {}) {
|
||||||
|
this.userInfo = { ...this.userInfo, ...userInfo }
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -97,6 +97,14 @@ export function isUrl(path) {
|
|||||||
return reg.test(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 isServer = typeof window === 'undefined'
|
||||||
|
|
||||||
export const isClient = !isServer
|
export const isClient = !isServer
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<h1>401</h1>
|
|
||||||
</template>
|
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
const { replace } = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1>404</h1>
|
<div class="page-404">
|
||||||
|
<n-result status="404" description="抱歉,您访问的页面不存在。">
|
||||||
|
<template #icon>
|
||||||
|
<img src="@/assets/imgs/404/404.png" width="500" />
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<n-button @click="replace('/')">返回首页</n-button>
|
||||||
|
</template>
|
||||||
|
</n-result>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-404 {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
3
src/views/examples/table/index.vue
Normal file
3
src/views/examples/table/index.vue
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<router-view></router-view>
|
||||||
|
</template>
|
||||||
98
src/views/examples/table/post/index.vue
Normal file
98
src/views/examples/table/post/index.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="action-btns">
|
||||||
|
<n-button size="small" type="primary" @click="handleCreate">新建文章</n-button>
|
||||||
|
</div>
|
||||||
|
<n-data-table
|
||||||
|
mt-20
|
||||||
|
:loading="loading"
|
||||||
|
:scroll-x="1600"
|
||||||
|
:data="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:pagination="pagination"
|
||||||
|
:row-key="(row) => row.id"
|
||||||
|
max-height="calc(100vh - 260px)"
|
||||||
|
@update:checked-row-keys="handleCheck"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onBeforeMount } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { getTableData, createColumns } from './post-table'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 静态变量
|
||||||
|
const columns = createColumns({
|
||||||
|
handleDelete,
|
||||||
|
handleRecommend,
|
||||||
|
handlePublish,
|
||||||
|
})
|
||||||
|
|
||||||
|
// refs
|
||||||
|
let tableData = ref([])
|
||||||
|
let pagination = ref({ pageSize: 10 })
|
||||||
|
let loading = ref(false)
|
||||||
|
|
||||||
|
// 钩子函数
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initTableData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// fns
|
||||||
|
async function initTableData() {
|
||||||
|
loading.value = true
|
||||||
|
tableData.value = await getTableData()
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreate() {
|
||||||
|
router.push('/example/table/post-create')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(row) {
|
||||||
|
if (row && row.id) {
|
||||||
|
$dialog.confirm({
|
||||||
|
content: '确定删除?',
|
||||||
|
confirm() {
|
||||||
|
$message.success('删除成功')
|
||||||
|
initTableData()
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
$message.success('已取消')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRecommend(row) {
|
||||||
|
if (row && row.id) {
|
||||||
|
row.recommending = true
|
||||||
|
setTimeout(() => {
|
||||||
|
$message.success(row.isRecommend ? '已取消推荐' : '已推荐')
|
||||||
|
row.recommending = false
|
||||||
|
}, 800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePublish(row) {
|
||||||
|
if (row && row.id) {
|
||||||
|
row.publishing = true
|
||||||
|
setTimeout(() => {
|
||||||
|
$message.success(row.isPublish ? '已取消推荐' : '已推荐')
|
||||||
|
row.publishing = false
|
||||||
|
}, 800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheck(rowKeys) {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.action-btns {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
60
src/views/examples/table/post/post-create.vue
Normal file
60
src/views/examples/table/post/post-create.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<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 - 180px)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import MdEditor from 'md-editor-v3'
|
||||||
|
import 'md-editor-v3/lib/style.css'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// refs
|
||||||
|
let post = ref({})
|
||||||
|
let btnLoading = ref(false)
|
||||||
|
|
||||||
|
function handleSavePost(e) {
|
||||||
|
btnLoading.value = true
|
||||||
|
$message.loading('正在保存...')
|
||||||
|
setTimeout(() => {
|
||||||
|
$message.success('保存成功')
|
||||||
|
btnLoading.value = false
|
||||||
|
router.push('/example/table/post')
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.md-preview {
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
list-style: revert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.header {
|
||||||
|
background-color: #fff;
|
||||||
|
height: 60px;
|
||||||
|
padding: 0 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
padding: 15px 0;
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $primaryColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
99
src/views/examples/table/post/post-table.js
Normal file
99
src/views/examples/table/post/post-table.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { h } from 'vue'
|
||||||
|
import { NButton, NSwitch } from 'naive-ui'
|
||||||
|
import { getPosts } from '@/api/post'
|
||||||
|
import { formatDateTime } from '@/utils'
|
||||||
|
|
||||||
|
export async function getTableData() {
|
||||||
|
try {
|
||||||
|
const res = await getPosts()
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
console.warn(res.message)
|
||||||
|
return []
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createColumns({ handleDelete, handleRecommend, handlePublish }) {
|
||||||
|
return [
|
||||||
|
{ type: 'selection' },
|
||||||
|
{ title: '标题', key: 'title', width: 150 },
|
||||||
|
{ title: '分类', key: 'category', width: 80 },
|
||||||
|
{
|
||||||
|
title: '描述',
|
||||||
|
key: 'description',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{ title: '创建人', key: 'author', width: 80 },
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createDate',
|
||||||
|
width: 150,
|
||||||
|
render(row) {
|
||||||
|
return h('span', formatDateTime(row['createDate']))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最后更新时间',
|
||||||
|
key: 'updateDate',
|
||||||
|
width: 150,
|
||||||
|
render(row) {
|
||||||
|
return h('span', formatDateTime(row['updateDate']))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '推荐',
|
||||||
|
key: 'isRecommend',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h(NSwitch, {
|
||||||
|
size: 'small',
|
||||||
|
defaultValue: row['isRecommend'],
|
||||||
|
loading: !!row.recommending,
|
||||||
|
onUpdateValue: () => handleRecommend(row),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '发布',
|
||||||
|
key: 'isPublish',
|
||||||
|
width: 100,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return h(NSwitch, {
|
||||||
|
size: 'small',
|
||||||
|
defaultValue: row['isPublish'],
|
||||||
|
loading: !!row.publishing,
|
||||||
|
onUpdateValue: () => handlePublish(row),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 120,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
render(row) {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
style: 'margin-left: 15px;',
|
||||||
|
onClick: () => handleDelete(row),
|
||||||
|
},
|
||||||
|
{ default: () => '删除' }
|
||||||
|
),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -38,7 +38,9 @@ async function handleLogin() {
|
|||||||
setToken(res.data.token)
|
setToken(res.data.token)
|
||||||
|
|
||||||
if (query.redirect) {
|
if (query.redirect) {
|
||||||
router.push({ path: '/redirect', query })
|
const path = query.redirect
|
||||||
|
Reflect.deleteProperty(query, 'redirect')
|
||||||
|
router.push({ path, query })
|
||||||
} else {
|
} else {
|
||||||
router.push('/')
|
router.push('/')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup name="TestDialog">
|
<script setup name="TestDialog">
|
||||||
import { NButton } from 'naive-ui'
|
|
||||||
const handleDelete = function () {
|
const handleDelete = function () {
|
||||||
$dialog.confirm({
|
$dialog.confirm({
|
||||||
content: '确认删除?',
|
content: '确认删除?',
|
||||||
|
|||||||
20
src/views/test-page/TestKeepAlive.vue
Normal file
20
src/views/test-page/TestKeepAlive.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!--使用keep-alive须设置name,注意请与对应的路由的name保持一致,方便统一处理-->
|
||||||
|
<script setup name="TEST-KEEP-ALIVE">
|
||||||
|
import { onMounted, onActivated, onDeactivated } from 'vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$message.success('触发onMounted')
|
||||||
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
$message.success('触发onActivated')
|
||||||
|
})
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
$message.success('触发onDeactivated')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-gradient-text gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)"> 注意查看提示语 </n-gradient-text>
|
||||||
|
</template>
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { NButton } from 'naive-ui'
|
|
||||||
|
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
$message.loading('登陆中...')
|
$message.loading('登陆中...')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -11,39 +11,6 @@
|
|||||||
<p text-19>测试19px</p>
|
<p text-19>测试19px</p>
|
||||||
<p text-20>测试20px</p>
|
<p text-20>测试20px</p>
|
||||||
</div>
|
</div>
|
||||||
<div mt-30 class="content-box">
|
|
||||||
<p text-12>测试12px</p>
|
|
||||||
<p text-13>测试13px</p>
|
|
||||||
<p text-14>测试14px</p>
|
|
||||||
<p text-15>测试15px</p>
|
|
||||||
<p text-16>测试16px</p>
|
|
||||||
<p text-17>测试17px</p>
|
|
||||||
<p text-18>测试18px</p>
|
|
||||||
<p text-19>测试19px</p>
|
|
||||||
<p text-20>测试20px</p>
|
|
||||||
</div>
|
|
||||||
<div mt-30 class="content-box">
|
|
||||||
<p text-12>测试12px</p>
|
|
||||||
<p text-13>测试13px</p>
|
|
||||||
<p text-14>测试14px</p>
|
|
||||||
<p text-15>测试15px</p>
|
|
||||||
<p text-16>测试16px</p>
|
|
||||||
<p text-17>测试17px</p>
|
|
||||||
<p text-18>测试18px</p>
|
|
||||||
<p text-19>测试19px</p>
|
|
||||||
<p text-20>测试20px</p>
|
|
||||||
</div>
|
|
||||||
<div mt-30 class="content-box">
|
|
||||||
<p text-12>测试12px</p>
|
|
||||||
<p text-13>测试13px</p>
|
|
||||||
<p text-14>测试14px</p>
|
|
||||||
<p text-15>测试15px</p>
|
|
||||||
<p text-16>测试16px</p>
|
|
||||||
<p text-17>测试17px</p>
|
|
||||||
<p text-18>测试18px</p>
|
|
||||||
<p text-19>测试19px</p>
|
|
||||||
<p text-20>测试20px</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<h1>权限管理</h1>
|
|
||||||
</template>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<script setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>用户管理</h1>
|
|
||||||
</template>
|
|
||||||
Reference in New Issue
Block a user