TEditor.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import { Editor } from '@tinymce/tinymce-react';
  2. import React, { useRef, useEffect, useState, useCallback } from 'react';
  3. import SmMaterialModal from '@/components/MaterialModal/smModalBox'
  4. import WeChatImgCard from "@/components/WeChatImgCard"
  5. import Alink from '@/components/Alink'
  6. // https://mmbiz.qpic.cn/mmbiz_png/Bic8GaUSeMXaRjA1qiaGAu35ssNpnPejMzyY4c0HHPrO1kjh93uWIE4F9aQUfIsEiakib5TWfHls8GLHEPXOIQiaiakg/640?wx_fmt=png
  7. //https://zx-oss-dev.oss-cn-hangzhou.aliyuncs.com/file/B9B6AEB034044B5E9A7FC05227060789.jpg
  8. export default function TEditor(props: {
  9. dispatch: (arg: any) => void,
  10. setLoding: (arg: boolean) => void,
  11. isOk: (arg: any) => void,
  12. content: string,
  13. actionId: any
  14. }) {
  15. let { dispatch, setLoding, isOk, content, actionId } = props
  16. let [value, setValue] = useState<any>(null)
  17. let [imgVisible, setImgVisible] = useState(false)
  18. let [wImgVisible, setWImgVisible] = useState(false)
  19. let [wCardVisible, setWCardVisible] = useState(false)
  20. let [wAVisible, setWAVisible] = useState(false)
  21. const editorRef: any = useRef(null);
  22. // console.log('传进来的content====>',content)
  23. /**==================实时处理html===================*/
  24. const disposeHtml = useCallback((html) => {
  25. console.log(333333, '执行HTML处理方法')
  26. // =================处理腾讯的图片,解决非法引用看不到图的问题==========================
  27. // 筛选出是腾讯的图片,并查看是否带了随机数,以免加载失败
  28. // let imgArr = html?.match(/(<img .*(\B>))/ig)?.filter((img: string ) => !img.includes('aliyuncs.com') && img.search(/=?\w{0,4}\?v/)!== -1)
  29. // imgArr?.forEach((img: any) => {
  30. // // let src = img.match(/src=.*=png/)[0]
  31. // console.log('img====>', img)
  32. // let src = img.match(/(src=.*=\w{3,4})|(src=.*\/[0-9]{0,3}")/)[0]
  33. // console.log('src====>', src)
  34. // // 不存在no-referrer执行添加
  35. // if (!img.includes('referrerpolicy="no-referrer"')) {
  36. // console.log('修改IMG1')
  37. // html = html.replace(src, `${src + '?v' + Math.random() * 10}" referrerpolicy="no-referrer"`)
  38. // } else {
  39. // console.log('修改IMG2')
  40. // html = html.replace(src, src + '?v' + Math.random() * 10)
  41. // }
  42. // })
  43. // 处理小程序卡片假如没有外层加上外层好在本地展示
  44. let mp_miniprogram = html?.match(/(<mp-miniprogram .*>\s?<\/mp-miniprogram>)/)
  45. mp_miniprogram?.forEach((mp: any) => {
  46. if (!mp.includes('data-bd="true"')) {
  47. let imgSrc = mp?.match(/(http.*\.\w{3,4})/)[0]
  48. let newMp = mp.replace('<mp-miniprogram', `<mp-miniprogram data-bd="true"`)
  49. html = html.replace(mp, `
  50. <div class='mp-miniprogram'>
  51. ${newMp}
  52. <img src='${imgSrc}' />
  53. </div>
  54. `)
  55. }
  56. })
  57. console.log(content !== html)
  58. //存放处理后的html
  59. if (content !== html) {
  60. // console.log('setValue=====>',html)
  61. console.log(444444, '改变后的HTML和当前存放的HTML不同替换旧的HTML')
  62. setValue(html)
  63. }
  64. }, [content])
  65. /**====================处理保存的html=======================*/
  66. const disposeSaverHtml = useCallback((html) => {
  67. console.log(5555555, '焦点脱离,处理HTML并保存内容dispatch=====>')
  68. let divStr = html?.match(/<div\s?class="mp-miniprogram">.*<img\s?src=.*\.\w{3,4}">\s?<\/div>/)
  69. divStr?.forEach((div: any) => {
  70. let mpStr = div?.match(/(<mp-miniprogram .*>\s?<\/mp-miniprogram>)/)[0]
  71. mpStr = mpStr?.replace(`data-bd="true"`, '')
  72. html = html?.replace(div, mpStr)
  73. })
  74. console.log('html====>', html)
  75. dispatch({ type: 'pushData', params: { menuId: actionId, content: html || '<p></p>' } })
  76. }, [actionId])
  77. useEffect(() => {
  78. // actionId的监听因为content可能一样为默认值所以监听actionId的变化来强行执行
  79. console.log(1111111111, 'props.content改变')
  80. setValue(content)
  81. }, [content, actionId])
  82. // 腾讯图片出去referrer请求头
  83. useEffect(() => {
  84. let meta = document.createElement('meta')
  85. meta.name = 'referrer'
  86. meta.content = 'no-referrer'
  87. document.head.appendChild(meta)
  88. return () => {
  89. let m = document.head.querySelector('[name="referrer"]')
  90. if (m) {
  91. document.head.removeChild(m)
  92. }
  93. }
  94. }, [])
  95. // console.log('value======>',value)
  96. return <>
  97. <Editor
  98. apiKey='wkn1n49be4rmnjq9h33nzp15z9n9v5jn2n4dr3kav8bx7y4y'
  99. // 初始化
  100. onInit={(evt, editor) => {
  101. editorRef.current = editor
  102. setLoding(false)
  103. isOk(editor);
  104. // editor.ui.registry.addButton
  105. }}
  106. // initialValue={content}
  107. value={value}
  108. onBlur={() => {
  109. // 假如焦点丢失时最后数据与数据存放中的数据不同执行保存
  110. if (value !== content || content === '') {
  111. disposeSaverHtml(value)
  112. }
  113. }}
  114. // 编辑变化
  115. onEditorChange={(html, editor) => {
  116. console.log(22222222, '监听到html变化,执行处理HTML函数方法', html)
  117. // console.log('html===>',html)
  118. //每次html变化去处理函数处理html
  119. disposeHtml(html)
  120. }}
  121. init={{
  122. height: 730,
  123. menubar: true,
  124. language: 'zh-Hans',
  125. language_url: 'http://test.zanxiangnet.com/aaplugin/zh-Hans.js',
  126. extended_valid_elements: 'img[src|_src|referrerpolicy|style|class],mp-miniprogram[data-*]',//自定义标签支持属性
  127. custom_elements: 'mp-miniprogram',
  128. branding: false,//右下角支持关闭
  129. typeahead_urls: false,//输入地址时的自动完成
  130. plugins: [//头部文字插件
  131. 'advlist', 'autolink', 'lists', 'link', 'charmap', 'preview',
  132. 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
  133. 'insertdatetime', 'emoticons', 'table', 'code', 'wordcount', 'image', 'paste', 'help'
  134. ],
  135. // 图标插件按钮
  136. toolbar: 'undo redo blocks fontsize|bold italic forecolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent paste |img wImg wCard wA',//工具栏
  137. toolbar_mode: 'wrap',//工具换行
  138. paste_webkit_styles: 'all',
  139. content_style: `
  140. body { font-family:Helvetica,Arial,sans-serif; font-size:14px ;}
  141. .mp-miniprogram{
  142. width:200px;
  143. height:200px;
  144. }
  145. .mp-miniprogram img{
  146. width:100%;
  147. }
  148. `,
  149. setup(ed) {
  150. // 图
  151. ed.ui.registry.addButton('img', {
  152. text: '图片',
  153. onAction: () => {
  154. setImgVisible(true)
  155. }
  156. })
  157. // 小程序图片链插件
  158. ed.ui.registry.addButton('wImg', {
  159. text: '小程序图片',
  160. onAction: () => {
  161. setWImgVisible(true)
  162. }
  163. })
  164. // 小程序卡片链插件
  165. ed.ui.registry.addButton('wCard', {
  166. text: '小程序卡片',
  167. onAction: () => {
  168. setWCardVisible(true)
  169. }
  170. })
  171. // A链接功能插件
  172. ed.ui.registry.addButton('wA', {
  173. text: 'A链接',
  174. onAction: () => {
  175. setWAVisible(true)
  176. }
  177. })
  178. }
  179. // '' +
  180. // 'alignright alignjustify | bullist numlist outdent indent | ' +
  181. // 'removeformat |code| emoticons|media| image|help'
  182. }}
  183. />
  184. {/* 图片选择 */}
  185. <SmMaterialModal
  186. mediaType={1}
  187. onCancel={() => { setImgVisible(false) }}
  188. title='选择图片'
  189. visible={imgVisible}
  190. onOk={(props: any) => {
  191. let url = props.url
  192. editorRef.current.insertContent(`<img
  193. style='max-width:100%'
  194. src=\"${url}\"
  195. alt=\"\"
  196. data-w=\"auto\"
  197. data-ratio=\"0.66796875\"
  198. data-type=\"${url.replace(/http[s]?:\/\/\S*(wx_fmt=|\.)/ig, '') === 'gif' ? 'png' : url.replace(/http[s]?:\/\/\S*(wx_fmt=|\.)/ig, '')}\"
  199. />`)
  200. setImgVisible(false)
  201. }}
  202. // isShowWx={isShowWx}
  203. isAllData
  204. noFile={true}
  205. />
  206. {/* 小程序图片链 */}
  207. {
  208. wImgVisible && <WeChatImgCard
  209. onCancel={() => {
  210. setWImgVisible(false)
  211. }}
  212. title='小程序图片链'
  213. visible={wImgVisible}
  214. onOk={(props: { img: string, appid: string, appPath: string, href?: string }) => {
  215. editorRef.current.insertContent(`<a data-miniprogram-appid=\"${props.appid}\" data-miniprogram-path=\"${props.appPath}\" href=\"${props.href}\"><img src=\"${props.img}\" alt=\"\" data-width=\"null\" data-ratio=\"NaN\"/></a>`)
  216. setWImgVisible(false)
  217. }}
  218. weCathType='img'
  219. isOnUserId={false}
  220. />
  221. }
  222. {/* 小程序卡片链 */}
  223. {
  224. wCardVisible && <WeChatImgCard
  225. onCancel={() => {
  226. setWCardVisible(false)
  227. }}
  228. title='小程序卡片链'
  229. visible={wCardVisible}
  230. onOk={(props: { img: string, appid: string, appPath: string, cardTitle?: string }) => {
  231. editorRef.current.insertContent(`
  232. <div class='mp-miniprogram'>
  233. <mp-miniprogram data-bd="true" data-miniprogram-appid=\"${props?.appid}\" data-miniprogram-path=\"${props?.appPath}\" data-miniprogram-title=\"${props?.cardTitle}\" data-miniprogram-type=\"card\" data-miniprogram-servicetype=\"0\" data-miniprogram-imageurl=\"${props?.img}\"></mp-miniprogram>
  234. <img src='${props.img}' />
  235. </div>
  236. `)
  237. setWCardVisible(false)
  238. }}
  239. weCathType='card'
  240. isOnUserId={false}
  241. />
  242. }
  243. {/*A链 */}
  244. {
  245. wAVisible && <Alink
  246. onCancel={() => { setWAVisible(false) }}
  247. title='A链接'
  248. visible={wAVisible}
  249. onOk={(props: string) => {
  250. editorRef.current.insertContent(props)
  251. setWAVisible(false)
  252. }}
  253. weCathType='card'
  254. isOnUserId={false}
  255. />
  256. }
  257. </>
  258. }
  259. // /**图片上传插件*/