CMS后台管理系统
继上一篇文章 ,开头还是和之前的一样,使用Next,styled-components,唯一不同的是我们把UI组件库从Chakra UI 换成了Ant Design,因为Ant Design对Form Ui支持的更好。
$ yarn create next-app
//使用TS
$ yarn create next-app --typescript
$ yarn add antd
然后开始干什么???? 动代码!!!!!
Layout
同样,我们要处理一下SSR样式渲染的问题,和上一篇文章一样。我们把styled-components的处理放在_document.tsx文件内,参考上一篇文章。
对于我们的CMS后台管理系统来说,我们是要有一个整体的菜单栏,这个就作为我们基础的Layout,这是一个除了登陆页面都应该包含的组件,我们应该把这个组件放在_app.tsx文件里,放一下代码:
//_app.tsx
import "@/styles/globals.css";
import Layout from "@/components/Layout";
export default function App({ Component , pageProps } : any) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
然后我们建一个components文件夹,用来存放我们的公共组件。以下是Layout的代码,我们基本上都是使用的antd的Layout,只是修改了一些需要的内容。
//Layout.jsx
import React, { useState } from "react";
import {
LaptopOutlined,
NotificationOutlined,
UserOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
} from "@ant-design/icons";
import { IconBox } from "./Categories.styles";
import type { MenuProps } from "antd";
import { Breadcrumb, Layout, Menu, theme } from "antd";
const { Header, Content, Sider } = Layout;
const items1: MenuProps["items"] = ["1"].map((key) => ({
key:'one',
label: `MY BLOG`,
}));
const _Layout = ({ children }: any) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const [collapsed, setCollapsed] = useState(false);
const categories = [
{
key: "1",
icon: <UserOutlined />,
label: "user",
onMouseDown: () => {
// window.location.href = "/editblog";
},
},
{
key: "2",
icon: <LaptopOutlined />,
label: "Blog Management",
onMouseDown: () => {
window.location.href = "/bloglist";
},
},
{
key: "3",
icon: <NotificationOutlined />,
label: "Publish Blog",
onMouseDown: () => {
window.location.href = "/editblog";
},
},
];
// Use the layout defined at the page level, if available
return (
<Layout>
<Header className="header">
<div className="logo" />
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["one"]}
items={items1}
/>
</Header>
<IconBox onClick={() => setCollapsed(!collapsed)}>
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
</IconBox>
<Layout>
<Sider
width={200}
style={{ background: colorBgContainer }}
trigger={null}
collapsible
collapsed={collapsed}
>
<Menu
mode="inline"
defaultSelectedKeys={['1']}
// openKeys={[keys]}
defaultOpenKeys={["sub1"]}
style={{ height: "100%", borderRight: 0 }}
items={categories}
/>
</Sider>
<Layout style={{ padding: "0 24px 24px" }}>
<Content
style={{
padding: 24,
margin: 0,
minHeight: 280,
background: colorBgContainer,
}}
>
{children}
</Content>
</Layout>
</Layout>
</Layout>
);
};
export default _Layout;
👇是样式代码Categories.styles.js
//Categories.styles.js
import styled from "styled-components";
export const IconBox = styled.span`
cursor: pointer;
width: 24px;
svg {
width: 24px;
height: 24px;
}
`;
现在我们以及完成了基本的布局,看一下他长什么样子:
文章发布功能
我们现在先写一个我们的Blog最基础的文章发布功能,这里我采用的是富文本编辑器,通过我不断的Baidu、Google的各种查阅富文本编辑器的插件,最后我使用了for-editor开箱即用,非常好用,官方链接,我们先安装一下:
$ yarn add for-editor
装完之后我们开始敲代码,创建compoentns/MarkEdit/index.tsx文件,写下以下代码
// MarkEdit/index.tsx
import Editor from "for-editor";
export default function MrkEdit({doc,setDoc}:any) {
return (
<Editor
value={doc}
onChange={setDoc}
height="100vh"
placeholder="Please edit you content"
/>
);
}
创建我们的页面,创建pages/editblog/index.tsx,我们先要定义一下我们的文章都包含什么数据:
{
title:'',//文章标题
doc: '',//文章内容
description:'',//文章描述
tag:'',//文章类别,也是包含的标签(抄别人博客的,hhhhh)
image:'',//文章展示在首页的图片
}
//index.jsx
import { use, useEffect, useState } from "react";
import {
HeaderBox,
EditBox,
InputA,
TextAreaA,
DrawerItem,
} from "./EditBlog.styles";
import { Button, Drawer, Form, Input } from "antd";
import dynamic from "next/dynamic";
import axios from "axios";
//解决报错引入
const MarkEdit = dynamic(import("@/components/MarkEdit"), {
ssr: false,
loading: () => <p>loading....</p>,
});
export default function EditBlog() {
const [doc, setDoc] = useState("");
const [blogInfo, setBlogInfo] = useState();
const [drawer, setDrawer] = useState(false);
const publishNow = () => {
console.log(blogInfo,doc,'testData')
};
return (
<EditBox>
<HeaderBox>
<InputA
placeholder="Please Edit your title..."
size="large"
onChange={(e) => {
setBlogInfo({ ...blogInfo, title: e.target.value });
}}
bordered={false}
/>
<Button
type="primary"
onClick={() => {
setDrawer(true);
}}
>
Publish
</Button>
</HeaderBox>
<MarkEdit doc={doc} setDoc={setDoc} />
<Drawer
title="Publish My Blog"
placement="right"
onClose={() => {
setDrawer(false);
}}
open={drawer}
width={500}
>
<div>
<DrawerItem>Description :</DrawerItem>
<TextAreaA
placeholder="Please Edit your description..."
autoSize
showCount
maxLength={200}
onChange={(e) => {
setBlogInfo({ ...blogInfo, description: e.target.value });
}}
/>
</div>
<div>
<DrawerItem>Tag : </DrawerItem>
<Input
placeholder="Please Edit your tag..."
onChange={(e) => {
setBlogInfo({ ...blogInfo, tag: e.target.value });
}}
/>
</div>
<div>
<DrawerItem>Image : </DrawerItem>
<Input
placeholder="Please Edit your tag..."
onChange={(e) => {
setBlogInfo({ ...blogInfo, tag: e.target.value });
}}
/>
</div>
<div style={{ marginTop: "40px", float: "right" }}>
<Button
style={{ marginRight: "20px" }}
type="primary"
onClick={() => publishNow()}
>
Publish Now
</Button>
<Button
type="primary"
onClick={() => {
setDrawer(false);
}}
ghost
>
Cancel
</Button>
</div>
</Drawer>
</EditBox>
);
}
👇是样式代码EditBlog.styles.js
EditBlog.styles.js
import styled from "styled-components";
import { Input,Button } from "antd";
const { TextArea } = Input;
export const EditBox = styled.div`
background-color: #f5f5f5;
`
export const HeaderBox = styled.div`
display: flex;
background-color: #fff;
justify-content: center;
align-items: center;
margin-bottom: 20px;
`
export const InputA = styled(Input)`
height: 60px;
`;
export const TextAreaA = styled(TextArea)`
`;
export const DrawerItem = styled.p`
margin-top: 20px;
margin-bottom: 10px;
`
👇就是写出来的样子
问题总结
在开发的过程中,我发现在引入编辑器渲染的时候会发生报错,所以我们在引入的时候要用next的dynamic方式引入,同时设置ssr为false,这样就解决啦(看着简单,又耗费了好长时间)。
图片上传还没写,后续会补,现在我们已经完成了CMS的搭建了,前端,CMS数据要连通,后面我们就要开始写后端了