123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- import React, { useCallback, useMemo, useRef, useState } from 'react'
- import styles from './index.module.scss'
- // 引入编辑器组件
- // 安装---npm install braft-editor --save --force
- // npm install braft-utils --save --force
- import { ContentUtils } from 'braft-utils'
- import BraftEditor from 'braft-editor'
- // 引入编辑器样式
- import 'braft-editor/dist/index.css'
- import classNames from 'classnames'
- import { MessageFu } from '@/utils/message'
- import { fileDomInitialFu } from '@/utils/domShow'
- import { baseURL } from '@/utils/http'
- import { forwardRef, useImperativeHandle } from 'react'
- import { API_upFile } from '@/store/action/layout'
- import ZupAudio, { ZupAudioType } from '../ZupAudio'
- import { Button, Checkbox, Input } from 'antd'
- import { ArrowDownOutlined, DeleteOutlined, ArrowUpOutlined } from '@ant-design/icons'
- import MyPopconfirm from '../MyPopconfirm'
- export type SectionArrType = {
- id: number
- name: string
- txt: any
- fileInfo: ZupAudioType
- }
- type Props = {
- check: boolean //表单校验,为fasle表示不校验
- dirCode: string //文件的code码
- isLook: boolean //是否是查看进来
- ref: any //当前自己的ref,给父组件调用
- myUrl: string //上传的api地址
- isOne?: boolean //只显示单个富文本
- upAudioBtnNone?: boolean //是否能上传无障碍音频
- }
- function ZRichTexts(
- { check, dirCode, isLook, myUrl, isOne = false, upAudioBtnNone = false }: Props,
- ref: any
- ) {
- const [sectionArr, setSectionArr] = useState<SectionArrType[]>([
- {
- id: Date.now(),
- name: '',
- txt: BraftEditor.createEditorState(''),
- fileInfo: { fileName: '', filePath: '' }
- }
- ])
- // 是否按章节发布
- const [isSection, setIsSection] = useState(false)
- // 当前上传 图片 视频的索引
- const nowIndexRef = useRef(0)
- // 判断 富文本是否为空
- const isTxtFlag = useMemo(() => {
- let flag = false
- // 不是按章节发布,检查第一个富文本
- if (!isSection) {
- const txt = sectionArr[0].txt.toText()
- const txtHtml = sectionArr[0].txt.toHTML()
- const txtRes = txt.replaceAll('\n', '').replaceAll(' ', '')
- if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
- } else {
- // 按章节发布 检查 所有的 标题 和富文本
- sectionArr.forEach(v => {
- if (!v.name) flag = true
- const txt = v.txt.toText()
- const txtHtml = sectionArr[0].txt.toHTML()
- const txtRes = txt.replaceAll('\n', '').replaceAll(' ', '')
- if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
- })
- }
- return flag
- }, [isSection, sectionArr])
- const myInput = useRef<HTMLInputElement>(null)
- // 上传图片、视频
- const handeUpPhoto = useCallback(
- async (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.target.files) {
- // 拿到files信息
- const filesInfo = e.target.files[0]
- let type = ['image/jpeg', 'image/png', 'video/mp4']
- let size = 5
- let txt = '图片只支持png、jpg和jpeg格式!'
- let txt2 = '图片最大支持5M!'
- const isVideoFlag = filesInfo.name.endsWith('.mp4') || filesInfo.name.endsWith('.MP4')
- if (isVideoFlag) {
- // 上传视频
- size = 500
- txt = '视频只支持mp4格式!'
- txt2 = '视频最大支持500M!'
- }
- // 校验格式
- if (!type.includes(filesInfo.type)) {
- e.target.value = ''
- return MessageFu.warning(txt)
- }
- // 校验大小
- if (filesInfo.size > size * 1024 * 1024) {
- e.target.value = ''
- return MessageFu.warning(txt2)
- }
- // 创建FormData对象
- const fd = new FormData()
- // 把files添加进FormData对象(‘photo’为后端需要的字段)
- fd.append('type', isVideoFlag ? 'video' : 'img')
- fd.append('dirCode', dirCode)
- fd.append('file', filesInfo)
- e.target.value = ''
- try {
- const res = await API_upFile(fd, myUrl)
- if (res.code === 0) {
- MessageFu.success('上传成功!')
- // 在光标位置插入图片
- const newTxt = ContentUtils.insertMedias(sectionArr[nowIndexRef.current].txt, [
- {
- type: isVideoFlag ? 'VIDEO' : 'IMAGE',
- url: baseURL + res.data.filePath
- }
- ])
- const arr = [...sectionArr]
- arr[nowIndexRef.current].txt = newTxt
- setSectionArr(arr)
- }
- fileDomInitialFu()
- } catch (error) {
- fileDomInitialFu()
- }
- }
- },
- [dirCode, myUrl, sectionArr]
- )
- // 让父组件调用的 回显 富文本
- const ritxtShowFu = useCallback((val: any) => {
- if (val) {
- setIsSection(val.isSection || false)
- if (val.txtArr) {
- const arr = val.txtArr.map((v: any) => ({
- ...v,
- txt: BraftEditor.createEditorState(v.txt)
- }))
- setSectionArr(arr)
- }
- }
- }, [])
- // 让父组件调用的返回 富文本信息 和 表单校验 isTxtFlag为ture表示未通过校验
- const fatherBtnOkFu = useCallback(() => {
- const arr: any[] = []
- sectionArr.forEach((v, i) => {
- arr.push({
- ...v,
- txt: v.txt.toHTML()
- })
- })
- const obj = {
- isSection: isSection, //是否按章节发布
- txtArr: arr
- }
- return { val: obj, flag: isTxtFlag }
- }, [isSection, isTxtFlag, sectionArr])
- // 可以让父组件调用子组件的方法
- useImperativeHandle(ref, () => ({
- ritxtShowFu,
- fatherBtnOkFu
- }))
- // 点击新增章节
- const addSectionFu = useCallback(() => {
- if (sectionArr.length >= 20) return MessageFu.warning('最多存在20个章节')
- setSectionArr([
- ...sectionArr,
- {
- id: Date.now(),
- name: '',
- txt: BraftEditor.createEditorState(''),
- fileInfo: { fileName: '', filePath: '' }
- }
- ])
- }, [sectionArr])
- // 章节音频上传成功
- const upSectionFu = useCallback(
- (info: ZupAudioType, index: number) => {
- const arr = [...sectionArr]
- arr[index].fileInfo = info
- setSectionArr(arr)
- },
- [sectionArr]
- )
- // 章节音频删除
- const delSectionFu = useCallback(
- (index: number) => {
- // console.log("ppppppppp", index);
- const arr = [...sectionArr]
- arr[index].fileInfo = { fileName: '', filePath: '' }
- setSectionArr(arr)
- },
- [sectionArr]
- )
- // 整个章节的删除
- const delSectionAllFu = useCallback(
- (id: number) => {
- setSectionArr(sectionArr.filter(v => v.id !== id))
- },
- [sectionArr]
- )
- // 整个章节的位移
- const moveSectionFu = useCallback(
- (index: number, num: number) => {
- const arr = [...sectionArr]
- const temp = arr[index]
- arr[index] = arr[index + num]
- arr[index + num] = temp
- setSectionArr(arr)
- },
- [sectionArr]
- )
- // 单个富文本是否输入完整
- const isOneTxtFlag = useCallback(
- (name: string, txt: any) => {
- let flag = false
- if (!name && isSection) flag = true
- const txt2 = txt.toText()
- const txtHtml = txt.toHTML()
- const txtRes = txt2.replaceAll('\n', '').replaceAll(' ', '')
- if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
- return flag
- },
- [isSection]
- )
- return (
- <div className={styles.ZRichTexts}>
- <input
- id='upInput'
- type='file'
- accept='.png,.jpg,.jpeg,.mp4'
- ref={myInput}
- onChange={e => handeUpPhoto(e)}
- />
- <div className={classNames('formRightZW', isLook ? 'formRightZWLook' : '')}>
- {isOne ? (
- <div></div>
- ) : (
- <Checkbox checked={isSection} onChange={e => setIsSection(e.target.checked)}>
- 按章节发布
- </Checkbox>
- )}
- {isSection ? (
- <Button hidden={isLook} type='primary' onClick={addSectionFu}>
- 新增章节
- </Button>
- ) : (
- <div className='formRightZWRR'>
- {upAudioBtnNone ? null : (
- <ZupAudio
- fileInfo={sectionArr[0].fileInfo}
- upDataFu={info => upSectionFu(info, 0)}
- delFu={() => delSectionFu(0)}
- dirCode={dirCode}
- myUrl={myUrl}
- isLook={isLook}
- />
- )}
- <div hidden={isLook} style={{ marginLeft: 20 }}>
- <Button
- onClick={() => {
- nowIndexRef.current = 0
- myInput.current?.click()
- }}
- >
- 上传图片/视频
- </Button>
- </div>
- </div>
- )}
- </div>
- <div className={classNames('txtBox', isLook ? 'txtBoxLook' : '')}>
- {sectionArr.map((item, index) => (
- <div
- className={classNames(
- 'zztxtRow',
- isOneTxtFlag(item.name, item.txt) && check ? 'zztxtRowErr' : ''
- )}
- key={item.id}
- hidden={!isSection && index > 0}
- >
- {/* 顶部 */}
- <div className='zztxtRow1' hidden={!isSection && index === 0}>
- <div className='zztxtRow1_1'>
- <div className='zztxtRow1_1_1'>章节 {index + 1}</div>
- <div className='zztxtRow1_1_2'>
- 标题:
- <Input
- readOnly={isLook}
- value={item.name}
- placeholder='请输入内容'
- maxLength={100}
- showCount
- style={{ width: 400 }}
- onChange={e => {
- const arr = [...sectionArr]
- arr[index].name = e.target.value.replace(/\s+/g, '')
- setSectionArr(arr)
- }}
- />
-  
- <Button
- hidden={isLook}
- onClick={() => {
- nowIndexRef.current = index
- myInput.current?.click()
- }}
- >
- 上传图片/视频
- </Button>
- </div>
- </div>
- <div className='zztxtRow1_2'>
- <ZupAudio
- fileInfo={item.fileInfo}
- upDataFu={info => upSectionFu(info, index)}
- delFu={() => delSectionFu(index)}
- dirCode={dirCode}
- myUrl={myUrl}
- isLook={isLook}
- />
-  
- <div
- hidden={isLook}
- className={classNames('zztxtRow1_2Icon', index === 0 ? 'zztxtRow1_2IconNo' : '')}
- onClick={() => moveSectionFu(index, -1)}
- >
- <ArrowUpOutlined title='上移' />
- </div>
-  
- <div
- hidden={isLook}
- className={classNames(
- 'zztxtRow1_2Icon',
- index === sectionArr.length - 1 ? 'zztxtRow1_2IconNo' : ''
- )}
- onClick={() => moveSectionFu(index, 1)}
- >
- <ArrowDownOutlined title='下移' />
- </div>
-  
- {isLook || sectionArr.length <= 1 ? null : (
- <MyPopconfirm
- txtK='删除'
- onConfirm={() => delSectionAllFu(item.id)}
- Dom={<DeleteOutlined title='删除' className='ZTbox2X' />}
- />
- )}
- </div>
- </div>
- {/* 主体 */}
- <BraftEditor
- readOnly={isLook}
- placeholder='请输入内容'
- value={item.txt}
- onChange={e => {
- const arr = [...sectionArr]
- arr[index].txt = e
- setSectionArr(arr)
- }}
- imageControls={['remove']}
- />
- </div>
- ))}
- </div>
- <div className={classNames('noUpThumb', check && isTxtFlag ? 'noUpThumbAc' : '')}>
- {`请完整输入${isSection ? '标题/' : ''}正文!`}
- </div>
- </div>
- )
- }
- export default forwardRef(ZRichTexts)
|