实现基础文章发布
创建基础结构
import {
Card,
Breadcrumb,
Form,
Button,
Radio,
Input,
Upload,
Space,
Select
} from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import { Link } from 'react-router-dom'
import './index.scss'
const { Option } = Select
const Publish = () => {
return (
<div className="publish">
<Card
title={
<Breadcrumb items={[
{ title: <Link to={'/'}>首页</Link> },
{ title: '发布文章' },
]}
/>
}
>
<Form
labelCol={{ span: 4 }}
wrapperCol={{ span: 16 }}
initialValues={{ type: 1 }}
>
<Form.Item
label="标题"
name="title"
rules={[{ required: true, message: '请输入文章标题' }]}
>
<Input placeholder="请输入文章标题" style={{ width: 400 }} />
</Form.Item>
<Form.Item
label="频道"
name="channel_id"
rules={[{ required: true, message: '请选择文章频道' }]}
>
<Select placeholder="请选择文章频道" style={{ width: 400 }}>
<Option value={0}>推荐</Option>
</Select>
</Form.Item>
<Form.Item
label="内容"
name="content"
rules={[{ required: true, message: '请输入文章内容' }]}
></Form.Item>
<Form.Item wrapperCol={{ offset: 4 }}>
<Space>
<Button size="large" type="primary" htmlType="submit">
发布文章
</Button>
</Space>
</Form.Item>
</Form>
</Card>
</div>
)
}
export default Publish
pages/Publish/index.scss
.publish {
position: relative;
}
.ant-upload-list {
.ant-upload-list-picture-card-container,
.ant-upload-select {
width: 146px;
height: 146px;
}
}
准备富文本编辑器
实现步骤
- 安装富文本编辑器
- 导入富文本编辑器组件以及样式文件
- 渲染富文本编辑器组件
- 调整富文本编辑器的样式
代码落地
1-安装 react-quill
npm i react-quill@2.0.0-beta.2
2-导入资源渲染组件
import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'
const Publish = () => {
return (
// ...
<Form
labelCol={{ span: 4 }}
wrapperCol={{ span: 16 }}
>
<Form.Item
label="内容"
name="content"
rules={[{ required: true, message: '请输入文章内容' }]}
>
<ReactQuill
className="publish-quill"
theme="snow"
placeholder="请输入文章内容"
/>
</Form.Item>
</Form>
)
}
.publish-quill {
.ql-editor {
min-height: 300px;
}
}
频道数据获取
实现步骤
- 使用useState初始化数据和修改数据的方法
- 在useEffect中调用接口并保存数据
- 使用数据渲染对应模版
代码实现
import { http } from '@/utils'
// 频道列表
const [channels, setChannels] = useState([])
// 调用接口
useEffect(() => {
async function fetchChannels() {
const res = await http.get('/channels')
setChannels(res.data.channels)
}
fetchChannels()
}, [])
// 模板渲染
return (
<Form.Item
label="频道"
name="channel_id"
rules={[{ required: true, message: '请选择文章频道' }]}
>
<Select placeholder="请选择文章频道" style={{ width: 200 }}>
{channels.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
))}
</Select>
</Form.Item>
)
发布文章
// 发布文章
const onFinish = async (formValue) => {
const { channel_id, content, title } = formValue
const params = {
channel_id,
content,
title,
type: 1,
cover: {
type: 1,
images: []
}
}
await http.post('/mp/articles?draft=false', params)
message.success('发布文章成功')
}
上传封面实现
准备上传结构
<Form.Item label="封面">
<Form.Item name="type">
<Radio.Group>
<Radio value={1}>单图</Radio>
<Radio value={3}>三图</Radio>
<Radio value={0}>无图</Radio>
</Radio.Group>
</Form.Item>
<Upload
listType="picture-card"
showUploadList
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>
</Form.Item>
实现基础上传
实现步骤
- 为 Upload 组件添加
action 属性
,配置封面图片上传接口地址 - 为 Upload组件添加
name属性
, 接口要求的字段名 - 为 Upload 添加
onChange 属性
,在事件中拿到当前图片数据,并存储到React状态中
代码实现
import { useState } from 'react'
const Publish = () => {
// 上传图片
const [imageList, setImageList] = useState([])
const onUploadChange = (info) => {
setImageList(info.fileList)
}
return (
<Form.Item label="封面">
<Form.Item name="type">
<Radio.Group>
<Radio value={1}>单图</Radio>
<Radio value={3}>三图</Radio>
<Radio value={0}>无图</Radio>
</Radio.Group>
</Form.Item>
<Upload
name="image"
listType="picture-card"
showUploadList
action={'http://geek.itheima.net/v1_0/upload'}
onChange={onUploadChange}
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>
</Form.Item>
)
}
切换图片Type
实现步骤
- 点击单选框时拿到当前的类型value
- 根据value控制上传组件的显示(大于零时才显示)
const Publish = ()=>{
// 控制图片Type
const [imageType, setImageType] = useState(0)
const onTypeChange = (e) => {
console.log(e)
setImageType(e.target.value)
}
return (
<FormItem>
<Radio.Group onChange={onTypeChange}>
<Radio value={1}>单图</Radio>
<Radio value={3}>三图</Radio>
<Radio value={0}>无图</Radio>
</Radio.Group>
{imageType > 0 &&
<Upload
name="image"
listType="picture-card"
showUploadList
action={'http://geek.itheima.net/v1_0/upload'}
onChange={onUploadChange}
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>}
</FormItem>
)
}
控制最大上传图片数量
实现步骤
- 通过 maxCount 属性限制图片的上传图片数量
{imageType > 0 &&
<Upload
name="image"
listType="picture-card"
className="avatar-uploader"
showUploadList
action={'http://geek.itheima.net/v1_0/upload'}
onChange={onUploadChange}
maxCount={imageType}
multiple={imageType > 1}
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>}
暂存图片列表实现
业务描述
如果当前为三图模式,已经完成了上传,选择单图只显示一张,再切换到三图继续显示三张,该如何实现?
实现思路
在上传完毕之后通过ref存储所有图片,需要几张就显示几张,其实也就是把ref当仓库,用多少拿多少
实现步骤
- 通过useRef创建一个暂存仓库,在上传完毕图片的时候把图片列表存入
- 如果是单图模式,就从仓库里取第一张图,以数组的形式存入fileList
- 如果是三图模式,就把仓库里所有的图片,以数组的形式存入fileList
代码实现
const Publish = () => {
// 上传图片
const cacheImageList = useRef([])
const [imageList, setImageList] = useState([])
const onUploadChange = (info) => {
setImageList(info.fileList)
cacheImageList.current = info.fileList
}
// 控制图片Type
const [imageType, setImageType] = useState(0)
const onRadioChange = (e) => {
const type = e.target.value
setImageType(type)
if (type === 1) {
// 单图,截取第一张展示
const imgList = cacheImageList.current[0] ? [cacheImageList.current[0]] : []
setImageList(imgList)
} else if (type === 3) {
// 三图,取所有图片展示
setImageList(cacheImageList.current)
}
}
return (
{imageType > 0 &&
<Upload
name="image"
listType="picture-card"
className="avatar-uploader"
showUploadList
action={'http://geek.itheima.net/v1_0/upload'}
onChange={onUploadChange}
maxCount={imageType}
multiple={imageType > 1}
fileList={imageList}
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>}
)
}
注意:需要给Upload组件添加fileList属性,达成受控的目的
发布带封面的文章
校验图片类型和数量是否吻合
// 发布文章
const onFinish = async (formValue) => {
if (imageType !== imageList.length) return message.warning('图片类型和数量不一致')
const { channel_id, content, title } = formValue
const params = {
channel_id,
content,
title,
type: imageType,
cover: {
type: imageType,
images: imageList.map(item => item.response.data.url)
}
}
await http.post('/mp/articles?draft=false', params)
message.success('发布文章成功')
}
处理图片列表格式为接口格式
// 发布文章
const onFinish = async (formValue) => {
const { channel_id, content, title } = formValue
const params = {
channel_id,
content,
title,
type: imageType,
cover: {
type: imageType,
images: imageList.map(item => item.response.data.url)
}
}
await http.post('/mp/articles?draft=false', params)
message.success('发布文章成功')
}