Vue3+TypeScript+Django Rest Framework 搭建个人博客(三):博客管理后台(3)

一个完整的网站都是有前台和管理后台组成的,前台用来给真正的用户浏览和使用,后台用来给管理员管理网站内容,配置各种功能和数据等。博客的管理后台就是用来承载创建博客,发布博客,查看留言,管理博客用户这些功能的子系统。

大家好,我是落霞孤鹜,上一篇我们已经实现了管理后台的前端部分页面,这一章我们继续搭建博客的管理后台的前端,实现对博客网站的管理功能。

一、前端开发

1.4 分类和文章管理

文章和分类是关系比较密切的两个业务对象,因此这里把分类管理的功能和文章管理的功能放在同一个页面处理。

1.4.1 Type

src/types/index.ts文件中增加代码如下:

export interface Catalog {
    id: number,
    name: string,
    parent: number,
    parents: Array,
    children: Array

}

export interface Article {
    id: number,
    title: string,
    cover: string,
    toc: string,
    excerpt: string,
    markdown: string,
    html: string,
    create_at: string,
    views: number,
    likes: number,
    comments: number,
    words: number,
    tags: Array | any,
    tags_info: Array | any
    catalog: number,
    catalog_info: Catalog,
    created_at: string,
    modified_at: string,
    author: string,
    status?: string,
}

export interface ArticleArray {
    count: number,
    results: Array
| any } export interface ArticleParams { title: string | any, status: string | any, tags: Array | any, catalog: number | any, page: number, page_size: number, }

1.4.2 API

这里要编写标签管理相关的接口,列表查询、新增、修改、删除。在src/api/service.ts编写如下代码:

export function getCatalogTree() {
    return request({
        url: '/catalog/',
        method: 'get',
    }) as unknown as Array
}

export function saveCatalog(method: string, data: Catalog) {
    let url = '/catalog/'
    if (['put', 'patch'].includes(method)) {
        url += data.id + '/'
    }
    // @ts-ignore
    return request({
        url,
        method,
        data,
    }) as unknown as ResponseData

}

export function deleteCatalog(catalogId: number) {

    return request({
        url: '/catalog/' + catalogId + '/',
        method: 'delete',
    }) as unknown as ResponseData

}

export function getArticleList(params: ArticleParams) {
    return request({
        url: '/list/',
        method: 'get',
        params
    }) as unknown as ArticleArray
}

export function remoteDeleteArticle(articleId: number) {
    return request({
        url: '/article/' + articleId + '/',
        method: 'delete',
    }) as unknown as ResponseData
}

export function getArticleDetail(articleId: number) {
    return request({
        url: '/article/' + articleId + '/',
        method: 'get',
    }) as unknown as Article
}

export function remoteSaveArticle(method: string, data: Article) {
    let url = '/article/'
    if (['put', 'patch'].includes(method)) {
        url += data.id + '/'
    }
    // @ts-ignore
    return request({
        url,
        method,
        data,
    }) as unknown as Article
}

export function remotePublishArticle(articleId: number) {

    // @ts-ignore
    return request({
        url: '/publish/' + articleId + '/',
        method: 'patch',
    }) as unknown as Article
}

export function remoteOfflineArticle(articleId: number) {
    return request({
        url: '/offline/' + articleId + '/',
        method: 'patch',
    }) as unknown as Article
}

1.4.3 Component

提供一个管理分类的抽屉组件,因此在src/components下创建文件CatalogTree.vue,编写代码如下:






由于文章管理的界面需要有Markdown编辑器,因此安装markdown编辑器的依赖

yarn add @kangc/[email protected]
yarn add [email protected]

main.ts 中增加编辑器的 jscss和插件

import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";
import { StateKey, store } from "./store";
import 'element-plus/lib/theme-chalk/index.css';
import 'element-plus/lib/theme-chalk/base.css';

// @ts-ignore
import VMdEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
// @ts-ignore
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js';
import '@kangc/v-md-editor/lib/theme/style/github.css';

// highlightjs
import hljs from 'highlight.js';

VMdEditor.use(githubTheme, {
    Hljs: hljs,
});
import {
    ElAffix,
    ElButton,
    ElCard,
    ElCascader,
    ElCol,
    ElDescriptions,
    ElDescriptionsItem,
    ElDialog,
    ElDrawer,
    ElDropdown,
    ElDropdownItem,
    ElDropdownMenu,
    ElForm,
    ElFormItem,
    ElIcon,
    ElInput,
    ElLoading,
    ElMenu,
    ElMenuItem,
    ElMessage,
    ElMessageBox,
    ElOption,
    ElPagination,
    ElPopconfirm,
    ElProgress,
    ElRow,
    ElSelect,
    ElTable,
    ElTableColumn,
    ElTag,
    ElTimeline,
    ElTimelineItem,
    ElTooltip,
    ElTree,
    ElUpload,
} from 'element-plus';

const app = createApp(App)


const components = [
    ElAffix,
    ElButton,
    ElCard,
    ElCascader,
    ElCol,
    ElDescriptions,
    ElDescriptionsItem,
    ElDialog,
    ElDrawer,
    ElDropdown,
    ElDropdownItem,
    ElDropdownMenu,
    ElForm,
    ElFormItem,
    ElIcon,
    ElInput,
    ElLoading,
    ElMenu,
    ElMenuItem,
    ElMessage,
    ElMessageBox,
    ElOption,
    ElPagination,
    ElPopconfirm,
    ElProgress,
    ElRow,
    ElSelect,
    ElTable,
    ElTableColumn,
    ElTag,
    ElTimeline,
    ElTimelineItem,
    ElTooltip,
    ElTree,
    ElUpload,
]

