Commit 3649ea48 authored by 周宏民's avatar 周宏民

fix: 修改

parent 3437a61e
...@@ -308,13 +308,13 @@ ...@@ -308,13 +308,13 @@
}, },
{ {
"path": "packages/base-components/RichText/lib/**/*.js", "path": "packages/base-components/RichText/lib/**/*.js",
"limit": "6 s", "limit": "2 s",
"webpack": false, "webpack": false,
"running": false "running": false
}, },
{ {
"path": "packages/base-components/RichText/es/**/*.js", "path": "packages/base-components/RichText/es/**/*.js",
"limit": "6 s", "limit": "2 s",
"webpack": false, "webpack": false,
"running": false "running": false
} }
......
/*
* @Title:富文本编辑器
* @Author: hongmye
* @Date: 2022-03-01 14:23:55
*/
/**
* 1.引入组件 import RichText from '@/components/RichText';
* 示例:<RichText
content={this.state.content}
personList={this.state.personList}
placeholder={'placeholder属性值'}
onChange={val => {
this.setState({ content: val });
}}
onChangeFile={arr => {
this.setState({ fileList: arr });
}}
fileList={this.state.fileList}
projectId={19}
ref={this.myRichText}
/>
*
* 2.传递方法 onChange 每次更改内容回调
*
* 3.传值接收 可选值 projectId 项目id,根据项目id获取项目参与人员,
* 可选值 personList 人员列表 示例:[{userId:1,userName:'xxx'}]
* 可选值 config 框架wangEditor的配置参数
*
* 4.注意事项 projectId和personList只用传一个,projectId优先级高于personList
* content内容如果不是初始有的,可调用setHtml设置内容
*
* 2022-03-21新增图片预览,附件上传功能
* 新增方法:onChangeFile 每次附件更改回调 若不传则不显示附件上传按钮
* fileList 附件列表 示例:[{name:'xxx.jpg',type:'image/jpg',size:8192,path:'xxxx'}]
* 其中name和path是必传的,type为图片可以预览,其它类型文件直接下载
*
* 2022-04-29 修改@人员列表逻辑
* personList 传任务相关人员列表(如 创建、负责、跟进人),同时传入projectId,personList
* 下拉列表默认显示为任务相关人员,加项目人员(做了去重,任务相关人员在最上面)
* @搜索时,搜索全部人员
*/
import { Image, message, Spin } from 'antd';
import classNames from 'classnames';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
// import { request } from '@wisdom-utils/utils';
import request from 'umi-request';
import WangEditor from './wangEditor.js';
// import { API } from '@/api/service/workflow';
const API = {};
// import { appWork, projectManageService } from '@/api';
import FileListItem from './fileListItem';
import styles from './index.less';
let editor = null;
let range;
const selection = window.getSelection();
let startOffset;
let tempList = [];
let allPeople = []; // 全部人员
let selectPersonIndex;
let selectPerson = [];
const RichText = forwardRef((props, ref) => {
const [loading, setLoading] = useState(false);
const [zIndex, setZIndex] = useState(500);
// const [inputValue, setInputValue] = useState('');
const [selectIndex, setSelectIndex] = useState(null);
const [selectList, setSelectList] = useState([]);
const [selectSearchList, setSelectSearchList] = useState([]);
const [fileList, setFileList] = useState([]);
const [imgVisible, setImgVisible] = useState(false);
const [imgPreviewSrc, setImgPreviewSrc] = useState('');
const richTextRef = useRef();
const selectBoxRef = useRef();
const fileInputRef = useRef();
const getData = async () => {
// const { data } = await projectManageService.GetWorkHourUserList({ projectId: props.projectId });
// setSelectList(data);
};
// 获取全部人员信息
const getAllPeople = async () => {
// const res = await appWork.GetAllPersonnels();
// allPeople = res?.data?.data || [];
};
// 图片上传
const uploadImg = (file) => {
const formData = new FormData();
formData.append('file', file);
setLoading(true);
request({
url: API.POST_UPLOADERFILES,
method: 'POST',
data: formData,
})
.then((res) => {
if (!res.data) return;
const img = res.data.replace(/[\\ \/=]/g, '/');
const imgHtml = `
<img contenteditable="false" style="display: block;" width="50%" src="${API.GET_DOWNLOADFILES}?filePath=${img}" >
`;
// 已弃用(会出现插入图片后,光标消失的问题)
// const imgHtml = `
// <div class="RichText-image" contenteditable="false">
// <img class="RichText-image-img" src="${API.GET_DOWNLOADFILES}?filePath=${img}">
// <div class="RichText-image-mask" type="preview" >
// <div class="RichText-image-mask-info" type="preview">
// <span role="img" aria-label="eye" class="anticon anticon-eye">
// <svg viewBox="64 64 896 896" focusable="false" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true">
// <path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path>
// </svg>
// </span>
// 预览
// </div>
// </div>
// </div>`;
editor.cmd.do('insertHTML', imgHtml);
setLoading(false);
})
.catch((err) => {
setLoading(false);
});
};
const init = () => {
const { BtnMenu } = WangEditor;
editor = new WangEditor('#RichTextToolbar', '#RichTextContainer');
// 自定义菜单
const menuKey = 'fileMenuKey';
if (props.onChangeFile) {
class InsertABCMenu extends BtnMenu {
constructor(editor) {
const $elem = WangEditor.$(
`<div class="w-e-menu">
<i class="w-e-icon-link">
</i>
</div>`,
);
super($elem, editor);
}
// 菜单点击事件
clickHandler() {
// 触发选择文件
fileInputRef.current.click();
}
// 菜单激活状态
tryChangeActive() {
// this.active(); // 菜单激活
}
}
editor.menus.extend(menuKey, InsertABCMenu);
}
editor.config = Object.assign(
{},
editor.config,
{
placeholder: props.placeholder ?? '',
focus: false,
pasteFilterStyle: true, // 忽略粘贴样式
pasteIgnoreImg: true, // 忽略粘贴的图片
styleWithCSS: false,
zIndex: 500,
menus: [
'bold',
'fontSize',
'italic',
'underline',
'strikeThrough',
'foreColor',
'backColor',
'list',
'justify',
'table',
menuKey,
'undo',
'redo',
'image',
],
},
props.config || {},
);
setZIndex(Number(editor.config.zIndex));
// 内容变更
editor.config.onchange = (newHtml, e) => {
props.onChange(newHtml);
};
// 粘贴前置处理
editor.config.pasteTextHandle = (pasteStr) => pasteStr;
editor.config.onblur = (newHtml) => {
selectBoxRef.current.style.display = 'none';
};
// 点击事件
editor.txt.eventHooks.clickEvents.push((e) => {
// 图片预览
// 已弃用
if (e.target.getAttribute('type') === 'preview') {
const imgSrc = e.target?.parentNode?.parentNode
?.getElementsByTagName('img')?.[0]
?.getAttribute('src');
setImgPreviewSrc(imgSrc);
setImgVisible(true);
}
// 关闭选人的下拉框
selectBoxRef.current.style.display = 'none';
});
editor.txt.eventHooks.onPreviewEvents.push((link) => {
// 图片预览
if (link) {
setImgPreviewSrc(link);
setImgVisible(true);
}
});
editor.txt.eventHooks.imgClickEvents.push((e) => {
// 图片预览
// 已弃用
// let img = e?.selector?.getAttribute('src') || '';
// if (img) {
// setImgPreviewSrc(img);
// setImgVisible(true);
// }
});
// 粘贴图片上传
editor.txt.eventHooks.pasteEvents.push((e) => {
const file = e?.clipboardData?.items[0]?.getAsFile() || null;
if (!file) return;
uploadImg(file);
});
editor.create();
editor.txt.html(props.content || '');
richTextRef.current.onkeydown = keyDownEvent;
richTextRef.current.addEventListener('input', (e) => {
if (range) {
// 判断节点是否在选区及光标是否在@后面
const type = selection.containsNode(selection.getRangeAt(0).commonAncestorContainer, false);
if (!type || startOffset > selection.focusOffset) {
closeList();
return;
}
range.setEnd(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset);
// console.log('🚀 ~ range', range.toString(), tempList, selectList);
const str = range.toString() || '';
moveListBox();
handleChange(str, tempList);
}
if (e.data !== '@') return;
if (range) {
closeList();
}
range = document.createRange();
startOffset = selection.focusOffset;
range.setStart(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset);
selection.addRange(range);
moveListBox();
// 清空搜索
handleChange('', tempList);
});
};
// 跟据光标位置移动下拉框
const moveListBox = () => {
// 获取光标位置
const cursor = window?.getSelection()?.getRangeAt(0)?.getBoundingClientRect() || null;
const containerRect = document.querySelector('#RichText').getBoundingClientRect();
selectBoxRef.current.style.display = 'block';
selectBoxRef.current.style.left = `${parseInt(cursor.x - containerRect.x, 10) + 5}px`;
selectBoxRef.current.style.top = `${parseInt(cursor.y - containerRect.y, 10) + 25}px`;
};
// 键盘事件
const keyDownEvent = (evet) => {
// 上下方向键
if (evet.key === 'ArrowDown' || evet.key === 'ArrowUp') {
evet.preventDefault();
if (selectBoxRef.current?.style?.display === 'block') {
const max = selectBoxRef.current.querySelectorAll('.selectItem')?.length || 1000;
let val = selectPersonIndex;
if (evet.key === 'ArrowDown') {
if (!val && val != 0) {
val = 0;
} else {
val += 1;
}
}
if (evet.key === 'ArrowUp') val -= 1;
if (isNaN(val) || !val || val < 0) val = 0;
if (val > max - 1) val = max - 1;
selectPersonIndex = val;
setSelectIndex(selectPersonIndex);
}
}
if (evet.key === 'Enter') {
// 解决无法回车换行的bug
if (selectBoxRef.current.style.display === 'block' && selectSearchList.length) {
evet.preventDefault();
if (selectPerson[selectPersonIndex]) {
onSelect(selectPerson[selectPersonIndex]);
}
return false;
}
}
};
useEffect(() => {
richTextRef.current && richTextRef.current.removeEventListener('input', (e) => {});
init();
getAllPeople();
return () => {
richTextRef.current && richTextRef.current.removeEventListener('input', (e) => {});
editor && editor.destroy();
editor = null;
};
}, []);
useEffect(() => {
selectPersonIndex = null;
setSelectIndex(null);
selectPerson = selectSearchList || [];
}, [selectSearchList]);
useEffect(() => {
if (props.projectId) getData();
}, [props.projectId]);
useEffect(() => {
const keys = [];
const arr = [];
if (props.personList) {
props.personList.forEach((i) => {
i.userId = Number(i.userId);
if (!keys.includes(i.userId)) {
keys.push(i.userId);
arr.push(i);
}
});
}
if (selectList) {
selectList.forEach((i) => {
i.userId = Number(i.userId);
if (!keys.includes(i.userId)) {
arr.push(i);
keys.push(i.userId);
}
});
}
tempList = arr;
setSelectSearchList(arr);
}, [selectList, props.personList]);
// useEffect(() => {
// if (!props.projectId) {
// setSelectList(props.personList || []);
// }
// }, [props.personList]);
useEffect(() => {
setFileList(props.fileList);
}, [props.fileList]);
const getHtml = (val) => editor.txt.html();
// 获取文本,不含标签
const getText = (val) => editor.txt.text();
// 清除
const onClear = () => {
editor.txt.clear();
};
// 设置内容
const setHtml = (val) => {
editor.txt.html(val || '');
};
// 关闭人员下拉选框
const closeList = () => {
selectPersonIndex = null;
setSelectIndex(null);
selectBoxRef.current.style.display = 'none';
if (range) {
selection.removeRange(range);
range = null;
}
};
// @某人
const onSelect = (item) => {
if (range) {
range.deleteContents();
// 删除前一个@符号
editor.cmd.do('delete');
const _html = `<span><span data-userId="${item.userId}" data-type="person" >@${item.userName}</span><span>&nbsp;</span></span>`;
editor.cmd.do('insertElem', _html);
closeList();
selection.collapseToEnd();
}
};
// const onChange = val => {
// setInputValue(val.currentTarget.value);
// handleChange(val.currentTarget.value);
// };
let timer = null;
const filterList = (val, list) => {
if (!val) {
if (list.length === 0) {
selectBoxRef.current.style.display = 'none';
}
setSelectSearchList(list);
} else {
const arr = getArrayByName(val, allPeople);
if (arr.length === 0) {
selectBoxRef.current.style.display = 'none';
}
setSelectSearchList(arr);
}
};
const handleChange = (val, list) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
filterList(val, list);
}, 200);
// filterList(val, list);
};
/**
* 根据字符串模糊搜索返回符合条件的数据
* name 搜索字符串
* array 检索json数组
*/
const getArrayByName = (name, array) => {
const result = [];
array.forEach((i) => {
if (i.name.indexOf(name) != -1) result.push({ userName: i.name, userId: i.id, port: i.port });
});
return result;
};
const addFile = (e) => {
if (e.target) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
setLoading(true);
request({
url: API.POST_UPLOADERFILES,
method: 'POST',
data: formData,
})
.then((res) => {
if (res.data) {
const arr = [...fileList];
const url = res.data.replace(/[\\ \/=]/g, '/');
arr.unshift({
name: file.name,
type: file.type ? file.type.toLowerCase() : '',
size: file.size,
path: `${API.GET_DOWNLOADFILES}?filePath=${url}`,
});
// setFileList(arr);
props.onChangeFile(arr);
setLoading(false);
} else {
message.error(res.msg);
setLoading(false);
}
})
.catch((err) => {
setLoading(false);
});
}
};
const onDelFile = (item) => {
const arr = [];
fileList.forEach((i) => {
if (i.path !== item.path) {
arr.push(i);
}
});
// setFileList(arr);
props.onChangeFile(arr);
};
useImperativeHandle(ref, () => ({
setHtml,
onClear,
getHtml,
getText,
}));
return (
<div className={styles.RichText} id="RichText">
{loading ? (
<div className={styles.loadingWrap} style={{ zIndex: zIndex + 20 }}>
<Spin spinning={loading} />
</div>
) : null}
<div id="RichTextToolbar" className={styles.RichTextToolbar} />
<div ref={richTextRef} id="RichTextContainer" className={styles.RichTextContainer} />
<div className={styles.RichTextFileList}>
<FileListItem
list={fileList}
onDel={(val) => {
onDelFile(val);
}}
type="edit"
onPreview={(val) => {
if (!val) return;
setImgPreviewSrc(val.path);
setImgVisible(true);
}}
/>
</div>
<div
ref={selectBoxRef}
className={styles.selectBox}
style={{ maxWidth: '300px', minWidth: '150px', zIndex: zIndex + 10 }}
>
{selectSearchList.length ? (
<div className={styles.selectList}>
{selectSearchList.map((item, index) => (
<div
key={item.userId}
onClick={() => {
onSelect(item);
}}
className={classNames(
'selectItem',
styles.selectItem,
selectIndex === index ? styles.selectActiveItem : '',
)}
>
{item.userName}
</div>
))}
</div>
) : null}
</div>
<input
style={{ display: 'none' }}
type="file"
ref={fileInputRef}
onChange={(e) => {
addFile(e);
}}
name="file"
/>
<Image
width={200}
style={{ display: 'none' }}
src={imgPreviewSrc}
preview={{
visible: imgVisible,
src: imgPreviewSrc,
onVisibleChange: (value) => {
setImgVisible(value);
if (!value) setImgPreviewSrc('');
},
}}
/>
</div>
);
});
export default RichText;
...@@ -41,18 +41,17 @@ ...@@ -41,18 +41,17 @@
* 下拉列表默认显示为任务相关人员,加项目人员(做了去重,任务相关人员在最上面) * 下拉列表默认显示为任务相关人员,加项目人员(做了去重,任务相关人员在最上面)
* @搜索时,搜索全部人员 * @搜索时,搜索全部人员
*/ */
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react'; import { Image, message, Spin } from 'antd';
import { Spin, Input, Image, message } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
// import { request } from '@wisdom-utils/utils'; // import { request } from '@wisdom-utils/utils';
import request from 'umi-request'; import request from 'umi-request';
import WangEditor from './wangEditor.js'; import WangEditor from './wangEditor.js';
// import { API } from '@/api/service/workflow'; // import { API } from '@/api/service/workflow';
const API = {} const API = {};
// import { appWork, projectManageService } from '@/api'; // import { appWork, projectManageService } from '@/api';
import FileListItem from './fileListItem'; import FileListItem from './fileListItem';
import styles from './index.less'; import styles from './index.less';
let editor = null; let editor = null;
let range; let range;
const selection = window.getSelection(); const selection = window.getSelection();
...@@ -62,502 +61,496 @@ let allPeople = []; // 全部人员 ...@@ -62,502 +61,496 @@ let allPeople = []; // 全部人员
let selectPersonIndex; let selectPersonIndex;
let selectPerson = []; let selectPerson = [];
const RichText = forwardRef((props, ref) => { const RichText = forwardRef((props, ref) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [zIndex, setZIndex] = useState(500); const [zIndex, setZIndex] = useState(500);
// const [inputValue, setInputValue] = useState(''); // const [inputValue, setInputValue] = useState('');
const [selectIndex, setSelectIndex] = useState(null); const [selectIndex, setSelectIndex] = useState(null);
const [selectList, setSelectList] = useState([]); const [selectList, setSelectList] = useState([]);
const [selectSearchList, setSelectSearchList] = useState([]); const [selectSearchList, setSelectSearchList] = useState([]);
const [fileList, setFileList] = useState([]); const [fileList, setFileList] = useState([]);
const [imgVisible, setImgVisible] = useState(false); const [imgVisible, setImgVisible] = useState(false);
const [imgPreviewSrc, setImgPreviewSrc] = useState(''); const [imgPreviewSrc, setImgPreviewSrc] = useState('');
const richTextRef = useRef(); const richTextRef = useRef();
const selectBoxRef = useRef(); const selectBoxRef = useRef();
const fileInputRef = useRef(); const fileInputRef = useRef();
const getData = async () => {
const getData = async () => { // const { data } = await projectManageService.GetWorkHourUserList({ projectId: props.projectId });
// const { data } = await projectManageService.GetWorkHourUserList({ projectId: props.projectId }); // setSelectList(data);
// setSelectList(data); };
}; // 获取全部人员信息
// 获取全部人员信息 const getAllPeople = async () => {
const getAllPeople = async () => { // const res = await appWork.GetAllPersonnels();
// const res = await appWork.GetAllPersonnels(); // allPeople = res?.data?.data || [];
// allPeople = res?.data?.data || []; };
}; // 图片上传
// 图片上传 const uploadImg = (file) => {
const uploadImg = file => { const formData = new FormData();
const formData = new FormData(); formData.append('file', file);
formData.append('file', file); setLoading(true);
setLoading(true); request({
request({ url: API.POST_UPLOADERFILES,
url: API.POST_UPLOADERFILES, method: 'POST',
method: 'POST', data: formData,
data: formData, })
}) .then((res) => {
.then(res => { if (!res.data) return;
if (!res.data) return; const img = res.data.replace(/[\\ \/=]/g, '/');
const img = res.data.replace(/[\\ \/=]/g, '/'); const imgHtml = `
const imgHtml = ` <img contenteditable="false" style="display: block;" width="50%" src="${API.GET_DOWNLOADFILES}?filePath=${img}" >
<img contenteditable="false" style="display: block;" width="50%" src="${API.GET_DOWNLOADFILES
}?filePath=${img}" >
`; `;
// 已弃用(会出现插入图片后,光标消失的问题) // 已弃用(会出现插入图片后,光标消失的问题)
// const imgHtml = ` // const imgHtml = `
// <div class="RichText-image" contenteditable="false"> // <div class="RichText-image" contenteditable="false">
// <img class="RichText-image-img" src="${API.GET_DOWNLOADFILES}?filePath=${img}"> // <img class="RichText-image-img" src="${API.GET_DOWNLOADFILES}?filePath=${img}">
// <div class="RichText-image-mask" type="preview" > // <div class="RichText-image-mask" type="preview" >
// <div class="RichText-image-mask-info" type="preview"> // <div class="RichText-image-mask-info" type="preview">
// <span role="img" aria-label="eye" class="anticon anticon-eye"> // <span role="img" aria-label="eye" class="anticon anticon-eye">
// <svg viewBox="64 64 896 896" focusable="false" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true"> // <svg viewBox="64 64 896 896" focusable="false" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true">
// <path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path> // <path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path>
// </svg> // </svg>
// </span> // </span>
// 预览 // 预览
// </div> // </div>
// </div> // </div>
// </div>`; // </div>`;
editor.cmd.do('insertHTML', imgHtml); editor.cmd.do('insertHTML', imgHtml);
setLoading(false); setLoading(false);
}) })
.catch(err => { .catch((err) => {
setLoading(false); setLoading(false);
}); });
}; };
const init = () => { const init = () => {
const { BtnMenu } = WangEditor; const { BtnMenu } = WangEditor;
editor = new WangEditor('#RichTextToolbar', '#RichTextContainer'); editor = new WangEditor('#RichTextToolbar', '#RichTextContainer');
// 自定义菜单 // 自定义菜单
const menuKey = 'fileMenuKey'; const menuKey = 'fileMenuKey';
if (props.onChangeFile) { if (props.onChangeFile) {
class InsertABCMenu extends BtnMenu { class InsertABCMenu extends BtnMenu {
constructor(editor) { constructor(editor) {
const $elem = WangEditor.$( const $elem = WangEditor.$(
`<div class="w-e-menu"> `<div class="w-e-menu">
<i class="w-e-icon-link"> <i class="w-e-icon-link">
</i> </i>
</div>`, </div>`,
); );
super($elem, editor); super($elem, editor);
}
// 菜单点击事件
clickHandler() {
// 触发选择文件
fileInputRef.current.click();
}
// 菜单激活状态
tryChangeActive() {
// this.active(); // 菜单激活
}
}
editor.menus.extend(menuKey, InsertABCMenu);
} }
editor.config = Object.assign( // 菜单点击事件
{}, clickHandler() {
editor.config, // 触发选择文件
{ fileInputRef.current.click();
placeholder: props.placeholder ?? '', }
focus: false,
pasteFilterStyle: true, // 忽略粘贴样式
pasteIgnoreImg: true, // 忽略粘贴的图片
styleWithCSS: false,
zIndex: 500,
menus: [
'bold',
'fontSize',
'italic',
'underline',
'strikeThrough',
'foreColor',
'backColor',
'list',
'justify',
'table',
menuKey,
'undo',
'redo',
'image',
],
},
props.config || {},
);
setZIndex(Number(editor.config.zIndex));
// 内容变更
editor.config.onchange = (newHtml, e) => {
props.onChange(newHtml);
};
// 粘贴前置处理
editor.config.pasteTextHandle = pasteStr => pasteStr;
editor.config.onblur = newHtml => {
selectBoxRef.current.style.display = 'none';
};
// 点击事件
editor.txt.eventHooks.clickEvents.push(e => {
// 图片预览
// 已弃用
if (e.target.getAttribute('type') === 'preview') {
const imgSrc = e.target?.parentNode?.parentNode?.getElementsByTagName('img')?.[0]?.getAttribute('src');
setImgPreviewSrc(imgSrc);
setImgVisible(true);
}
// 关闭选人的下拉框
selectBoxRef.current.style.display = 'none';
});
editor.txt.eventHooks.onPreviewEvents.push(link => {
// 图片预览
if (link) {
setImgPreviewSrc(link);
setImgVisible(true);
}
});
editor.txt.eventHooks.imgClickEvents.push(e => { // 菜单激活状态
// 图片预览 tryChangeActive() {
// 已弃用 // this.active(); // 菜单激活
// let img = e?.selector?.getAttribute('src') || ''; }
// if (img) { }
// setImgPreviewSrc(img); editor.menus.extend(menuKey, InsertABCMenu);
// setImgVisible(true); }
// }
});
// 粘贴图片上传
editor.txt.eventHooks.pasteEvents.push(e => {
const file = e?.clipboardData?.items[0]?.getAsFile() || null;
if (!file) return;
uploadImg(file);
});
editor.create(); editor.config = Object.assign(
editor.txt.html(props.content || ''); {},
richTextRef.current.onkeydown = keyDownEvent; editor.config,
richTextRef.current.addEventListener('input', e => { {
if (range) { placeholder: props.placeholder ?? '',
// 判断节点是否在选区及光标是否在@后面 focus: false,
const type = selection.containsNode(selection.getRangeAt(0).commonAncestorContainer, false); pasteFilterStyle: true, // 忽略粘贴样式
if (!type || startOffset > selection.focusOffset) { pasteIgnoreImg: true, // 忽略粘贴的图片
closeList(); styleWithCSS: false,
return; zIndex: 500,
} menus: [
range.setEnd(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset); 'bold',
// console.log('🚀 ~ range', range.toString(), tempList, selectList); 'fontSize',
const str = range.toString() || ''; 'italic',
moveListBox(); 'underline',
handleChange(str, tempList); 'strikeThrough',
} 'foreColor',
if (e.data !== '@') return; 'backColor',
if (range) { 'list',
closeList(); 'justify',
} 'table',
range = document.createRange(); menuKey,
startOffset = selection.focusOffset; 'undo',
range.setStart(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset); 'redo',
selection.addRange(range); 'image',
],
},
props.config || {},
);
moveListBox(); setZIndex(Number(editor.config.zIndex));
// 清空搜索 // 内容变更
handleChange('', tempList); editor.config.onchange = (newHtml, e) => {
}); props.onChange(newHtml);
}; };
// 跟据光标位置移动下拉框 // 粘贴前置处理
const moveListBox = () => { editor.config.pasteTextHandle = (pasteStr) => pasteStr;
// 获取光标位置 editor.config.onblur = (newHtml) => {
const cursor = selectBoxRef.current.style.display = 'none';
window
?.getSelection()
?.getRangeAt(0)
?.getBoundingClientRect() || null;
const containerRect = document.querySelector('#RichText').getBoundingClientRect();
selectBoxRef.current.style.display = 'block';
selectBoxRef.current.style.left = `${parseInt(cursor.x - containerRect.x, 10) + 5}px`;
selectBoxRef.current.style.top = `${parseInt(cursor.y - containerRect.y, 10) + 25}px`;
}; };
// 键盘事件 // 点击事件
const keyDownEvent = evet => { editor.txt.eventHooks.clickEvents.push((e) => {
// 上下方向键 // 图片预览
if (evet.key === 'ArrowDown' || evet.key === 'ArrowUp') { // 已弃用
evet.preventDefault(); if (e.target.getAttribute('type') === 'preview') {
if (selectBoxRef.current?.style?.display === 'block') { const imgSrc = e.target?.parentNode?.parentNode
const max = selectBoxRef.current.querySelectorAll('.selectItem')?.length || 1000; ?.getElementsByTagName('img')?.[0]
let val = selectPersonIndex; ?.getAttribute('src');
if (evet.key === 'ArrowDown') { setImgPreviewSrc(imgSrc);
if (!val && val != 0) { setImgVisible(true);
val = 0; }
} else { // 关闭选人的下拉框
val += 1; selectBoxRef.current.style.display = 'none';
} });
}
if (evet.key === 'ArrowUp') val -= 1;
if (isNaN(val) || !val || val < 0) val = 0;
if (val > max - 1) val = max - 1;
selectPersonIndex = val;
setSelectIndex(selectPersonIndex);
}
}
if (evet.key === 'Enter') {
// 解决无法回车换行的bug
if (selectBoxRef.current.style.display === 'block' && selectSearchList.length) {
evet.preventDefault();
if (selectPerson[selectPersonIndex]) {
onSelect(selectPerson[selectPersonIndex]);
}
return false;
}
} editor.txt.eventHooks.onPreviewEvents.push((link) => {
}; // 图片预览
if (link) {
setImgPreviewSrc(link);
setImgVisible(true);
}
});
useEffect(() => { editor.txt.eventHooks.imgClickEvents.push((e) => {
richTextRef.current && richTextRef.current.removeEventListener('input', e => { }); // 图片预览
init(); // 已弃用
getAllPeople(); // let img = e?.selector?.getAttribute('src') || '';
return () => { // if (img) {
richTextRef.current && richTextRef.current.removeEventListener('input', e => { }); // setImgPreviewSrc(img);
// setImgVisible(true);
// }
});
// 粘贴图片上传
editor.txt.eventHooks.pasteEvents.push((e) => {
const file = e?.clipboardData?.items[0]?.getAsFile() || null;
if (!file) return;
uploadImg(file);
});
editor && editor.destroy(); editor.create();
editor = null; editor.txt.html(props.content || '');
}; richTextRef.current.onkeydown = keyDownEvent;
}, []); richTextRef.current.addEventListener('input', (e) => {
useEffect(() => { if (range) {
selectPersonIndex = null; // 判断节点是否在选区及光标是否在@后面
setSelectIndex(null); const type = selection.containsNode(selection.getRangeAt(0).commonAncestorContainer, false);
selectPerson = selectSearchList || []; if (!type || startOffset > selection.focusOffset) {
}, [selectSearchList]); closeList();
useEffect(() => { return;
if (props.projectId) getData();
}, [props.projectId]);
useEffect(() => {
const keys = [];
const arr = [];
if (props.personList) {
props.personList.forEach(i => {
i.userId = Number(i.userId);
if (!keys.includes(i.userId)) {
keys.push(i.userId);
arr.push(i);
}
});
}
if (selectList) {
selectList.forEach(i => {
i.userId = Number(i.userId);
if (!keys.includes(i.userId)) {
arr.push(i);
keys.push(i.userId);
}
});
} }
range.setEnd(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset);
// console.log('🚀 ~ range', range.toString(), tempList, selectList);
const str = range.toString() || '';
moveListBox();
handleChange(str, tempList);
}
if (e.data !== '@') return;
if (range) {
closeList();
}
range = document.createRange();
startOffset = selection.focusOffset;
range.setStart(selection.getRangeAt(0).commonAncestorContainer, selection.focusOffset);
selection.addRange(range);
tempList = arr; moveListBox();
setSelectSearchList(arr); // 清空搜索
}, [selectList, props.personList]); handleChange('', tempList);
// useEffect(() => { });
// if (!props.projectId) { };
// setSelectList(props.personList || []); // 跟据光标位置移动下拉框
// } const moveListBox = () => {
// }, [props.personList]); // 获取光标位置
useEffect(() => { const cursor = window?.getSelection()?.getRangeAt(0)?.getBoundingClientRect() || null;
setFileList(props.fileList); const containerRect = document.querySelector('#RichText').getBoundingClientRect();
}, [props.fileList]); selectBoxRef.current.style.display = 'block';
const getHtml = val => editor.txt.html(); selectBoxRef.current.style.left = `${parseInt(cursor.x - containerRect.x, 10) + 5}px`;
// 获取文本,不含标签 selectBoxRef.current.style.top = `${parseInt(cursor.y - containerRect.y, 10) + 25}px`;
const getText = val => editor.txt.text(); };
// 清除 // 键盘事件
const onClear = () => { const keyDownEvent = (evet) => {
editor.txt.clear(); // 上下方向键
}; if (evet.key === 'ArrowDown' || evet.key === 'ArrowUp') {
// 设置内容 evet.preventDefault();
const setHtml = val => { if (selectBoxRef.current?.style?.display === 'block') {
editor.txt.html(val || ''); const max = selectBoxRef.current.querySelectorAll('.selectItem')?.length || 1000;
}; let val = selectPersonIndex;
// 关闭人员下拉选框 if (evet.key === 'ArrowDown') {
const closeList = () => { if (!val && val != 0) {
selectPersonIndex = null; val = 0;
setSelectIndex(null); } else {
selectBoxRef.current.style.display = 'none'; val += 1;
if (range) { }
selection.removeRange(range);
range = null;
}
};
// @某人
const onSelect = item => {
if (range) {
range.deleteContents();
// 删除前一个@符号
editor.cmd.do('delete');
const _html = `<span><span data-userId="${item.userId}" data-type="person" >@${item.userName
}</span><span>&nbsp;</span></span>`;
editor.cmd.do('insertElem', _html);
closeList();
selection.collapseToEnd();
} }
}; if (evet.key === 'ArrowUp') val -= 1;
// const onChange = val => { if (isNaN(val) || !val || val < 0) val = 0;
// setInputValue(val.currentTarget.value); if (val > max - 1) val = max - 1;
// handleChange(val.currentTarget.value); selectPersonIndex = val;
// }; setSelectIndex(selectPersonIndex);
let timer = null; }
const filterList = (val, list) => { }
if (!val) { if (evet.key === 'Enter') {
if (list.length === 0) { // 解决无法回车换行的bug
selectBoxRef.current.style.display = 'none'; if (selectBoxRef.current.style.display === 'block' && selectSearchList.length) {
} evet.preventDefault();
setSelectSearchList(list); if (selectPerson[selectPersonIndex]) {
} else { onSelect(selectPerson[selectPersonIndex]);
const arr = getArrayByName(val, allPeople);
if (arr.length === 0) {
selectBoxRef.current.style.display = 'none';
}
setSelectSearchList(arr);
} }
return false;
}
}
};
useEffect(() => {
richTextRef.current && richTextRef.current.removeEventListener('input', (e) => {});
init();
getAllPeople();
return () => {
richTextRef.current && richTextRef.current.removeEventListener('input', (e) => {});
editor && editor.destroy();
editor = null;
}; };
const handleChange = (val, list) => { }, []);
if (timer) { useEffect(() => {
clearTimeout(timer); selectPersonIndex = null;
setSelectIndex(null);
selectPerson = selectSearchList || [];
}, [selectSearchList]);
useEffect(() => {
if (props.projectId) getData();
}, [props.projectId]);
useEffect(() => {
const keys = [];
const arr = [];
if (props.personList) {
props.personList.forEach((i) => {
i.userId = Number(i.userId);
if (!keys.includes(i.userId)) {
keys.push(i.userId);
arr.push(i);
} }
timer = setTimeout(() => { });
timer = null; }
filterList(val, list); if (selectList) {
}, 200); selectList.forEach((i) => {
// filterList(val, list); i.userId = Number(i.userId);
}; if (!keys.includes(i.userId)) {
/** arr.push(i);
* 根据字符串模糊搜索返回符合条件的数据 keys.push(i.userId);
* name 搜索字符串
* array 检索json数组
*/
const getArrayByName = (name, array) => {
const result = [];
array.forEach(i => {
if (i.name.indexOf(name) != -1) result.push({ userName: i.name, userId: i.id, port: i.port });
});
return result;
};
const addFile = e => {
if (e.target) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
setLoading(true);
request({
url: API.POST_UPLOADERFILES,
method: 'POST',
data: formData,
})
.then(res => {
if (res.data) {
const arr = [...fileList];
const url = res.data.replace(/[\\ \/=]/g, '/');
arr.unshift({
name: file.name,
type: file.type ? file.type.toLowerCase() : '',
size: file.size,
path: `${API.GET_DOWNLOADFILES}?filePath=${url}`,
});
// setFileList(arr);
props.onChangeFile(arr);
setLoading(false);
} else {
message.error(res.msg);
setLoading(false);
}
})
.catch(err => {
setLoading(false);
});
} }
}; });
const onDelFile = item => { }
const arr = [];
fileList.forEach(i => { tempList = arr;
if (i.path !== item.path) { setSelectSearchList(arr);
arr.push(i); }, [selectList, props.personList]);
} // useEffect(() => {
// if (!props.projectId) {
// setSelectList(props.personList || []);
// }
// }, [props.personList]);
useEffect(() => {
setFileList(props.fileList);
}, [props.fileList]);
const getHtml = (val) => editor.txt.html();
// 获取文本,不含标签
const getText = (val) => editor.txt.text();
// 清除
const onClear = () => {
editor.txt.clear();
};
// 设置内容
const setHtml = (val) => {
editor.txt.html(val || '');
};
// 关闭人员下拉选框
const closeList = () => {
selectPersonIndex = null;
setSelectIndex(null);
selectBoxRef.current.style.display = 'none';
if (range) {
selection.removeRange(range);
range = null;
}
};
// @某人
const onSelect = (item) => {
if (range) {
range.deleteContents();
// 删除前一个@符号
editor.cmd.do('delete');
const _html = `<span><span data-userId="${item.userId}" data-type="person" >@${item.userName}</span><span>&nbsp;</span></span>`;
editor.cmd.do('insertElem', _html);
closeList();
selection.collapseToEnd();
}
};
// const onChange = val => {
// setInputValue(val.currentTarget.value);
// handleChange(val.currentTarget.value);
// };
let timer = null;
const filterList = (val, list) => {
if (!val) {
if (list.length === 0) {
selectBoxRef.current.style.display = 'none';
}
setSelectSearchList(list);
} else {
const arr = getArrayByName(val, allPeople);
if (arr.length === 0) {
selectBoxRef.current.style.display = 'none';
}
setSelectSearchList(arr);
}
};
const handleChange = (val, list) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
timer = null;
filterList(val, list);
}, 200);
// filterList(val, list);
};
/**
* 根据字符串模糊搜索返回符合条件的数据
* name 搜索字符串
* array 检索json数组
*/
const getArrayByName = (name, array) => {
const result = [];
array.forEach((i) => {
if (i.name.indexOf(name) != -1) result.push({ userName: i.name, userId: i.id, port: i.port });
});
return result;
};
const addFile = (e) => {
if (e.target) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
setLoading(true);
request({
url: API.POST_UPLOADERFILES,
method: 'POST',
data: formData,
})
.then((res) => {
if (res.data) {
const arr = [...fileList];
const url = res.data.replace(/[\\ \/=]/g, '/');
arr.unshift({
name: file.name,
type: file.type ? file.type.toLowerCase() : '',
size: file.size,
path: `${API.GET_DOWNLOADFILES}?filePath=${url}`,
});
// setFileList(arr);
props.onChangeFile(arr);
setLoading(false);
} else {
message.error(res.msg);
setLoading(false);
}
})
.catch((err) => {
setLoading(false);
}); });
// setFileList(arr); }
props.onChangeFile(arr); };
}; const onDelFile = (item) => {
useImperativeHandle(ref, () => ({ const arr = [];
setHtml, fileList.forEach((i) => {
onClear, if (i.path !== item.path) {
getHtml, arr.push(i);
getText, }
})); });
return ( // setFileList(arr);
<div className={styles.RichText} id="RichText"> props.onChangeFile(arr);
{loading ? ( };
<div className={styles.loadingWrap} style={{ zIndex: zIndex + 20 }}> useImperativeHandle(ref, () => ({
<Spin spinning={loading} /> setHtml,
</div> onClear,
) : null} getHtml,
<div id="RichTextToolbar" className={styles.RichTextToolbar} /> getText,
<div ref={richTextRef} id="RichTextContainer" className={styles.RichTextContainer} /> }));
<div className={styles.RichTextFileList}> return (
<FileListItem <div className={styles.RichText} id="RichText">
list={fileList} {loading ? (
onDel={val => { <div className={styles.loadingWrap} style={{ zIndex: zIndex + 20 }}>
onDelFile(val); <Spin spinning={loading} />
}}
type="edit"
onPreview={val => {
if (!val) return;
setImgPreviewSrc(val.path);
setImgVisible(true);
}}
/>
</div>
<div
ref={selectBoxRef}
className={styles.selectBox}
style={{ maxWidth: '300px', minWidth: '150px', zIndex: zIndex + 10 }}
>
{selectSearchList.length ? (
<div className={styles.selectList}>
{selectSearchList.map((item, index) => (
<div
key={item.userId}
onClick={() => {
onSelect(item);
}}
className={classNames(
'selectItem',
styles.selectItem,
selectIndex === index ? styles.selectActiveItem : '',
)}
>
{item.userName}
</div>
))}
</div>
) : null}
</div>
<input
style={{ display: 'none' }}
type="file"
ref={fileInputRef}
onChange={e => {
addFile(e);
}}
name="file"
/>
<Image
width={200}
style={{ display: 'none' }}
src={imgPreviewSrc}
preview={{
visible: imgVisible,
src: imgPreviewSrc,
onVisibleChange: value => {
setImgVisible(value);
if (!value) setImgPreviewSrc('');
},
}}
/>
</div> </div>
); ) : null}
<div id="RichTextToolbar" className={styles.RichTextToolbar} />
<div ref={richTextRef} id="RichTextContainer" className={styles.RichTextContainer} />
<div className={styles.RichTextFileList}>
<FileListItem
list={fileList}
onDel={(val) => {
onDelFile(val);
}}
type="edit"
onPreview={(val) => {
if (!val) return;
setImgPreviewSrc(val.path);
setImgVisible(true);
}}
/>
</div>
<div
ref={selectBoxRef}
className={styles.selectBox}
style={{ maxWidth: '300px', minWidth: '150px', zIndex: zIndex + 10 }}
>
{selectSearchList.length ? (
<div className={styles.selectList}>
{selectSearchList.map((item, index) => (
<div
key={item.userId}
onClick={() => {
onSelect(item);
}}
className={classNames(
'selectItem',
styles.selectItem,
selectIndex === index ? styles.selectActiveItem : '',
)}
>
{item.userName}
</div>
))}
</div>
) : null}
</div>
<input
style={{ display: 'none' }}
type="file"
ref={fileInputRef}
onChange={(e) => {
addFile(e);
}}
name="file"
/>
<Image
width={200}
style={{ display: 'none' }}
src={imgPreviewSrc}
preview={{
visible: imgVisible,
src: imgPreviewSrc,
onVisibleChange: (value) => {
setImgVisible(value);
if (!value) setImgPreviewSrc('');
},
}}
/>
</div>
);
}); });
export default RichText; export default RichText;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment