一个完整的网站都是有前台和管理后台组成的,前台用来给真正的用户浏览和使用,后台用来给管理员管理网站内容,配置各种功能和数据等。博客的管理后台就是用来承载创建博客,发布博客,查看留言,管理博客用户这些功能的子系统。
大家好,我是落霞孤鹜
,上一篇我们已经实现了管理后台的前端部分页面,这一章我们继续搭建博客的管理后台的前端,实现对博客网站的管理功能。
一、前端开发
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
中增加编辑器的 js
、css
和插件
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
,编写如下代码:
今日博客访问情况
{{ state.numbers.views }}
用户访问量
{{ state.numbers.likes }}
点赞量
{{ state.numbers.comments }}
评论量
{{ state.numbers.messages }}
留言量
文章访问量TOP10
{{ index + 1 + '. ' + article.title }}
{{ article.views }} / {{ article.likes }}
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 前端代码结构
下一篇我们编写博客网站给用户使用的页面。