const plugins = [
    ElLoading,
    ElMessage,
    ElMessageBox,
]

components.forEach(component => {
    app.component(component.name, component)
})

plugins.forEach(plugin => {
    app.use(plugin)
})

app.use(router).use(store, StateKey).use(VMdEditor).mount('#app')

提供一个编辑文章的抽屉组件,因此在src/components下创建文件EditArticle.vue,编写代码如下:






1.4.4 View

通过表格管理文章,通过树形组件管理分类,在src/views/admin下新增文件Article.vue文件,编写如下代码:






1.4.5 Router

定义route来完成路由跳转。在src/route/index.ts 文件中新增代码:

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/client/Home.vue";

const routes: Array = [
    {
        path: "/",
        name: "Home",
        component: Home,
        meta: {}
    },
    {
        path: "/login/",
        name: "Login",
        component: () =>
            import("../views/admin/Login.vue")
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import("../views/admin/Admin.vue"),
        children: [
            {
                path: '/admin/',
                name: 'Dashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/dashboard',
                name: 'AdminDashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/user',
                name: 'UserManagement',
                component: () => import("../views/admin/User.vue"),
            },
            {
                path: '/admin/tag',
                name: 'Tag',
                component: () => import("../views/admin/Tag.vue"),
            },
            {
                path: '/admin/article',
                name: 'ArticleManagement',
                component: () => import("../views/admin/Article.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});


export default router;

1.4.6 vite.config.ts

由于我们需要展示对上传后的图片,因此需要对上传后的图片代理,在vite.config.ts文件中,增加如下代理:

'/upload': {
     target: 'http://localhost:8000/',
     changeOrigin: true,
     ws: false,
     rewrite: (pathStr) => pathStr.replace('/api', ''),
     timeout: 5000,
},

1.5 评论管理

15.1 Type

src/types/index.ts文件中增加代码如下:

export interface CommentInfo {
    id: number,
    user: number,
    user_info: User | any,
    article: number,
    article_info: Article | any,
    created_at: string,
    reply: number | any,
    content: string,
    comment_replies: CommentInfo | any,
}

export interface CommentPara {
    user: number,
    article: number,
    reply: number | any,
    content: string,
    page: number,
    page_size: number
}

1.5.2 API

这里要处理列表查询。在src/api/service.ts编写如下代码:

export function getCommentList(params: CommentPara) {
    return request({
        url: '/comment/',
        method: 'get',
        params,
    }) as unknown as ResponseData
}

1.5.3 Component

由于评论无需要做修改删除等操作,只有查看评论详情,因此复用文章详情页面。

1.5.4 View

通过表格查看评论,在src/views/admin下新增文件Comment.vue文件,编写如下代码:






1.5.5 Router

定义route来完成路由跳转。在src/route/index.ts 文件中新增代码:

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Home from "../views/client/Home.vue";

const routes: Array = [
    {
        path: "/",
        name: "Home",
        component: Home,
        meta: {}
    },
    {
        path: "/login/",
        name: "Login",
        component: () =>
            import(/* webpackChunkName: "login" */ "../views/admin/Login.vue")
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import(/* webpackChunkName: "admin" */ "../views/admin/Admin.vue"),
        children: [
            {
                path: '/admin/',
                name: 'Dashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/dashboard',
                name: 'AdminDashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/user',
                name: 'UserManagement',
                component: () => import("../views/admin/User.vue"),
            },
            {
                path: '/admin/tag',
                name: 'Tag',
                component: () => import("../views/admin/Tag.vue"),
            },
            {
                path: '/admin/article',
                name: 'ArticleManagement',
                component: () => import("../views/admin/Article.vue"),
            },
            {
                path: '/admin/comment',
                name: 'CommentManagement',
                component: () => import("../views/admin/Comment.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});


export default router;

1.6 管理后台首页

1.6.1 Type

src/types/index.ts文件中增加代码如下:

export interface NumberInfo {
    views: number,
    likes: number,
    comments: number,
    messages: number
}

1.6.2 API

这里要编写标签管理相关的接口,列表查询、新增、修改、删除。在src/api/service.ts编写如下代码:

export function getTopArticleList() {
    return request({
        url: '/top/',
        method: 'get',
    }) as unknown as ResponseData
}

export function getNumbers() {
    return request({
        url: '/number/',
        method: 'get',
    }) as unknown as NumberInfo
}

1.6.3 Component

无需提供额外的组件。

1.6.4 View

通过图标和指标卡的形式展示网站的整体情况,修改src/views/admin/Dashboard.vue,编写如下代码:






1.6.5Router

管理后台已经开发完成,因此需要在路由中做好权限控制,当访问admin路径的时候,需要判断用户是否登录,且用户是否是管理员,因此在src/router/index.ts中增加如下代码:

router.beforeEach((to, from, next) => {
    if (/\/admin/i.test(to.path)
        && (!store.state.user.id ||
            store.state.user.role !== 'Admin')) {
        next('/login')
        return
    }
    next()
})

src/views/admin/Login.vue中第143行后增加一行代码:

is_superuser: data.is_superuser

至此管理后台的前端开发完成

二、前端效果

2.1 前端管理后台页面效果

2.2 前端代码结构

下一篇我们编写博客网站给用户使用的页面。

你可能感兴趣的:(Vue3+TypeScript+Django Rest Framework 搭建个人博客(三):博客管理后台(3))