从零开始搭建自己的博客平台:前端开发指南(持续更新-2.CMS)

921 阅读2分钟

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;
  }
`;

现在我们以及完成了基本的布局,看一下他长什么样子:

image.png

文章发布功能

我们现在先写一个我们的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;
`

👇就是写出来的样子

image.png

问题总结

在开发的过程中,我发现在引入编辑器渲染的时候会发生报错,所以我们在引入的时候要用nextdynamic方式引入,同时设置ssrfalse,这样就解决啦(看着简单,又耗费了好长时间)。

图片上传还没写,后续会补,现在我们已经完成了CMS的搭建了,前端,CMS数据要连通,后面我们就要开始写后端了