import React from 'react'; import styles from './DraftEditor.less'; import { Modal, Form, Input, Button, message, Upload, Icon } from 'antd'; import b from '../assets/专家经验系统切图/智能报告/B.png'; import ii from '../assets/专家经验系统切图/智能报告/_.png'; import it from '../assets/专家经验系统切图/智能报告/I.png'; import h from '../assets/专家经验系统切图/智能报告/H.png'; import blockQu from '../assets/专家经验系统切图/智能报告/blockQu.png'; import code from '../assets/专家经验系统切图/智能报告/___.png'; import prjectNum from '../assets/专家经验系统切图/智能报告/prjectNum.png'; import num from '../assets/专家经验系统切图/智能报告/num.png'; import t from '../assets/专家经验系统切图/智能报告/T.png'; import lk from '../assets/专家经验系统切图/智能报告/lk.png'; import pic from '../assets/专家经验系统切图/智能报告/pic.png'; import video from '../assets/专家经验系统切图/智能报告/video.png'; import sum from '../assets/专家经验系统切图/智能报告/sum.png'; import attrs from '../assets/专家经验系统切图/智能报告/attr.png'; import config from '@/webPublic/one_stop_public/config'; import MyBlockRender from './MyBlockRender'; import { changeToDraftState2, changeFromDraftState2 } from '../utils/myutils'; import { Editor, EditorState, AtomicBlockUtils, convertFromRaw, convertToRaw, CompositeDecorator, RichUtils, } from 'draft-js'; import { getToken } from '@/webPublic/one_stop_public/utils/token'; import { queryFileUrl } from '@/webPublic/one_stop_public/utils/queryConfig'; import { getSassApiHeader, getSysCode } from '@/webPublic/one_stop_public/2023yunshangguizhou/utils'; const FormItem = Form.Item; function getBlockStyle(block) { switch (block.getType()) { case 'blockquote': return 'RichEditor-blockquote'; default: return null; } } const StyleControls = (props) => { const { editorState } = props; const selection = editorState.getSelection(); const blockType = editorState .getCurrentContent() .getBlockForKey(selection.getStartKey()) .getType(); var currentStyle = props.editorState.getCurrentInlineStyle(); return ( <div style={{ borderBottom: '1px solid gray', borderTop: '1px solid gray', height: 50, paddingTop: 10, }}> {props.btns.map((fn) => ( <StyleButton key={fn.label} active={fn.type == 'block' ? fn.style === blockType : currentStyle.has(fn.style)} label={fn} onToggle={fn.type == 'block' ? props.toggleBlockType : props.toggleInlineStyle} style={fn.style} /> ))} </div> ); }; export default class DraftEditor extends React.Component { constructor(props) { super(props); const value = props.value || {}; this.state = { editorState: value.editorState, modalVisible: false, fnKey: '', styleMap: { RED: { color: 'red', }, }, }; this.setEditor = (editor) => { this.editor = editor; }; this.onChange = (editorState, callback) => { if (!('value' in this.props)) { this.setState({ editorState }, () => { if (callback) callback(); }); } this.triggerChange({ editorState }, () => { if (callback) callback(); }); }; this.focusEditor = () => { if (this.editor) { this.editor.focus(); } }; this.triggerChange = (changedValue, callback) => { // Should provide an event to pass value to Form. const onChange = this.props.onChange; if (onChange) { onChange(Object.assign({}, this.state, changedValue)); } if (callback) callback(); }; } exchange = (data, editKey, callback) => { const blocks = changeFromDraftState2(this.state.editorState); const bs = blocks.blocks; var b; for (var i = 0; i < bs.length; i++) { if (bs[i].key == editKey) { b = bs[i]; break; } } const entityKey = b.entityRanges[0].key + ''; blocks.entityMap[entityKey].data = data; const editorState = changeToDraftState2(blocks); this.onChange(editorState, callback); }; extends = (data, type, text, callback) => { var editorState = this.state.editorState; const contentState = editorState.getCurrentContent(); const contentStateWithEntity = contentState.createEntity(type, 'IMMUTABLE', data); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity }); const xx = AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, text); if (!('value' in this.props)) { this.setState({ editorState: xx }, () => { callback(); }); } this.triggerChange({ editorState: xx }, () => { callback(); }); }; urlChange(event) { const target = event.target; this.setState({ url: target.value, }); } toggleBlockType = (blockType) => { this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType)); }; toggleInlineStyle = (inlineStyle) => { this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, inlineStyle)); }; componentWillReceiveProps(nextProps) { // Should be a controlled component. if ('value' in nextProps) { const value = nextProps.value; this.setState(value); } } okHandle = () => {}; insertAttr = () => { if (!('value' in this.props)) { this.setState({ modalVisible: true, fnKey: 'attr' }); } this.triggerChange({ modalVisible: true, fnKey: 'attr' }); }; insertPic = () => { if (!('value' in this.props)) { this.setState({ modalVisible: true, fnKey: 'image' }); } this.triggerChange({ modalVisible: true, fnKey: 'image' }); }; insertFormula = () => { if (!('value' in this.props)) { this.setState({ modalVisible: true, fnKey: 'formula' }); } this.triggerChange({ modalVisible: true, fnKey: 'formula' }); }; insertVideo = () => { if (!('value' in this.props)) { this.setState({ modalVisible: true, fnKey: 'video' }); } this.triggerChange({ modalVisible: true, fnKey: 'video' }); }; insertLink = () => { const x = this.state.editorState.getSelection().getStartOffset(); const y = this.state.editorState.getSelection().getEndOffset(); if (y - x == 0) { message.error('请选择需要插入链接的内容'); return; } if (!('value' in this.props)) { this.setState({ modalVisible: true, fnKey: 'link' }); } this.triggerChange({ modalVisible: true, fnKey: 'link' }); }; callback3 = (content) => { const { editorState } = this.state; const contentState = editorState.getCurrentContent(); const contentStateWithEntity = contentState.createEntity('video', 'IMMUTABLE', content); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity }); const xx = AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, '[视频]'); if (!('value' in this.props)) { this.setState({ editorState: xx, modalVisible: false }); } this.triggerChange({ editorState: xx, modalVisible: false }); }; callback4 = (content) => { const { editorState } = this.state; const contentState = editorState.getCurrentContent(); const contentStateWithEntity = contentState.createEntity('attr', 'IMMUTABLE', content); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity }); const xx = AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, '[附件]'); if (!('value' in this.props)) { this.setState({ editorState: xx, modalVisible: false }); } this.triggerChange({ editorState: xx, modalVisible: false }); }; callback2 = (content) => { const { editorState } = this.state; const contentState = editorState.getCurrentContent(); const contentStateWithEntity = contentState.createEntity('image', 'IMMUTABLE', content); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity }); const xx = AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, '[图片]'); if (!('value' in this.props)) { this.setState({ editorState: xx, modalVisible: false }); } this.triggerChange({ editorState: xx, modalVisible: false }); }; callback = (content) => { const { editorState } = this.state; // 获取contentState const contentState = editorState.getCurrentContent(); // 在contentState上新建entity const contentStateWithEntity = contentState.createEntity( 'LINK', // 'MUTABLE', // 'IMMUTABLE', 'SEGMENTED', { url: content.url }, ); // 获取到刚才新建的entity const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); // 把带有entity的contentState设置到editorState上 const newEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity }); // 把entity和选中的内容对应 const xx = RichUtils.toggleLink(newEditorState, newEditorState.getSelection(), entityKey); if (!('value' in this.props)) { this.setState({ editorState: xx, modalVisible: false }); } this.triggerChange({ editorState: xx, modalVisible: false }); }; modelContent = () => { if (this.state.fnKey == 'link') { return <LinkForm callback={this.callback} />; } else if (this.state.fnKey == 'image') { return <PicForm callback={this.callback2} />; } else if (this.state.fnKey == 'video') { return <VideoForm callback={this.callback3} />; } else if (this.state.fnKey == 'formula') { return <FormulaForm callback={this.callback2} />; } else if (this.state.fnKey == 'attr') { return <AttrForm callback={this.callback4} />; } }; deleteBlock = (editKey) => { const oldObj = changeFromDraftState2(this.state.editorState); const blocks = JSON.parse(JSON.stringify(oldObj)); const bs = blocks.blocks; var b; var j; for (var i = 0; i < bs.length; i++) { if (bs[i].key == editKey) { b = bs[i]; j = i; break; } } const entityKey = b.entityRanges[0].key + ''; delete blocks.entityMap[entityKey]; blocks.blocks.splice(j, 1); const editorState = changeToDraftState2(blocks); if (!('value' in this.props)) { this.setState({ editorState }); } this.triggerChange({ editorState }); }; render() { const btns = [ { label: 'Bold', style: 'BOLD', type: 'inline', icon: b }, { label: 'Italic', style: 'ITALIC', type: 'inline', icon: it }, { label: 'line1', type: 'line', icon: ii }, { label: 'H1', style: 'header-one', type: 'block', icon: h }, /* {label: 'H2', style: 'header-two',type:"block",icon:b}, {label: 'H3', style: 'header-three',type:"block",icon:b}, {label: 'H4', style: 'header-four',type:"block",icon:b}, {label: 'H5', style: 'header-five',type:"block",icon:b}, {label: 'H6', style: 'header-six',type:"block",icon:b}, */ { label: 'Blockquote', style: 'blockquote', type: 'block', icon: blockQu }, { label: 'Code Block', style: 'code-block', type: 'block', icon: code }, { label: 'UL', style: 'unordered-list-item', type: 'block', icon: prjectNum }, { label: 'OL', style: 'ordered-list-item', type: 'block', icon: num }, { label: 'line2', type: 'line', icon: ii }, { label: 'lk', type: 'fn', icon: lk, fn: this.insertLink }, { label: 'pic', type: 'fn', icon: pic, fn: this.insertPic }, { label: 'video', type: 'fn', icon: video, fn: this.insertVideo }, /* { label: 'sum', type: "fn", icon: sum, fn: this.insertFormula }, */ { label: 'att', type: 'fn', icon: attrs, fn: this.insertAttr }, { label: 'line3', type: 'line', icon: ii }, { label: 'Del', style: 'STRIKETHROUGH', type: 'inline', icon: t }, ]; const { modalVisible } = this.state; return ( <div> <StyleControls btns={btns} editorState={this.state.editorState} toggleInlineStyle={this.toggleInlineStyle} toggleBlockType={this.toggleBlockType} /> <div className={styles.basic} style={{ height: '400px', overflowY: 'auto', }} onClick={this.focusEditor}> <Editor ref={this.setEditor} blockStyleFn={getBlockStyle} customStyleMap={this.state.styleMap} blockRendererFn={MyBlockRender.bind( this, false, null, this.props.editBlock, this.deleteBlock, )} editorState={this.state.editorState} placeholder={this.props.placeholder || '请输入'} onChange={this.onChange} /> </div> <Modal width="700px" maskClosable={false} destroyOnClose title={'插入对象'} visible={modalVisible} footer={null} onCancel={() => this.setState({ modalVisible: false })}> {this.modelContent()} </Modal> </div> ); } } class StyleButton extends React.Component { constructor() { super(); this.onToggle = (e) => { e.preventDefault(); this.props.onToggle(this.props.style); }; } render() { const { label } = this.props; return ( <a key={label.label} onMouseDown={this.onToggle} onClick={label.fn} style={{ marginLeft: 20 }}> <img src={label.icon} /> </a> ); } } @Form.create() class LinkForm extends React.Component { constructor(props) { super(props); } submit = (e) => { e.preventDefault(); const { form } = this.props; form.validateFields((err, fieldsValue) => { if (err) return; this.props.callback(fieldsValue); }); }; render() { const { form } = this.props; return ( <Form onSubmit={this.submit}> <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="链接地址"> {form.getFieldDecorator('url', { rules: [{ required: true, message: '请输入链接名称' }], })(<Input placeholder="请输入链接名称" />)} </FormItem> <Button type="primary" htmlType="submit"> 确定 </Button> </Form> ); } } @Form.create() class AttrForm extends React.Component { constructor(props) { super(props); this.state = { url: null, name: null, }; } submit = (e) => { e.preventDefault(); const { form } = this.props; form.validateFields((err, fieldsValue) => { if (err) return; var src = fieldsValue.src; src = src[src.length - 1].response; const params = { ...fieldsValue, src, name: this.state.name, }; this.props.callback(params); }); }; normFile = (e) => { if (Array.isArray(e)) { return e; } return e && e.fileList; }; onChange = (info) => { if (info.file.status === 'done') { message.success(`文件上传成功`); this.setState({ url: info.file.response, name: info.file.name }); } else if (info.file.status === 'error') { message.error(`文件上传失败`); } }; render() { const { form } = this.props; const { url, name } = this.state; return ( <Form onSubmit={this.submit}> <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="上传附件"> {form.getFieldDecorator('src', { valuePropName: 'fileList', getValueFromEvent: this.normFile, })( <Upload.Dragger onChange={this.onChange} showUploadList={false} name="file" action={config.uploadUrl} headers={getSassApiHeader()} data={{ token: getToken(), }} onChangemultiple={false} style={{ padding: 0 }}> {url ? ( <a href={queryFileUrl(url)} target="_blank"> {name} </a> ) : ( <p className="ant-upload-drag-icon" style={{ marginBottom: 0, height: 400 }}> <Icon type="video" /> </p> )} </Upload.Dragger>, )} </FormItem> <Button type="primary" htmlType="submit"> 确定 </Button> </Form> ); } } @Form.create() class VideoForm extends React.Component { constructor(props) { super(props); this.state = { url: null, }; } submit = (e) => { e.preventDefault(); const { form } = this.props; form.validateFields((err, fieldsValue) => { if (err) return; var src = fieldsValue.src; src = src[src.length - 1].response; const params = { ...fieldsValue, src, }; this.props.callback(params); }); }; normFile = (e) => { if (Array.isArray(e)) { return e; } return e && e.fileList; }; beforeUpload = (file) => { const isJPG = file.type === 'video/mp4'; if (!isJPG) { message.error('请上传mp4格式文件!'); } return isJPG; }; onChange = (info) => { if (info.file.status === 'done') { message.success(`视频上传成功`); this.setState({ url: info.file.response }); } else if (info.file.status === 'error') { message.error(`视频上传失败`); } }; render() { const { form } = this.props; const { url } = this.state; return ( <Form onSubmit={this.submit}> <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="上传视频"> {form.getFieldDecorator('src', { valuePropName: 'fileList', getValueFromEvent: this.normFile, })( <Upload.Dragger beforeUpload={this.beforeUpload} onChange={this.onChange} showUploadList={false} name="file" action={config.uploadUrl} headers={getSassApiHeader()} data={{ token: getToken(), }} onChangemultiple={false} style={{ padding: 0 }}> {url ? ( <video src={queryFileUrl(url)} controls="controls"> 您的浏览器不支持 video 标签。 </video> ) : ( <p className="ant-upload-drag-icon" style={{ marginBottom: 0, height: 400 }}> <Icon type="video" /> </p> )} </Upload.Dragger>, )} </FormItem> <Button type="primary" htmlType="submit"> 确定 </Button> </Form> ); } } @Form.create() class PicForm extends React.Component { constructor(props) { super(props); this.state = { url: null, }; } beforeUpload = (file) => { const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'; if (!isJPG) { message.error('请上传jpg或者png格式文件!'); } return isJPG; }; submit = (e) => { e.preventDefault(); const { form } = this.props; form.validateFields((err, fieldsValue) => { if (err) return; var src = fieldsValue.src; src = src[src.length - 1].response; const params = { ...fieldsValue, src, }; this.props.callback(params); }); }; normFile = (e) => { if (Array.isArray(e)) { return e; } return e && e.fileList; }; onChange = (info) => { if (info.file.status === 'done') { message.success(`图片上传成功`); this.setState({ url: info.file.response }); } else if (info.file.status === 'error') { message.error(`图片上传失败`); } }; render() { const { form } = this.props; const { url } = this.state; return ( <Form onSubmit={this.submit}> <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="上传图片"> {form.getFieldDecorator('src', { valuePropName: 'fileList', getValueFromEvent: this.normFile, })( <Upload.Dragger beforeUpload={this.beforeUpload} showUploadList={false} name="file" action={config.uploadUrl} onChange={this.onChange} headers={getSassApiHeader()} data={{ token: getToken(), }} multiple={false} style={{ padding: 0 }}> {url ? ( <img src={queryFileUrl(url)} style={{ height: 400, width: '100%' }} /> ) : ( <p className="ant-upload-drag-icon" style={{ marginBottom: 0, height: 400 }}> <Icon type="video" /> </p> )} </Upload.Dragger>, )} </FormItem> <Button type="primary" htmlType="submit"> 确定 </Button> </Form> ); } } @Form.create() class FormulaForm extends React.Component { constructor(props) { super(props); window.latex = (gs) => { this.props.callback({ src: '/latex?code=' + encodeURI(gs), description: '' }); }; } render() { const { form } = this.props; return ( <iframe src="/gongshi/gongshi.html" style={{ width: '100%', minHeight: '400px', border: 'solid 1px #0062d5' }} /> ); } }