├── index.js ├── assets └── utf8-php.zip ├── src ├── Test.js ├── index.js ├── OwnServer.js ├── QiniuServer.js ├── App.js └── components │ └── ReactUEditorComponent.js ├── .babelrc ├── .gitignore ├── LICENSE ├── .eslintrc.js ├── public └── index.html ├── package.json ├── proxy.js ├── lib └── react-ueditor-component.min.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/react-ueditor-component.min.js'); 2 | -------------------------------------------------------------------------------- /assets/utf8-php.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Eschere/react-editor-component/HEAD/assets/utf8-php.zip -------------------------------------------------------------------------------- /src/Test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default props => ( 4 |
5 | {props.test} 6 |
7 | ); 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": "> 0.25%, not dead" 7 | } 8 | ], 9 | [ 10 | "@babel/preset-react" 11 | ] 12 | ] 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # pravite 26 | api 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 eschere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true 5 | }, 6 | extends: [ 7 | "airbnb", 8 | "standard", 9 | ], 10 | parser: "babel-eslint", 11 | parserOptions: { 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true 15 | }, 16 | ecmaVersion: 2018, 17 | sourceType: "module" 18 | }, 19 | }, 20 | plugins: [ 21 | "react", 22 | "jsx-a11y" 23 | ], 24 | rules: { 25 | "quotes": [ 26 | "error", 27 | "single" 28 | ], 29 | "semi": [ 30 | "error", 31 | "always" 32 | ], 33 | "no-unused-vars": [ 34 | 1 35 | ], 36 | 'prefer-template': 0, 37 | 'prefer-destructuring': 0, 38 | "prefer-const": 0, 39 | "global-require": 0, 40 | "linebreak-style": 0, 41 | 'no-plusplus': ["error", { "allowForLoopAfterthoughts": true }], 42 | "no-return-assign": 0, 43 | "no-restricted-syntax": 0, 44 | "no-param-reassign": 0, 45 | "react/destructuring-assignment": 0, 46 | 'react/jsx-one-expression-per-line': 0, 47 | 'react/jsx-no-undef': 0, // 本项目自动引入React 48 | "react/self-closing-comp": 0, 49 | "react/prop-types": 0, 50 | "react/no-string-refs": 0, 51 | 'jsx-a11y/no-static-element-interactions': 0, 52 | 'jsx-a11y/click-events-have-key-events': 0, 53 | 'jsx-a11y/anchor-is-valid': 0, 54 | 'jsx-a11y/label-has-for': 0, 55 | 'import/no-unresolved': [2, { ignore: ['^@/', './'] }], 56 | 'import/no-extraneous-dependencies': 0, 57 | 'react/jsx-filename-extension': 0 58 | }, 59 | globals: { 60 | React: true 61 | } 62 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 20 | 21 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ueditor-component", 3 | "version": "1.0.5", 4 | "description": "UEditor wrapped by React Component", 5 | "main": "./index.js", 6 | "scripts": { 7 | "dev": "react-scripts start", 8 | "build": "node build.js" 9 | }, 10 | "keywords": [ 11 | "ueditor", 12 | "rich-text", 13 | "富文本", 14 | "react" 15 | ], 16 | "author": "eschere", 17 | "license": "MIT", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/Eschere/react-editor-component.git" 21 | }, 22 | "homepage": "https://github.com/Eschere/react-editor-component#readme", 23 | "devDependencies": { 24 | "debounce": "^1.2.0", 25 | "react": "^16.8.6", 26 | "@babel/preset-env": "^7.4.4", 27 | "@babel/preset-react": "^7.0.0", 28 | "eslint": "^5.4.0", 29 | "eslint-config-airbnb": "^17.1.0", 30 | "eslint-config-standard": "^12.0.0", 31 | "eslint-plugin-babel": "^5.3.0", 32 | "eslint-plugin-import": "^2.14.0", 33 | "eslint-plugin-jsx-a11y": "^6.1.2", 34 | "eslint-plugin-node": "^8.0.0", 35 | "eslint-plugin-promise": "^4.0.1", 36 | "eslint-plugin-react": "^7.11.1", 37 | "eslint-plugin-standard": "^4.0.0", 38 | "ora": "^3.4.0", 39 | "react-dom": "^16.8.6", 40 | "react-scripts": "3.0.1", 41 | "rimraf": "^2.6.3", 42 | "uglifyjs-webpack-plugin": "^2.1.2" 43 | }, 44 | "eslintConfig": { 45 | "extends": "react-app" 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /proxy.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const url = require('url'); 3 | 4 | /** 5 | * 如果你要准备ueditor和ReactUEditorComponent同时开发联调 6 | * 一下的配置可以解决两个项目在不同端口下运行时,iframe产生的跨域问题 7 | * 将ueditor运行在8080端口,/utf8-phplu路径 8 | * 将ReactUEditorComponent运行在3000端口 9 | * 浏览器访问8000端口 10 | */ 11 | let server = http.createServer((req, res) => { 12 | const path = url.parse(req.url).path; 13 | console.log(path); 14 | 15 | if (path.startsWith('/utf8-php')) { 16 | let request = http.request({ 17 | port: 8080, 18 | path, 19 | method: req.method, 20 | headers: req.headers 21 | }, (response) => { 22 | res.writeHead(response.statusCode, response.headers); 23 | response.pipe(res); 24 | }); 25 | req.pipe(request); 26 | } else if (!path.includes('websocket')) { 27 | let request = http.request({ 28 | port: 3000, 29 | path, 30 | method: req.method, 31 | headers: req.headers 32 | }, (response) => { 33 | res.writeHead(response.statusCode, response.headers); 34 | response.pipe(res); 35 | }); 36 | req.pipe(request); 37 | } else { 38 | res.end(); 39 | } 40 | }).listen(8000); 41 | 42 | // websocket转发 43 | server.on('upgrade', (req, client) => { 44 | const path = url.parse(req.url).path; 45 | let request = http.request({ 46 | port: 3000, 47 | path, 48 | headers: req.headers 49 | }); 50 | 51 | request.on('upgrade', (res, socket) => { 52 | client.write(formatProxyResponse(res)); 53 | client.pipe(socket); 54 | socket.pipe(client); 55 | }); 56 | 57 | request.end(); 58 | }); 59 | 60 | function formatProxyResponse (res) { 61 | const headers = res.headers; 62 | const keys = Object.getOwnPropertyNames(headers); 63 | let switchLine = '\r\n'; 64 | let response = [`HTTP/${res.httpVersion} ${res.statusCode} ${res.statusMessage}${switchLine}`]; 65 | keys.forEach((key) => { 66 | response.push(`${key}: ${headers[key]}${switchLine}`); 67 | }); 68 | response.push(switchLine); 69 | return response.join(''); 70 | } 71 | -------------------------------------------------------------------------------- /src/OwnServer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactUEditorComponent from './components/ReactUEditorComponent'; 3 | 4 | import { 5 | upload, headers 6 | } from './api/api'; 7 | 8 | export default class extends Component { 9 | state = { 10 | value: '', 11 | serverExtra: { 12 | headers, 13 | extraData: { 14 | yourdata: '123' 15 | } 16 | } 17 | } 18 | 19 | onChange = (value) => { 20 | console.log('change', value); 21 | 22 | this.setState({ 23 | value 24 | }); 25 | } 26 | 27 | render () { 28 | return ( 29 |
30 | 上传到自己的服务器 31 |
35 | {this.state.value} 36 |
37 | 80 | 81 |
82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/QiniuServer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactUEditorComponent from './components/ReactUEditorComponent'; 3 | 4 | import { 5 | uploadqiniu as upload, getToken, headers, qiniuDomain 6 | } from './api/api'; 7 | 8 | export default class App extends Component { 9 | state = { 10 | value: '' 11 | } 12 | 13 | onChange = (value) => { 14 | console.log('change', value); 15 | 16 | this.setState({ 17 | value 18 | }); 19 | } 20 | 21 | /** 22 | * @tips 23 | * 由于react的组件更新机制,应该选用更保险的附加数据更新方法 24 | * 以下三种方法 25 | * 大多数情况下,方法一能按预想的执行顺序执行,这里只是提供了另外两种保险方法参考 26 | */ 27 | // 1. 直接更新,有无法及时更新upload携带参数的风险 28 | /* 29 | beforeUpload = (file) => { 30 | this.setState({ 31 | serverExtra: { 32 | headers, 33 | extraData: { 34 | tik: 123 35 | } 36 | } 37 | }) 38 | return file 39 | } 40 | */ 41 | 42 | // 2. setState回调中resolve一个promise 43 | // beforeUpload = file => new Promise((resolve, reject) => { 44 | // this.setState({ 45 | // serverExtra: { 46 | // headers, 47 | // extraData: { 48 | // tik: 123 49 | // } 50 | // } 51 | // }, () => resolve(file)); 52 | // }) 53 | 54 | // 3. 在setExtraDataComplete中resolve一个promise 55 | // setExtraDataComplete会在重新设置上传携带参数时被调用 56 | // 需要将setExtraDataComplete传给ReactUEditorComponent才能生效 57 | beforeUpload = file => new Promise((resolve, reject) => { 58 | let key = 't' + Math.random().toString().slice(5, 16); 59 | 60 | // 请求服务器,获取七牛上传凭证 61 | fetch(`${getToken}?key=${key}`, { 62 | headers 63 | }) 64 | .then(response => response.json()) 65 | .then((data) => { 66 | // 设置七牛直传额外数据 67 | this.setState({ 68 | serverExtra: { 69 | extraData: { 70 | token: data.token, 71 | key 72 | } 73 | }, 74 | setExtraDataComplete: () => { 75 | resolve(file); 76 | } 77 | }); 78 | }); 79 | }) 80 | 81 | render () { 82 | return ( 83 |
84 | 上传到七牛 85 |
89 | {this.state.value} 90 |
91 | 136 | 137 |
138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import OwnServer from './OwnServer'; 3 | import QiniuServer from './QiniuServer'; 4 | 5 | import { 6 | upload, headers 7 | } from './api/api'; 8 | 9 | let toolbars = [[ 10 | 'fullscreen', /* */ 'source', '|', 'undo', 'redo', '|', 11 | 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|', 12 | 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|', 13 | 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|', 14 | 'directionalityltr', 'directionalityrtl', 'indent', '|', 15 | 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|', 16 | 'link', 'unlink', 'anchor', '|', 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|', 17 | 'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', /* 上传视频 , */ /* 'music', 'attachment', */ /* 'map', 'gmap', */ 'insertframe', 'insertcode', /* 'webapp', */ 'pagebreak', /* 'template', */ /* 'background', */ '|', 18 | 'horizontal', 'date', 'time', 'spechars', /* 'snapscreen', 'wordimage', */'|', 19 | 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', /* 'charts', */ '|', 20 | 'print', 'preview', 'searchreplace', 'drafts', 'help' 21 | ]]; 22 | 23 | export default class extends React.Component { 24 | componentDidMount () { 25 | window.UE.getEditor('static_editor', { 26 | autoHeightEnabled: false, 27 | toolbars, 28 | // 图片转存关闭 29 | catchRemoteImageEnable: false, 30 | serverUrl: upload, 31 | serverExtra: { 32 | headers 33 | }, 34 | serverOptions: { 35 | /* 上传图片配置项 */ 36 | imageActionName: 'uploadimage', /* 执行上传图片的action名称 */ 37 | imageFieldName: 'file', /* 提交的图片表单名称 */ 38 | imageMaxSize: 2048000, /* 上传大小限制,单位B */ 39 | imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */ 40 | // imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */ 41 | imageCompressEnable: true, /* 是否压缩图片,默认是true */ 42 | imageCompressBorder: 1600, /* 图片压缩最长边限制 */ 43 | imageInsertAlign: 'none', /* 插入的图片浮动方式 */ 44 | imageUrlPrefix: '', /* 图片访问路径前缀 */ 45 | imageResponseKey: 'fileURL', // ! 图片上传接口response中包含图片路径的键名 46 | 47 | /* 涂鸦图片上传配置项 */ 48 | scrawlActionName: 'uploadscrawl', /* 执行上传涂鸦的action名称 */ 49 | scrawlFieldName: 'file', /* 提交的图片表单名称 */ 50 | scrawlPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */ 51 | scrawlMaxSize: 2048000, /* 上传大小限制,单位B */ 52 | scrawlUrlPrefix: '', /* 图片访问路径前缀 */ 53 | scrawlInsertAlign: 'none', 54 | scrawlResponseKey: 'fileURL', /* 涂鸦图片上传接口response中包含图片路径的键名 */ 55 | 56 | /* 上传视频配置 */ 57 | videoActionName: 'uploadvideo', /* 执行上传视频的action名称 */ 58 | videoFieldName: 'file', /* 提交的视频表单名称 */ 59 | videoPathFormat: '/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */ 60 | videoUrlPrefix: '', /* 视频访问路径前缀 */ 61 | videoMaxSize: 102400000, /* 上传大小限制,单位B,默认100MB */ 62 | videoResponseKey: 'fileURL', /* 涂鸦图片上传接口response中包含图片路径的键名 */ 63 | videoAllowFiles: [ 64 | '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', 65 | '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid' 66 | ], 67 | /* 抓取远程图片配置 */ 68 | catcherLocalDomain: ['127.0.0.1', 'localhost', 'img.baidu.com'], 69 | catcherActionName: 'catchimage', /* 执行抓取远程图片的action名称 */ 70 | catcherFieldName: 'file', /* 提交的图片列表表单名称 */ 71 | catcherPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */ 72 | catcherUrlPrefix: '', /* 图片访问路径前缀 */ 73 | catcherMaxSize: 2048000, /* 上传大小限制,单位B */ 74 | catcherResponseKey: 'fileURL', 75 | catcherAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'] /* 抓取图片格式显示 */ 76 | } 77 | }); 78 | } 79 | 80 | render () { 81 | return ( 82 |
83 | {/* 三种不同的的ueditor对比 */} 84 | {/* 直接上传到服务器 */} 85 | 86 | 87 |
88 |
89 | {/* 获取上传凭证后上传到服务器 */} 90 | 91 | 92 |
93 |
94 | {/* 未被ReactUEditorComponent包裹的ueditor */} 95 |
96 |
97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/react-ueditor-component.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactUEditorComponent=t(require("react")):e.ReactUEditorComponent=t(e.react)}(window,function(r){return(i={},n.m=o=[function(e,t){e.exports=r},function(e,t){function r(t,r,o){var n,i,a,l,u;function s(){var e=Date.now()-l;e ({ 85 | editorReady: new Promise((resolve, reject) => { 86 | let ueditor = window.UE.getEditor(this.editorId, { 87 | ...this.ueditorOptions, 88 | ...this.props.ueditorOptions 89 | }); 90 | 91 | ueditor.ready(() => { 92 | resolve(ueditor); 93 | 94 | this.observerChangeListener(ueditor); 95 | 96 | ueditor.setContent(this.props.value || ''); 97 | }); 98 | }) 99 | })); 100 | } 101 | 102 | static getDerivedStateFromProps (nextProps, prevState) { 103 | let editorReady = prevState.editorReady; 104 | let value = nextProps.value; 105 | 106 | if (Object.prototype.hasOwnProperty.call(nextProps, 'value')) { 107 | editorReady && editorReady.then((ueditor) => { 108 | (value === prevState.content || value === ueditor.getContent()) || ueditor.setContent(value || ''); 109 | }); 110 | } 111 | 112 | // 只能更新severExtra 113 | if (Object.prototype.hasOwnProperty.call(nextProps.ueditorOptions, 'serverExtra')) { 114 | let serverExtraStr = JSON.stringify(nextProps.ueditorOptions.serverExtra); 115 | 116 | if (serverExtraStr === prevState.serverExtraStr) { 117 | return { 118 | ...prevState, 119 | content: value 120 | }; 121 | } 122 | editorReady && editorReady.then((ueditor) => { 123 | ueditor.setExtraData && ueditor.setExtraData(nextProps.ueditorOptions.serverExtra); 124 | // 增加一层保险,react的组件更新机制有可能使ueditor参数更新在beforeUpload之后 125 | nextProps.setExtraDataComplete && nextProps.setExtraDataComplete(); 126 | }); 127 | return { 128 | ...prevState, 129 | serverExtraStr, 130 | content: value 131 | }; 132 | } 133 | 134 | return { 135 | ...prevState, 136 | content: value 137 | }; 138 | } 139 | 140 | componentWillUnmount () { 141 | this.state.editorReady.then((ueditor) => { 142 | ueditor.destroy(); 143 | }); 144 | 145 | this.observer.disconnect(); 146 | } 147 | 148 | observerChangeListener (ueditor) { 149 | const changeHandle = () => { 150 | let onChange = this.props.onChange; 151 | 152 | if (ueditor.document.getElementById('baidu_pastebin')) { 153 | return; 154 | } 155 | 156 | onChange && onChange(ueditor.getContent()); 157 | }; 158 | 159 | // this.observer = new MutationObserver(changeHandle); 160 | this.observer = new MutationObserver(debounce(changeHandle, 50)); 161 | this.observer.observe(ueditor.body, this.observerOptions); 162 | } 163 | 164 | render () { 165 | return ( 166 |
this.editorDOM = el} 169 | > 170 |
171 | ); 172 | } 173 | } 174 | 175 | export default UEditor; 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-ueditor-component 2 | 3 | ueditor的react封装版,修改了ueditor中的获取服务器端配置实现,更符合前后端分离的思想 4 | 5 | 使用assets中的utf8-php.zip才能正常使用上传文件功能,所有ueditor源码改动都用`MARK:`做了标记,解压该文件可以查看具体改动 6 | 7 | ueditor基于官方1.4.3.3分支修改,功能还在不断完善中 8 | 9 |

10 | 11 | npm 12 | 13 | 14 | 15 | 16 |

17 | 18 | ### Features 19 | 1. 接收`value`和`onChange`,使用起来就像`input`一样简单,与`antd`的表单双向绑定配合使用更简单 20 | 2. 上传文件增加`beforeUpload`钩子,可在上传前修改需要上传的文件、数据和请求头,对接第三方OSS易如反掌 21 | 22 | ### 安装 23 | ```bash 24 | npm install react-ueditor-component --save-dev 25 | ``` 26 | 或者 27 | ```bash 28 | yarn add react-ueditor-component --save 29 | ``` 30 | 31 | ### 使用 32 | 下载修改后打包的[ueditor.zip](https://github.com/Eschere/react-editor-component/raw/master/assets/utf8-php.zip),或者找到`node_modules/react-ueditor-component/assets/utf8-php.zip`,解压文件,放在网站的根目录,react项目一般放在`public`文件夹下, 33 | `index.html`中`script`标签引入`ueditor`代码 34 | ```xml 35 | 36 | 37 | ``` 38 | 引入`ReactUEditorComponent`组件 39 | ```js 40 | import ReactUEditorComponent from 'react-ueditor-component'; 41 | 42 | export default class App extends React.Component { 43 | state = { 44 | value: '', 45 | serverExtra: { 46 | // 上传文件额外请求头 47 | headers: { 48 | Auth: 'token' 49 | }, 50 | // 上传文件额外的数据 51 | extraData: { 52 | desc: 'more data' 53 | } 54 | } 55 | } 56 | 57 | onChange = (value) => this.setState(value); 58 | 59 | render () { 60 | return ( 61 | 82 | ) 83 | } 84 | } 85 | ``` 86 | ### API 87 | 88 | | 参数 | 说明 | 类型 | 默认值 | 89 | |-|-|-|-| 90 | | value | 设置编辑器的内容 | `string` | - | 91 | | onChange | 编辑器内容变化回调 | `Function(value)` | - | 92 | | setExtraDataComplete | 设置上传文件额外数据完成事件 | `Function()` | - | 93 | | ueditorOptions | 编辑器初始化的配置,在[官方文档](https://fex.baidu.com/ueditor/#start-config)支持的参数上增加了一些内容,**除`serverExtra`外不能动态变动** | `object` | 见下文 | 94 | 95 | ### ueditorOptions 96 | 默认值: 97 | ```js 98 | { 99 | autoHeightEnabled: false, 100 | toolbars: [[ 101 | 'fullscreen', /* */ 'source', '|', 'undo', 'redo', '|', 102 | 'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|', 103 | 'rowspacingtop', 'rowspacingbottom', 'lineheight', '|', 104 | 'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|', 105 | 'directionalityltr', 'directionalityrtl', 'indent', '|', 106 | 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|', 107 | 'link', 'unlink', 'anchor', '|', 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|', 108 | 'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', /* 上传视频 , */ /* 'music', 'attachment', */ /* 'map', 'gmap', */ 'insertframe', 'insertcode', /* 'webapp', */ 'pagebreak', /* 'template', */ /* 'background', */ '|', 109 | 'horizontal', 'date', 'time', 'spechars', /* 'snapscreen', 'wordimage', */'|', 110 | 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', /* 'charts', */ '|', 111 | 'print', 'preview', 'searchreplace', 'drafts', 'help' 112 | ]], 113 | // 图片转存关闭 114 | catchRemoteImageEnable: false, initialFrameWidth: '100%', 115 | serverOptions: { 116 | /* 上传图片配置项 */ 117 | imageActionName: 'uploadimage', /* 执行上传图片的action名称 */ 118 | imageFieldName: 'file', /* 提交的图片表单名称 */ 119 | imageMaxSize: 2048000, /* 上传大小限制,单位B */ 120 | imageAllowFiles: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'], /* 上传图片格式显示 */ 121 | imageCompressEnable: true, /* 是否压缩图片,默认是true */ 122 | imageCompressBorder: 1600, /* 图片压缩最长边限制 */ 123 | imageInsertAlign: 'none', /* 插入的图片浮动方式 */ 124 | imageUrlPrefix: '', /* 图片访问路径前缀 */ 125 | imageResponseKey: 'url', // ! 图片上传接口response中包含图片路径的键名 126 | 127 | /* 涂鸦图片上传配置项 */ 128 | scrawlActionName: 'uploadscrawl', /* 执行上传涂鸦的action名称 */ 129 | scrawlFieldName: 'file', /* 提交的图片表单名称 */ 130 | scrawlPathFormat: '/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */ 131 | scrawlMaxSize: 2048000, /* 上传大小限制,单位B */ 132 | scrawlUrlPrefix: '', /* 图片访问路径前缀 */ 133 | scrawlInsertAlign: 'none', 134 | scrawlResponseKey: 'url', /* 涂鸦图片上传接口response中包含图片路径的键名 */ 135 | 136 | /* 上传视频配置 */ 137 | videoActionName: 'uploadvideo', /* 执行上传视频的action名称 */ 138 | videoFieldName: 'file', /* 提交的视频表单名称 */ 139 | videoPathFormat: '/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}', /* 上传保存路径,可以自定义保存路径和文件名格式 */ 140 | videoUrlPrefix: '', /* 视频访问路径前缀 */ 141 | videoMaxSize: 102400000, /* 上传大小限制,单位B,默认100MB */, 142 | videoResponseKey: 'url', 143 | videoAllowFiles: [ 144 | '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', 145 | '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.wav', '.mid' 146 | ] 147 | } 148 | } 149 | 150 | ``` 151 | `toolbars`中被注释的内容都是当前版本不能完美支持的功能,将在后续的版本中完善 152 | 153 | #### 上传配置 154 | `ueditorOptions.serverOptions` 155 | 将后端配置迁移到前端,支持[官方文档](https://fex.baidu.com/ueditor/#server-deploy)的参数,在这基础上增加了: 156 | 157 | `imageResponseKey`: 上传图片成功后,后台返回的json数据中包含图片地址信息的字段名 158 | 159 | 比如配置为 160 | ```js 161 | { 162 | imageUrlPrefix: 'http://demo.com/', /* 图片访问路径前缀 */ 163 | imageResponseKey: 'url', // 164 | } 165 | ``` 166 | 上传成功后后台返回的数据为 167 | ```js 168 | { 169 | url: 'demo.jpg' 170 | } 171 | ``` 172 | 则生成的图片地址为`http://demo.com/demo.jpg`(这里指编辑器的内容中插入的图片地址字符串,而不是服务器存储的地址,服务器把上传文件存在哪里完全有服务端决定) 173 | 174 | 175 | `scrawlResponseKey`: 涂鸦上传成功后,后台返回的json数据中包含图片地址信息的字段名 176 | 177 | 178 | `videoResponseKey`: 视频上传成功后,后台返回的json数据中包含视频地址信息的字段名 179 | 180 | #### 上传接口 181 | `ueditorOptions.serverUrl` 182 | type: `string`必填 183 | 184 | #### 上传接口额外数据 185 | `ueditorOptions.serverExtra` 186 | 上传接口的额外数据,可动态变动 187 | ```jsx 188 | 189 | 202 | ``` 203 | 如上例子在调用上传接口时header会增加`auth: token`, 204 | body中会增加`author: author` 205 | 206 | #### 上传前钩子 207 | `ueditorOptions.beforeUpload` 208 | `File => File | Promise` 209 | 210 | 接收预上传的文件,需要返回File或者Promise, 211 | 如果返回Promise,需要resolve一个File 212 | 213 | #### 设置额外数据完成钩子 214 | `setExtraDataComplete` 215 | 应用场景:上传钩子中设置上传额外数据,防止额外数据设置完成前就已经开始上传,可以使用安全的`setExtraDataComplete`钩子 216 | 217 | 应用场景举例:七牛云上传前需要调用后台接口获取上传凭证 218 | ```jsx 219 | export default class App extends React.Component { 220 | state = { 221 | value: '', 222 | serverExtra: { 223 | // 上传文件额外的数据 224 | extraData: {} 225 | } 226 | } 227 | 228 | beforeUpload = file => new Promise((resolve, reject) => { 229 | let key = 't' + Math.random().toString().slice(5, 16); 230 | 231 | // 请求服务器,获取七牛上传凭证 232 | fetch('getuploadtoken.com', { 233 | headers 234 | }) 235 | .then(response => response.json()) 236 | .then((data) => { 237 | // 设置七牛直传额外数据 238 | this.setState({ 239 | serverExtra: { 240 | extraData: { 241 | token: data.token, 242 | key 243 | } 244 | }, 245 | setExtraDataComplete: () => { 246 | resolve(file); 247 | } 248 | }); 249 | }); 250 | }) 251 | 252 | onChange = (value) => this.setState(value); 253 | 254 | render () { 255 | return ( 256 | 268 | ) 269 | } 270 | } 271 | ``` 272 | 273 | ### Contribution 274 | #### 如何运行项目 275 | 请查看[wiki](https://github.com/Eschere/react-editor-component/wiki/%E5%A6%82%E4%BD%95%E8%BF%90%E8%A1%8C%E9%A1%B9%E7%9B%AE)了解如何运行项目 --------------------------------------------------------------------------------