diff --git a/components/braftEditor/common.tsx b/components/braftEditor/common.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fa7cbec9b2c81c9cd33e0ebb1f8a594ca9cfae75 --- /dev/null +++ b/components/braftEditor/common.tsx @@ -0,0 +1,48 @@ +import request from '@/utils/request'; + + +// 文件上传获取token +export function getToken() { + return request('/qiniu/token', { method: 'get', prefix: '/adminApi' }); +} + + +/** + * + * @param len 随机树位数 默认6位 + * @param type 随机数集类型:n纯数字 l纯小写字母 u纯大写字母 lu大小写字母 默认数字字母 + * @param extra 随机数集扩展 可加其他字符 + * @returns {string} 返回随机字符串 + */ + +export function getRandom(len = 6, type?:string, extra?:string):string { + const num = '0123456789'; + const letter = 'abcdefghigklmnopqrstuvwxyz'; + const upLetter = letter.toUpperCase(); + let str = num; + let result = ''; + switch (type) { + case 'n': + str = num; + break; + case 'l': + str = letter; + break; + case 'u': + str = upLetter; + break; + case 'lu': + str = letter + upLetter; + break; + default: + str = num + letter + upLetter; + break; + } + if (extra && typeof extra === 'string') { + str += extra; + } + for (let i = 0; i < len; i += 1) { + result += str[Math.floor(Math.random() * str.length)]; + } + return result; +} diff --git a/components/braftEditor/index.tsx b/components/braftEditor/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c58627d78c261a4a6aaea25c0d43e9a3683aab78 --- /dev/null +++ b/components/braftEditor/index.tsx @@ -0,0 +1,130 @@ +/* eslint-disable react/no-access-state-in-setstate */ +import React, { useState, useRef, useEffect, useImperativeHandle } from 'react'; +// 引入编辑器组件 +import BraftEditor from 'braft-editor'; +import ColorPicker from 'braft-extensions/dist/color-picker'; +import { ContentUtils } from 'braft-utils'; +import { Progress } from 'antd'; +import { PropertySafetyFilled } from '@ant-design/icons'; +import UploadQIniu from './uploadQIniu'; +import Preview from './preview'; +// 引入编辑器样式 +import 'braft-editor/dist/index.css'; +import 'braft-extensions/dist/color-picker.css'; +import styles from './style.less'; + +// 颜色色盘 +BraftEditor.use(ColorPicker({ + includeEditors: ['editor-with-color-picker'], + theme: 'light', // 支持dark和light两种主题,默认为dark +})); + +// 默认不显示的控件 +const excludeControlsDefault = ['emoji', 'blockquote', 'code', 'media']; + +interface IObj { + [propsName: string]: any +} +interface IProps { + value?: string; // html + language?: 'zh' | 'en'; + cRef?: any; + excludeControls?: string[]; // 不需要显示的控件 + onChange?: Function +} + +const EditorDemo: React.FC = ({ value, language = 'zh', onChange, cRef, excludeControls = excludeControlsDefault }) => { + // 创建一个空的editorState作为初始值 + const [editorState, setEditorState] = useState(BraftEditor.createEditorState(null)); + const [percent, setPercent] = useState(0); // 进度条 + const previewRef = useRef(null); + + useEffect(() => { + // 使用BraftEditor.createEditorState将html字符串转换为编辑器需要的editorStat + setEditorState(BraftEditor.createEditorState(value)); + }, [value]); + + + // 获取HTML + const getHtml = () => { + // 直接调用editorState.toHTML()来获取HTML格式的内容 + const htmlContent = editorState.toHTML(); + return htmlContent; + }; + + + // change事件 + const handleEditorChange = (editorState: any) => { + setEditorState(editorState); + if (onChange) { + const htmlContent = editorState.toHTML(); + onChange(editorState, htmlContent); + } + }; + + + // 图片上传进度 + const percentCbk = (val: number) => { + setPercent(val); + }; + + // 图片上传进度条 + const componentBelowControlBar = () => { + return (percent > 0 && percent < 100) ? ( { return `图片上传中${parseInt(p, 10)}%`; }} + className={styles.progress} + />) : null; + }; + + // 图片上传成功,获取图片信息 + const successCbk = (obj: IObj) => { + const { url } = obj || {}; + setEditorState(ContentUtils.insertMedias(editorState, [{ + type: 'IMAGE', + url, + }])); + }; + + // 预览方法 + const previewMth = () => { + previewRef && previewRef.current && previewRef.current.openFun(); + }; + + // 图片上传组件 + const extendControls = [ + { + key: 'antd-uploader', + type: 'component', + component: ( + + ), + }, + ]; + + // 向外抛出数据 + useImperativeHandle(cRef, () => { + return { + getHtml, + previewMth, + }; + }); + return ( +
+ + +
+ ); +}; + + +export default EditorDemo; diff --git a/components/braftEditor/preview.tsx b/components/braftEditor/preview.tsx new file mode 100644 index 0000000000000000000000000000000000000000..23ef00d299ef32f804dcd11e21b2154d79677266 --- /dev/null +++ b/components/braftEditor/preview.tsx @@ -0,0 +1,40 @@ +import React, { useState, useImperativeHandle } from 'react'; +import { Modal } from 'antd'; + +interface IProps { + htmlStr?: string; + cRef?: any; + language?: 'zh' | 'en'; +} +const Preview:React.FC = ({ htmlStr, cRef, language = 'zh' }) => { + const [visible, setVisible] = useState(false); + + useImperativeHandle(cRef, () => { + return { + openFun: () => { + setVisible(true); + }, + }; + }); + + + const closeFun = () => { + setVisible(false); + }; + + return ( + +
+ + ); +}; + +export default Preview; diff --git a/components/braftEditor/style.less b/components/braftEditor/style.less new file mode 100644 index 0000000000000000000000000000000000000000..47bf428f10bd21b7813887d3112fc2b7e112b346 --- /dev/null +++ b/components/braftEditor/style.less @@ -0,0 +1,23 @@ + +.box{ + width: 100%; + // border: 1px solid #333; + border-radius:4px; +border:1px solid rgba(225,225,225,1); +} + + +.progress{ + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + :global .ant-progress-text{ + width: auto; + padding: 0 10px; + } +} + +.upload{ + margin-top: 0 !important; +} diff --git a/components/braftEditor/uploadQIniu.tsx b/components/braftEditor/uploadQIniu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16a475e22d0ea3803983d2c5da5e99e9288025c4 --- /dev/null +++ b/components/braftEditor/uploadQIniu.tsx @@ -0,0 +1,99 @@ +/* eslint-disable no-unused-expressions */ +import React, { useState } from 'react'; +import { Button, message, Upload } from 'antd'; +import moment from 'moment'; +import { PictureOutlined } from '@ant-design/icons'; +import classnames from 'classnames'; +import styles from './style.less'; +import { getToken, getRandom } from './common'; + +const maxSizeDefault = 20; // 图片最大限制 +interface IProps { + maxSize?: number; // 最大限制 默认20M + successCbk?: Function, // 成功回调 + percentCbk?: Function, // 进度条回调 + language?: 'zh' | 'en'; +} + +const UploadCom:React.FC = ({ maxSize = maxSizeDefault, successCbk, percentCbk, language = 'zh' }) => { + const [token, setToken] = useState(''); + const [domain, setDomain] = useState(''); + + // 获取七牛上传token + const getTokenFun = async () => { + const response = await getToken(); + if (response && response.success && response.data) { + setToken(response.data.token); + setDomain(response.data.domain); + } + }; + + // 自定义七牛文件唯一key + const getKey = (file) => { + if (!file) return; + const suffix = file.name.match(/\.\w+$/)[0]; + const rand6 = getRandom(); + const time = moment().format('YYYYMMDDHHmmss'); + return time + rand6 + suffix; + }; + + // 七牛上传额外数据,token和key + const getData = (file) => { + return { + token, + key: getKey(file), + }; + }; + + const beforeUpload = async ({ size }) => { + const fileMaxSizeB = maxSize * 1024 * 1024; + if (size > fileMaxSizeB) { + message.warning(language === 'zh' ? `上传图片超过最大限制${maxSize}M` : `limit ${maxSize}M`); + // eslint-disable-next-line prefer-promise-reject-errors + return Promise.reject(false); + } + await getTokenFun(); + return true; + }; + + const onChange = ({ file, fileList, event }) => { + if (file.status === 'done' && file.response) { + const data = file.response || {}; + const url = `${`https://${domain}`}/${data.key}`; + // 上传成功后处理为服务端数据结构 + const itemObj = { + uid: file.uid, + name: file.name, + value: url, + size: file.size, + url, + domain, + }; + successCbk && successCbk(itemObj); + } else if (file.status === 'error') { + message.error('upload error'); + percentCbk && percentCbk(0); + } + percentCbk && event && event.percent && percentCbk(event.percent); + }; + + + return ( + + + + ); +}; + +export default UploadCom; +// className="control-item button upload-button"