一、用到的技術(shù)
1弛随、技術(shù)棧
react: ^16.14.0
react-dom:^17.0.0
react-quill: ^1.3.5 富文本編輯器
react-router: ^4.3.1
react-dev-inspector:^1.1.1 這個(gè)包允許用戶通過簡(jiǎn)單的點(diǎn)擊直接從瀏覽器 React 組件跳轉(zhuǎn)到本地 IDE 代碼
2、UI組件
Ant Design --------------------------用到版本: antd: 4.15.1
Ant Design Pro Components
二墨林、用法總結(jié)
1敬惦、富文本
這里有個(gè)坑谣膳,如果和自定義下拉選項(xiàng)一起使用,得下拉框渲染完之后才能渲染富文本組件,不然會(huì)報(bào)錯(cuò),加不了必填項(xiàng)
import ReactQuill,{ Quill } from 'react-quill';
import QuillEmoji from 'quill-emoji'
import 'quill-emoji/dist/quill-emoji.css'
import 'react-quill/dist/quill.snow.css'
Quill.register({
'modules/emoji-toolbar': QuillEmoji.ToolbarEmoji,
// 'modules/emoji-textarea': QuillEmoji.TextAreaEmoji,
'modules/emoji-shortname': QuillEmoji.ShortNameEmoji
})
const modules={
toolbar:{
container:[
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'font': [] }],
[{ 'header': 1 }, { 'header': 2 }], // custom button values
['bold', 'italic', 'underline', 'strike'], // toggled buttons
[{ 'align': [] }],
[{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
[{ 'script': 'sub' }, { 'script': 'super' }], // superscript/subscript
['blockquote', 'code-block'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'color': [] }, { 'background': [] }],
['emoji', 'image', 'video', 'link'],
['clean']
],
handlers: {
image: ()=>imageHandler()
}
},
'emoji-toolbar': true,
// 'emoji-textarea': true,
'emoji-shortname': true,
}
//自定義上傳圖片格式
const imageHandler = () => {
const quillEditor = ref.current?.getEditor()
const input = document.createElement('input')
input.setAttribute('type', 'file')
input.setAttribute('accept', 'image/*')
input.click()
input.onchange = async () => {
const file = input.files[0]
const formData = new FormData()
formData.append('quill-image', file)
const code=218
const link=await upload(file,code)//把base64格式轉(zhuǎn)換為正常url路徑格式,自定義方法
const range = quillEditor?.getSelection()
quillEditor.insertEmbed(range.index, 'image', link)
}
}
{
onselect.length>0&&//如果和自定義下拉選項(xiàng)一起使用溉委,得下拉框渲染完之后才能渲染富文本組件鹃唯,不然會(huì)報(bào)錯(cuò)
<div className={styles.box}>
<Form.Item
label="文章詳情"
name="articleContent"
readonly={detailData?.id&&detailData?.edtil}
// rules={[{ required: true, message: '請(qǐng)?jiān)O(shè)置文章詳情!' }]}
>
<ReactQuill modules={modules} ref={ref}/>
</Form.Item>
<div className={styles.mark}>*</div>
</div>
}
1.1、base64轉(zhuǎn)url路徑upload方法
import OSS from 'ali-oss';
import request from '@/utils/request';
const getConfig = (params = {}, options = {}) => {
return request('/auth/goods/product/getOssConfig', {
method: 'POST',
data: params,
...options
});
}
const getImageSize = (file) => {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (theFile) => {
const image = new Image()
image.src = theFile.target.result
image.onload = function () {
const { width, height } = this;
resolve({ width, height })
}
}
});
}
let ossConfig = null;
let codeTemp = null
const upload = async (file, code) => {
if (code !== codeTemp) {
ossConfig = null
}
codeTemp = code;
if (!ossConfig) {
const res = await getConfig({ code });
ossConfig = res?.data?.records
}
const client = new OSS({
region: `oss-${ossConfig.regionId}`,
accessKeyId: ossConfig.credentials.accessKeyId,
accessKeySecret: ossConfig.credentials.accessKeySecret,
stsToken: ossConfig.credentials.securityToken,
bucket: ossConfig.uploadInfo.bucket,
})
return new Promise((resolve) => {
client.put(`${ossConfig.uploadInfo.dir}/${file.uid}-y_g-${file.name}`, file).then(res => {
if (file.type.indexOf('image') !== -1) {
getImageSize(file).then(size => {
resolve(`${res.url}?imgHeight=${size.height}&imgWidth=${size.width}`)
})
} else {
resolve(res.url);
}
}).catch(err => {
ossConfig = null;
// return upload(file, dirName)
console.log('上傳失敯旰啊:', err)
})
})
}
export default upload;
2坡慌、上傳圖片右邊添加圖片描述不影響Form表單提交的方法
企業(yè)微信截圖_16451702601132.png
const FromWrap = ({ value, onChange, content, right }) => (
<div style={{ display: 'flex' }}>
<div>{content(value, onChange)}</div>
<div style={{ flex: 1, marginLeft: 10, minWidth: 180 }}>{detailData?.id&&detailData?.edtil?null:right(value)}</div>
</div>
)
<Form.Item
label="封面圖片"
name="coverPicture"
rules={[{ required: true, message: '請(qǐng)上傳圖片!' }]}
readonly={detailData?.id&&detailData?.edtil}
>
<FromWrap
content={(value, onChange) => <Upload multiple value={value} onChange={onChange} maxCount={1} accept="image/*" size={(1*1024)/2} />}
right={(value) => {
return (
<dl>
<dt>圖片要求</dt>
<dd>1.圖片大小500kb以內(nèi)</dd>
<dd>2.建議尺寸為 720 x 200</dd>
<dd>3.圖片格式png/jpg/gif</dd>
</dl>
)
}}
/>
</Form.Item>
3、省市區(qū)多選級(jí)聯(lián)選擇
企業(yè)微信截圖_16451712318615.png
原級(jí)聯(lián)組件地址:https://rsuitejs.com/components/multi-cascader/
import React, { useState, useEffect, useMemo } from 'react';
import { Button, Form, Input, message, Select, Tag } from 'antd';
import MultiCascader from 'rsuite/lib/MultiCascader';
import 'rsuite/lib/MultiCascader/styles';
import './style.less'
const AddressMultiCascader = ({ value = '', onChange = () => { }, data, pId = 0, ...rest }) => {
const [areaData, setAreaData] = useState([]);
const [selectAreaKey, setSelectAreaKey] = useState(value);
const renderMultiCascaderTag = (selectedItems) => {
const titleArr = [];
selectedItems.forEach(item => {
const arr = [];
let node = item.parent;
arr.push(item.label)
while (node) {
if (node.pvalue !==-1) {
arr.push(node.label)
}
node = node.parent;
}
titleArr.push({
label: arr.reverse().join('-'),
value: item.value
})
})
const arrayToTree = (list, parId = 0) => {
const len = list.length
function loop(pid) {
const res = [];
for (let i = 0; i < len; i += 1) {
const item = list[i]
if (item&&item.pid === pid) {
item.children = loop(item.id)
res.push(item)
}
}
return res.length ? res : null
}
return loop(parId)
}
return (
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{
titleArr.map(item => (
<Tag
key={item.value}
closable
style={{ marginBottom: 10 }}
onClose={() => {
setSelectAreaKey(selectAreaKey.filter(it => it !== item.value))
onChange(selectAreaKey.filter(it => it !== item.value))
}}
>
{item.label}
</Tag>
))
}
</div>
);
}
useEffect(() => {
const arr = arrayToTree(data || window.yeahgo_area || [], pId)
let str = JSON.stringify(arr)
str = str.replace(/name/g, 'label').replace(/id/g, 'value')
setAreaData(JSON.parse(str))
}, [data])
useEffect(() => {
setSelectAreaKey(value)
}, [value])
return (
<MultiCascader
value={selectAreaKey}
data={areaData}
style={{ width: '100%' }}
placeholder="請(qǐng)選擇"
renderValue={(a, b) => renderMultiCascaderTag(b)}
locale={{ searchPlaceholder: '輸入省市區(qū)名稱' }}
onChange={(v) => { onChange(v); setSelectAreaKey(v) }}
{...rest}
/>
)
}
export default AddressMultiCascader;
import AddressMultiCascader from '@/components/address-multi-cascader'
const [selectKeys, setSelectKeys] = useState([]);
const getAreaData = (v) => {
const arr = [];
v?.forEach?.(item => {
let deep = 0;
let node = window.yeahgo_area.find(it => it.id === item);
const nodeIds = [node.id];
const nodeNames = [node.name]
while (node.pid) {
deep += 1;
node = window.yeahgo_area.find(it => it.id === node.pid);
nodeIds.push(node.id);
nodeNames.push(node.name);
}
arr.push({
provinceId: nodeIds[deep],
cityId: deep > 0 ? nodeIds[deep - 1] : 0,
districtId: deep > 1 ? nodeIds[deep - 2] : 0,
areaName: nodeNames.reverse().join('|')
})
})
return arr;
}
const getAreaDatas = (v) => {
const arr = [];
const brr = []
v?.forEach?.(item => {
let deep = 0;
let node = window.yeahgo_area.find(it => it.id === item);
const nodeIds = [node.id];
const nodeNames = [node.name]
if(node.children){
const toTreeData = (data) => {
data?.forEach(item => {
if(item.deep == 3){
brr.push(item.id)
}
toTreeData(item.children)
})
}
toTreeData(node?.children)
}
while (node.pid) {//找父級(jí)
deep += 1;
node = window.yeahgo_area.find(it => it.id === node.pid);
nodeIds.push(node.id);
nodeNames.push(node.name);
}
arr.push({
provinceId: nodeIds[deep],
cityId: deep > 0 ? nodeIds[deep - 1] : 0,
districtId: deep > 1 ? nodeIds[deep - 2] : 0,
areaName: nodeNames.reverse().join('|')
})
})
if(brr.length){
return getAreaData(brr)
}else{
return arr;
}
}
<AddressMultiCascader
value={selectKeys}
placeholder="添加地區(qū)"
renderValue={() => <span style={{ color: '#8e8e93' }}>添加地區(qū)</span>}
renderExtraFooter={() =>
<div style={{ padding: 10, textAlign: 'right' }}>
<Button type="primary" onClick={() => { setArea() }}>確定</Button>
</div>}
onChange={setSelectKeys}
disabledItemValues={disabledItemValues}
onClose={() => { setSelectKeys([]) }}
/>