mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 20:10:22 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51a583fc1e | ||
|
|
396428104a | ||
|
|
ff2c25ff75 | ||
|
|
4f78bcb77c | ||
|
|
f62f59720d | ||
|
|
f296490569 | ||
|
|
606334ffad | ||
|
|
a44be5aec0 | ||
|
|
28f0815211 | ||
|
|
2f592f9570 | ||
|
|
d0c5a805d8 | ||
|
|
4929bf7d03 | ||
|
|
5fcc47816c | ||
|
|
8bcbcac531 | ||
|
|
402c7db7ba | ||
|
|
d955f55f7c | ||
|
|
b5fde56177 | ||
|
|
c761501855 | ||
|
|
8e04bc7821 |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -6,7 +6,6 @@
|
||||
"esbenp.prettier-vscode",
|
||||
"mikestead.dotenv",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"aaron-bond.better-comments"
|
||||
]
|
||||
}
|
||||
|
||||
17
README.md
17
README.md
@@ -76,3 +76,20 @@ npm run lint:fix
|
||||
# 预览发布包效果(需先执行构建指令)
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## Git 提交规范
|
||||
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `workflow` 工作流改进
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
- `mod` 不确定分类的修改
|
||||
|
||||
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
22
package.json
22
package.json
@@ -14,19 +14,19 @@
|
||||
"@vicons/fa": "^0.11.0",
|
||||
"axios": "^0.21.4",
|
||||
"dayjs": "^1.10.7",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md-editor-v3": "^1.10.2",
|
||||
"mockjs": "^1.1.0",
|
||||
"pinia": "^2.0.9",
|
||||
"vue": "^3.2.6",
|
||||
"pinia": "^2.0.11",
|
||||
"vue": "^3.2.30",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unocss/preset-attributify": "^0.16.3",
|
||||
"@unocss/preset-icons": "^0.16.3",
|
||||
"@unocss/preset-uno": "^0.16.3",
|
||||
"@vitejs/plugin-vue": "^1.6.0",
|
||||
"@vue/compiler-sfc": "^3.0.5",
|
||||
"@unocss/preset-attributify": "^0.16.4",
|
||||
"@unocss/preset-icons": "^0.16.4",
|
||||
"@unocss/preset-uno": "^0.16.4",
|
||||
"@vitejs/plugin-vue": "^1.10.2",
|
||||
"@vue/compiler-sfc": "^3.2.30",
|
||||
"chalk": "^5.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^8.6.0",
|
||||
@@ -35,12 +35,12 @@
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"esno": "^0.13.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"naive-ui": "^2.19.1",
|
||||
"naive-ui": "^2.25.2",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "^1.38.1",
|
||||
"unocss": "^0.16.3",
|
||||
"unocss": "^0.16.4",
|
||||
"unplugin-vue-components": "^0.17.18",
|
||||
"vite": "^2.7.6",
|
||||
"vite": "^2.8.0",
|
||||
"vite-plugin-html": "^2.1.1",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-vue-setup-extend": "^0.3.0"
|
||||
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -1,12 +1,12 @@
|
||||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@unocss/preset-attributify': ^0.16.3
|
||||
'@unocss/preset-icons': ^0.16.3
|
||||
'@unocss/preset-uno': ^0.16.3
|
||||
'@unocss/preset-attributify': ^0.16.4
|
||||
'@unocss/preset-icons': ^0.16.4
|
||||
'@unocss/preset-uno': ^0.16.4
|
||||
'@vicons/fa': ^0.11.0
|
||||
'@vitejs/plugin-vue': ^1.6.0
|
||||
'@vue/compiler-sfc': ^3.0.5
|
||||
'@vitejs/plugin-vue': ^1.10.2
|
||||
'@vue/compiler-sfc': ^3.2.30
|
||||
axios: ^0.21.4
|
||||
chalk: ^5.0.0
|
||||
dayjs: ^1.10.7
|
||||
@@ -17,28 +17,28 @@ specifiers:
|
||||
eslint-plugin-vue: ^8.2.0
|
||||
esno: ^0.13.0
|
||||
fs-extra: ^10.0.0
|
||||
js-cookie: ^3.0.1
|
||||
lodash-es: ^4.17.21
|
||||
md-editor-v3: ^1.10.2
|
||||
mockjs: ^1.1.0
|
||||
naive-ui: ^2.19.1
|
||||
pinia: ^2.0.9
|
||||
naive-ui: ^2.25.2
|
||||
pinia: ^2.0.11
|
||||
prettier: ^2.5.1
|
||||
sass: ^1.38.1
|
||||
unocss: ^0.16.3
|
||||
unocss: ^0.16.4
|
||||
unplugin-vue-components: ^0.17.18
|
||||
vite: ^2.7.6
|
||||
vite: ^2.8.0
|
||||
vite-plugin-html: ^2.1.1
|
||||
vite-plugin-mock: ^2.9.6
|
||||
vite-plugin-vue-setup-extend: ^0.3.0
|
||||
vue: ^3.2.6
|
||||
vue: ^3.2.30
|
||||
vue-router: ^4.0.12
|
||||
|
||||
dependencies:
|
||||
'@vicons/fa': 0.11.0
|
||||
axios: 0.21.4
|
||||
dayjs: 1.10.7
|
||||
js-cookie: 3.0.1
|
||||
lodash-es: 4.17.21
|
||||
md-editor-v3: 1.10.2
|
||||
mockjs: 1.1.0
|
||||
pinia: 2.0.11_vue@3.2.30
|
||||
vue: 3.2.30
|
||||
@@ -1554,11 +1554,6 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/js-cookie/3.0.1:
|
||||
resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/js-yaml/4.1.0:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
hasBin: true
|
||||
@@ -1635,6 +1630,11 @@ packages:
|
||||
dependencies:
|
||||
sourcemap-codec: 1.4.8
|
||||
|
||||
/md-editor-v3/1.10.2:
|
||||
resolution: {integrity: sha512-wuC3mQuWKUcKkEnND9Mzn5aDvJx/vTEJaRZcHXJL1v7WkQy+TiFkhSVKorSN/W/3jPd5H6Q+txorbK7eOsAcTQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dev: false
|
||||
|
||||
/merge-stream/2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
||||
11
src/App.vue
11
src/App.vue
@@ -3,16 +3,11 @@ import AppProvider from '@/components/AppProvider/index.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<app-provider>
|
||||
<keep-alive v-if="route.meta && route.meta.keepAlive">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else :key="route.fullPath" />
|
||||
</app-provider>
|
||||
</template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</app-provider>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defAxios } from '@/utils/http'
|
||||
import { defAxios as request } from '@/utils/http'
|
||||
|
||||
export const login = (data) => {
|
||||
return defAxios({
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
method: 'post',
|
||||
data,
|
||||
@@ -9,7 +9,7 @@ export const login = (data) => {
|
||||
}
|
||||
|
||||
export const refreshToken = () => {
|
||||
return defAxios({
|
||||
return request({
|
||||
url: '/auth/refreshToken',
|
||||
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 = {}) {
|
||||
return defAxios({
|
||||
return request({
|
||||
url: '/users',
|
||||
method: 'get',
|
||||
data,
|
||||
@@ -10,12 +10,12 @@ export function getUsers(data = {}) {
|
||||
|
||||
export function getUser(id) {
|
||||
if (id) {
|
||||
return defAxios({
|
||||
return request({
|
||||
url: `/user/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
return defAxios({
|
||||
return request({
|
||||
url: '/user',
|
||||
method: 'get',
|
||||
})
|
||||
@@ -23,14 +23,14 @@ export function getUser(id) {
|
||||
|
||||
export function saveUser(data = {}, id) {
|
||||
if (id) {
|
||||
return defAxios({
|
||||
return request({
|
||||
url: '/user',
|
||||
method: 'put',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
return defAxios({
|
||||
return request({
|
||||
url: `/user/${id}`,
|
||||
method: 'put',
|
||||
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 |
@@ -9,14 +9,13 @@ const appStore = useAppStore()
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme-overrides="appStore.themeOverrides">
|
||||
<n-global-style />
|
||||
<n-loading-bar-provider>
|
||||
<loading-bar />
|
||||
<n-dialog-provider>
|
||||
<dialog-content />
|
||||
<n-message-provider>
|
||||
<message-content />
|
||||
<slot name="default"></slot>
|
||||
<slot></slot>
|
||||
</n-message-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
<template>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade-slide" mode="out-in" appear>
|
||||
<component :is="Component" :key="route.fullPath"></component>
|
||||
<keep-alive :include="keepAliveRouteNames">
|
||||
<component :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</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>
|
||||
|
||||
@@ -12,7 +12,7 @@ import HeaderAction from './HeaderAction.vue'
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
padding: 0 35px;
|
||||
padding: 0 24px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { computed, h } from 'vue'
|
||||
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 permissionStore = usePermissionStore()
|
||||
|
||||
const { currentRoute } = router
|
||||
|
||||
const menuOptions = computed(() => {
|
||||
@@ -25,6 +27,25 @@ function resolvePath(basePath, path) {
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
let options = []
|
||||
routes.forEach((route) => {
|
||||
@@ -35,13 +56,10 @@ function generateOptions(routes, basePath) {
|
||||
path: resolvePath(basePath, route.path),
|
||||
}
|
||||
if (route.children && route.children.length) {
|
||||
curOption.icon = renderIcon(route.meta?.icon || ListAlt, { size: 16 })
|
||||
curOption.children = generateOptions(route.children, resolvePath(basePath, route.path))
|
||||
}
|
||||
if (curOption.children && curOption.children.length <= 1) {
|
||||
if (curOption.children.length === 1) {
|
||||
curOption = { ...curOption.children[0] }
|
||||
}
|
||||
delete curOption.children
|
||||
} else {
|
||||
curOption.icon = (route.meta?.icon && renderIcon(route.meta?.icon)) || renderIcon(CircleRegular, { size: 8 })
|
||||
}
|
||||
options.push(curOption)
|
||||
}
|
||||
@@ -67,7 +85,9 @@ function handleMenuSelect(key, item) {
|
||||
<template>
|
||||
<n-menu
|
||||
class="side-menu"
|
||||
:root-indent="20"
|
||||
accordion
|
||||
:indent="12"
|
||||
:root-indent="12"
|
||||
:options="menuOptions"
|
||||
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
||||
@update:value="handleMenuSelect"
|
||||
@@ -111,55 +131,7 @@ function handleMenuSelect(key, item) {
|
||||
font-weight: normal;
|
||||
position: relative;
|
||||
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>
|
||||
|
||||
@@ -11,18 +11,22 @@ import AppMain from './components/AppMain.vue'
|
||||
<side-menu />
|
||||
</n-layout-sider>
|
||||
<n-layout>
|
||||
<n-layout-header style="height: 100px; background-color: #f5f6fb">
|
||||
<n-layout-header>
|
||||
<app-header />
|
||||
</n-layout-header>
|
||||
<n-layout
|
||||
position="absolute"
|
||||
content-style="padding: 0 35px 35px"
|
||||
style="top: 100px; background-color: #f5f6fb"
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<n-layout position="absolute" style="top: 60px; background-color: #f5f6fb" :native-scrollbar="false">
|
||||
<app-main />
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.n-layout-header {
|
||||
height: 60px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-left: 1px solid #eee;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createPageLoadingGuard } from './pageLoadingGuard'
|
||||
import { createPageTitleGuard } from './pageTitleGuard'
|
||||
import { createPermissionGuard } from './permissionGuard'
|
||||
|
||||
export function setupRouterGuard(router) {
|
||||
createPageLoadingGuard(router)
|
||||
createPermissionGuard(router)
|
||||
createPageTitleGuard(router)
|
||||
}
|
||||
|
||||
12
src/router/guard/pageTitleGuard.js
Normal file
12
src/router/guard/pageTitleGuard.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const baseTitle = import.meta.env.VITE_APP_TITLE
|
||||
|
||||
export function createPageTitleGuard(router) {
|
||||
router.afterEach((to) => {
|
||||
const pageTitle = to.meta?.title
|
||||
if (pageTitle) {
|
||||
document.title = `${pageTitle} | ${baseTitle}`
|
||||
} else {
|
||||
document.title = baseTitle
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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 = [
|
||||
{
|
||||
@@ -32,20 +33,22 @@ export const basicRoutes = [
|
||||
},
|
||||
|
||||
{
|
||||
name: 'HOME',
|
||||
name: 'DASHBOARD',
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/dashboard',
|
||||
redirect: '/home',
|
||||
meta: {
|
||||
title: '首页',
|
||||
title: 'Dashboard',
|
||||
icon: ChartBar,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'DASHBOARD',
|
||||
path: 'dashboard',
|
||||
component: Dashboard,
|
||||
name: 'HOME',
|
||||
path: 'home',
|
||||
component: Home,
|
||||
meta: {
|
||||
title: 'Dashboard',
|
||||
title: '首页',
|
||||
icon: HouseDamage,
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -57,7 +60,7 @@ export const basicRoutes = [
|
||||
component: Layout,
|
||||
redirect: '/test/unocss',
|
||||
meta: {
|
||||
title: '测试',
|
||||
title: '基础功能测试',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -84,6 +87,36 @@ export const basicRoutes = [
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -92,7 +125,8 @@ export const basicRoutes = [
|
||||
path: '/external-link',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '外链',
|
||||
title: '外部链接',
|
||||
icon: Link,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -100,6 +134,7 @@ export const basicRoutes = [
|
||||
path: 'https://github.com/zclzone/vue-naive-admin',
|
||||
meta: {
|
||||
title: '源码 - github',
|
||||
icon: Github,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -114,6 +149,7 @@ export const basicRoutes = [
|
||||
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'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -1,3 +1,68 @@
|
||||
<template>
|
||||
<h1>首页</h1>
|
||||
<div>
|
||||
<n-card>
|
||||
<div flex items-center>
|
||||
<img width="60" style="border-radius: 50%" :src="userStore.avatar" />
|
||||
<div ml20>
|
||||
<p text-16>Hello, {{ userStore.name }}</p>
|
||||
<p op80 text-12 mt5>今天又是元气满满的一天</p>
|
||||
</div>
|
||||
<div flex ml-auto>
|
||||
<n-statistic label="待办" :value="4">
|
||||
<template #suffix> / 10 </template>
|
||||
</n-statistic>
|
||||
<n-statistic ml80 label="Stars">
|
||||
<n-number-animation ref="starsNumberRef" show-separator :from="0" :to="999" />
|
||||
</n-statistic>
|
||||
<n-statistic ml80 label="Forks">
|
||||
<n-number-animation ref="starsNumberRef" show-separator :from="0" :to="299" />
|
||||
</n-statistic>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<div p15 flex>
|
||||
<n-card title="项目" size="small" :segmented="true">
|
||||
<template #header-extra>
|
||||
<n-button text type="primary">更多</n-button>
|
||||
</template>
|
||||
<div class="card-list">
|
||||
<n-card v-for="i in 10" :key="i" title="Vue Naive Admin" size="small">
|
||||
<p op60>一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理模板</p>
|
||||
</n-card>
|
||||
<div class="blank"></div>
|
||||
<div class="blank"></div>
|
||||
<div class="blank"></div>
|
||||
<div class="blank"></div>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
.n-card {
|
||||
width: 300px;
|
||||
flex-shrink: 0;
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: 0 1px 2px -2px #00000029, 0 3px 6px #0000001f, 0 5px 12px 4px #00000017;
|
||||
}
|
||||
}
|
||||
.blank {
|
||||
width: 300px;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
const { replace } = useRouter()
|
||||
</script>
|
||||
|
||||
<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 color="#002d6f" @click="replace('/')">返回首页</n-button>
|
||||
</template>
|
||||
</n-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-404 {
|
||||
height: 100%;
|
||||
min-height: calc(100vh - 60px);
|
||||
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 p24>
|
||||
<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 - 250px)"
|
||||
@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 - 140px)" />
|
||||
</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)
|
||||
|
||||
if (query.redirect) {
|
||||
router.push({ path: '/redirect', query })
|
||||
const path = query.redirect
|
||||
Reflect.deleteProperty(query, 'redirect')
|
||||
router.push({ path, query })
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
@@ -13,5 +13,7 @@ const handleDelete = function () {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-button @click="handleDelete">删除</n-button>
|
||||
<div p24>
|
||||
<n-button type="error" @click="handleDelete">删除</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
22
src/views/test-page/TestKeepAlive.vue
Normal file
22
src/views/test-page/TestKeepAlive.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<!--使用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>
|
||||
<div p24>
|
||||
<n-gradient-text gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)"> 注意查看提示语 </n-gradient-text>
|
||||
</div>
|
||||
</template>
|
||||
@@ -12,5 +12,7 @@ function handleLogin() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-button @click="handleLogin">点击登陆</n-button>
|
||||
<div p24>
|
||||
<n-button type="primary" @click="handleLogin">点击登陆</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div 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 mt-30 class="content-box">
|
||||
<div p24>
|
||||
<div p20 bg="#fff">
|
||||
<p text-12>测试12px</p>
|
||||
<p text-13>测试13px</p>
|
||||
<p text-14>测试14px</p>
|
||||
@@ -46,11 +13,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content-box {
|
||||
background-color: #fff;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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