前台
Next.js
1. 路由拦截 — Middleware
Middleware enables you to use code over configuration. This gives you full flexibility in Next.js, because you can run code before a request is completed. Based on the user's incoming request, you can modify the response by rewriting, redirecting, adding headers, or even streaming HTML.
中间件使您能够使用代码来进行配置。这为您在Next.js中提供了完全的灵活性,因为您可以在请求完成之前运行代码。根据用户的传入请求,您可以通过重写、重定向、添加头文件、甚至是流媒体HTML来修改响应。
2. 获取数据 — getInitialProps
3. 接收query
- 使用withRouter
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
- 使用useRouter
动态引入(懒加载)
- 方式一:
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
- 方式二:
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/hello'))
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
样式编写
- 支持 style jsx
- 支持 [name].module.css
- 支持内嵌样式
- 支持import引入全局,支持在应用程序中的任何位置从 node_modules 目录导入(import) CSS 文件了
选择编辑器(react-quill | ByteMD)
react-quill 使用
- 引入
npm i react-quill
- 代码
/**
* @description 编辑文章
*/
import { useEffect, useState } from 'react';
import dynamic from 'next/dynamic'
import { Col, Row, Input, Button, Select, Modal, message } from 'antd';
import 'react-quill/dist/quill.snow.css';
import { nanoid } from 'nanoid'
import { getArticle, editArticle } from '../../../config';
import { useRouter } from 'next/router';
const { TextArea } = Input;
const QuillNoSSRWrapper = dynamic(import('react-quill'), {
ssr: false,
loading: () => <p>Loading ...</p>,
})
const modules = {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }], //字体设置
[
{
color: [],
},
],
[
{
background: [],
},
],
[{ font: [] }],
[{ align: [] }],
['link', 'image'], // a链接和图片的显示
],
},
};
const create: React.FC = () => {
// 文章id
const [articleId, setArticleId] = useState("");
// 文章标题
const [title, setTitle] = useState("用例标题")
// 文章内容
const [value, setValue] = useState("文章内容");
// 文章简介
const [intro, setIntro] = useState("内容简介");
// 文章分类
const [type, setType] = useState("6809635626879549454");
// 控制弹窗
const [isModalOpen, setIsModalOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const router = useRouter()
useEffect(() => {
const a = location.href.split("/")
const id1 = a[a.length - 1]
// getArticle(id1).then(res => {
// if (res.code === 200) {
// console.log(res.data);
// setArticleId(res.data.id)
// setTitle(res.data.title)
// setValue(res.data.articleContent)
// setIntro(res.data.introduce)
// setType(res.data.typeId)
// } else {
// console.log("获取失败edit", res);
// }
// })
}, [])
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setConfirmLoading(true);
let obj = {
id: articleId,
typeId: type,
framerId: localStorage.getItem("framer_id"),
title: title,
articleContent: value,
introduce: intro,
addTime: null,
updateTime: null,
viewCount: 0,
likeCount: 0,
collectCount: 0,
commentCount: 0
}
console.log(obj);
// editArticle(obj).then(res => {
// if (res.code === 200) {
// message.success("修改成功")
// } else {
// message.error("修改失败")
// console.log("修改文章失败:", res);
// }
// })
setTimeout(() => {
setIsModalOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const changeType = (value) => {
setType(value)
};
// const changeIntro = (value) => {
// setI(value)
// };
return (
<>
<Row>
<Col span={12}>
<Input placeholder="请输入文章标题..." bordered={false}
style={{ fontSize: '24px', lineHeight: '56px', marginLeft: '10px' }}
value={title}
onChange={(e) => {
setTitle(e.target.value);
}} />
<QuillNoSSRWrapper theme="snow" value={value} onChange={setValue} modules={modules} style={{ height: "80vh", backgroundColor: '#f8f9fa' }}
/>
</Col>
<Col span={12}>
<div>
<Button type="primary" size="large" onClick={showModal} style={{ position: 'fixed', right: '25px', top: '10px' }}>发 布</Button>
<Modal title="填写信息" open={isModalOpen} onOk={handleOk} onCancel={handleCancel} confirmLoading={confirmLoading}>
<div>分类: </div>
<Select
size="large"
value={type}
onChange={changeType}
style={{ width: 200 }}
options={[
{ value: '6809635626661445640', label: 'IOS' },
{ value: '6809635626879549454', label: 'Android' },
{ value: '6809637767543259144', label: '前端' },
{ value: '6809637769959178254', label: '后端' },
{ value: '6809637771511070734', label: '开发工具' },
{ value: '6809637772874219534', label: '阅读' },
{ value: '6809637773935378440', label: '人工智能' },
]}
/>
<div>简介: </div>
<TextArea
value={intro}
onChange={(e) => setIntro(e.target.value)}
placeholder="请输入简介..."
autoSize={{ minRows: 3, maxRows: 5 }}
/>
</Modal>
</div>
<div
style={{ border: '1px solid #cccccc', height: "88.6vh", borderLeft: 'none', marginTop: '64px', overflowY: 'auto', padding: '20px' }}
dangerouslySetInnerHTML={{ __html: value }}></div>
</Col>
</Row>
</>
)
}
export default create;
ByteMD (掘金同款)
后端
egg.js
app/router.js 配置路由
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get("/list", controller.list.index);
};
egg-mysql
连接数据库
config/config.default.js
exports.mysql = {
// database configuration
client: {
// host
host: 'mysql.com',
// port
port: '3306',
// username
user: 'test_user',
// password
password: 'test_password',
// database
database: 'test',
},
// load into app, default is open
app: true,
// load into agent, default is close
agent: false,
};
app/controllar/dafault.js
//获取数据库信息
pages/index.js
//利用axios连接中台,渲染数据
RESTful接口
REST架构原则
- 网络上的所有事物都被抽象为资源
- 每个资源都有一个唯一的资源标识符
- 同一个资源具有多种表现形式(xml,json等)
- 对资源的各种操作不会改变资源标识符
- 所有的操作都是无状态的
符合REST原则的架构方式即可称为RESTful
编写路由守卫
- 建文件
middleware/adminauth.js - 在路由配置文件里使用路由守卫
后台
react-router-dom(6版本)
- 安装
yarn add react-router-dom - 引入
import { BrowserRouter as Router, Route,Routes } from "react-router-dom"; - 使用
<Router>
<Routes>
<Route path="/login" element={<Login />} />
</Routes>
</Router>
- 多级路由配置
- 第一种,配置两次Route
//Main.js <Router> <Routes> <Route path="/login" element={<Login />} /> <Route path="/admin/*" element={<AdminIndex />} /> </Routes> </Router> //AdminIndex.js <Routes> <Route path="index" element={<Login />} /> </Routes>- 第二种,在父组件,用
<Outlet />给子组件留位置
//Main.js <Router> <Routes> <Route path="/login" element={<Login />} /> <Route path="/admin" element={<AdminIndex />} > <Route path="index" element={<AddArticle />}/> </Route> </Routes> </Router> //AdminIndex.js <Outlet /> - 编程式路由跳转
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/admin/index");
点击菜单跳转
- 为Menu绑定onClick
- onClick默认接收参数item
- 根据item.key进行路由跳转
登录功能
步骤
- 中台
- 建表
- 编写控制器,需要暴露
- 配置路由
router/admin.js,在router.js中引入 - 改变
config.default.js的配置
- 前台
- 在
Login.js中为按钮绑定方法 - 编写方法:判断用户输入的正确性,发送post请求,根据返回数据判断登录情况
- 配置路由文件
config/apiUrl.js
- 在
- post请求
- sql语句username和password的值加引号,有可能是中文
添加文章/更新文章
- 中台
- 接收数据
- 存入数据库
- 将id设置为自增(int)
- 前台
- 初始文章id为0,表示是新文章
- 判断数据的正确性
- 封装data数据
- 调用后端接口
- 将返回的文章id进行赋值
- 再次发布文章,如果id不为0,就更新文章,调用更新接口
文章列表
- 中台
- 获取文章列表方法
- 配置路由
- 前台
- 配置路由
- 获取文章列表
- 使用useEffect(()=>{getList()}.[]),在页面加载时首次渲染
删除文章
- 中台
- 获取文章列表方法
- 配置路由
- 前台
- 编写删除方法
- 配置路由
react为什么onclick都是使用箭头函数,为什么onClick会被调用,使用箭头函数就不会
修改文章
- 中台
- 将date数据转为年月日
STR_TO_DATE(concat(YEAR(addTime),'-',MONTH(addTime),'-',DAY(addTime)),'%Y-%m-%d'),STR_TO_DATE是将数据转为data类,但是中台接收的数据就不会是年月日的形式,所以要把这个方法去掉
- 将date数据转为年月日
- 前台
- 配置路由,为添加文章addArticle多配置一个带有params的路由
- 编写根据id获取文章信息的方法
- 获取params使用useParams
- 在useEffect中进行首次渲染操作