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

13 Commits

Author SHA1 Message Date
zhangchuanlong
606334ffad perf: 修改接口请求的axios实例别名 2022-03-14 16:36:21 +08:00
zhangchuanlong
a44be5aec0 mod: 删除多余文件 2022-03-14 16:34:38 +08:00
zhangchuanlong
28f0815211 mod: 调整菜单图标和dashboard路由结构 2022-03-13 14:41:02 +08:00
zhangchuanlong
2f592f9570 fix:修复登陆成功后重定向页面空白问题 2022-03-09 12:37:18 +08:00
zhangchuanlong
d0c5a805d8 mod:删除重复的vscode推荐插件 2022-03-07 12:25:25 +08:00
zhangchuanlong
4929bf7d03 feat:菜单栏添加icon支持 2022-03-07 12:24:31 +08:00
zhangchuanlong
5fcc47816c feat:添加组件实例 2022-03-07 12:18:42 +08:00
zhangchuanlong
8bcbcac531 fix:修复keep-alive失效问题,并添加keep-alive测试页面 2022-03-04 16:44:33 +08:00
zhangchuanlong
402c7db7ba mod:404页面引入的Naive组件改为自动引入 2022-02-25 14:16:30 +08:00
zhangchuanlong
d955f55f7c Merge branch 'main' of https://gitee.com/zclzone/vue-naive-admin 2022-02-25 14:15:08 +08:00
zhangchuanlong
b5fde56177 mod: 美化404页面 2022-02-25 14:08:17 +08:00
zhangchuanlong
c761501855 mod: 更新npm依赖包 2022-02-25 14:06:50 +08:00
zhangchuanlong
8e04bc7821 mod: 去除unocss测试页多余内容 2022-02-25 14:05:44 +08:00
26 changed files with 921 additions and 202 deletions

View File

@@ -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"
] ]
} }

393
mock/post/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -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,12 +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",
"unplugin-vue-components": "^0.17.18", "unplugin-vue-components": "^0.17.18",
"vite": "^2.7.6", "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"

34
pnpm-lock.yaml generated
View File

@@ -1,12 +1,12 @@
lockfileVersion: 5.3 lockfileVersion: 5.3
specifiers: specifiers:
'@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
'@vicons/fa': ^0.11.0 '@vicons/fa': ^0.11.0
'@vitejs/plugin-vue': ^1.6.0 '@vitejs/plugin-vue': ^1.10.2
'@vue/compiler-sfc': ^3.0.5 '@vue/compiler-sfc': ^3.2.30
axios: ^0.21.4 axios: ^0.21.4
chalk: ^5.0.0 chalk: ^5.0.0
dayjs: ^1.10.7 dayjs: ^1.10.7
@@ -17,28 +17,28 @@ specifiers:
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
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
naive-ui: ^2.19.1 naive-ui: ^2.25.2
pinia: ^2.0.9 pinia: ^2.0.11
prettier: ^2.5.1 prettier: ^2.5.1
sass: ^1.38.1 sass: ^1.38.1
unocss: ^0.16.3 unocss: ^0.16.4
unplugin-vue-components: ^0.17.18 unplugin-vue-components: ^0.17.18
vite: ^2.7.6 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
vue: ^3.2.6 vue: ^3.2.30
vue-router: ^4.0.12 vue-router: ^4.0.12
dependencies: dependencies:
'@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.11_vue@3.2.30 pinia: 2.0.11_vue@3.2.30
vue: 3.2.30 vue: 3.2.30
@@ -1554,11 +1554,6 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/js-cookie/3.0.1:
resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
engines: {node: '>=12'}
dev: false
/js-yaml/4.1.0: /js-yaml/4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true hasBin: true
@@ -1635,6 +1630,11 @@ packages:
dependencies: dependencies:
sourcemap-codec: 1.4.8 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: /merge-stream/2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true dev: true

View File

@@ -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">

View File

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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -9,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>

View File

@@ -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>

View File

@@ -1,13 +1,15 @@
<script setup> <script setup>
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' import { isExternal } from '@/utils/is'
const router = useRouter() const router = useRouter()
const permissionStore = usePermissionStore() const permissionStore = usePermissionStore()
const { currentRoute } = router const { currentRoute } = router
const menuOptions = computed(() => { 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) { function generateOptions(routes, basePath) {
let options = [] let options = []
routes.forEach((route) => { routes.forEach((route) => {
@@ -35,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)
} }
@@ -67,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"
@@ -111,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>

View File

@@ -16,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"
> >

View File

@@ -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 = [
{ {
@@ -32,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,
}, },
}, },
], ],
@@ -57,7 +60,7 @@ export const basicRoutes = [
component: Layout, component: Layout,
redirect: '/test/unocss', redirect: '/test/unocss',
meta: { meta: {
title: '测试', title: '基础功能测试',
}, },
children: [ children: [
{ {
@@ -84,6 +87,36 @@ 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',
},
},
], ],
}, },
@@ -92,7 +125,8 @@ export const basicRoutes = [
path: '/external-link', path: '/external-link',
component: Layout, component: Layout,
meta: { meta: {
title: '外', title: '外部链接',
icon: Link,
}, },
children: [ children: [
{ {
@@ -100,6 +134,7 @@ export const basicRoutes = [
path: 'https://github.com/zclzone/vue-naive-admin', path: 'https://github.com/zclzone/vue-naive-admin',
meta: { meta: {
title: '源码 - github', title: '源码 - github',
icon: Github,
}, },
}, },
{ {
@@ -114,6 +149,7 @@ export const basicRoutes = [
path: 'https://www.yuque.com/qszone/vue-naive-admin', path: 'https://www.yuque.com/qszone/vue-naive-admin',
meta: { meta: {
title: '文档 - 语雀', title: '文档 - 语雀',
icon: Dove,
}, },
}, },
], ],

View 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'],
},
},
],
},
],
},
]

View File

@@ -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'],
},
},
],
},
]

View File

@@ -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>

View File

@@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>

View 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>

View 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>

View 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: () => '删除' }
),
]
},
},
]
}

View File

@@ -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('/')
} }

View 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>

View File

@@ -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>

View File

@@ -1,3 +0,0 @@
<template>
<h1>权限管理</h1>
</template>

View File

@@ -1,5 +0,0 @@
<script setup></script>
<template>
<h1>用户管理</h1>
</template>