From a40f220f4b3fc369e8bdfc1e9b90b6cd66d7d744 Mon Sep 17 00:00:00 2001 From: lingting Date: Wed, 13 Oct 2021 15:44:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(Editor):=20=E7=BC=96=E8=BE=91=E5=99=A8?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- src/components/Editor/BraftEditor.tsx | 157 ------------------ src/components/Editor/WangEditor.tsx | 130 ++++++++++----- src/components/Editor/index.ts | 4 +- src/components/Editor/typings.ts | 10 +- .../RightContent/AvatarDropdown.tsx | 4 +- .../notify/announcement/AnnouncementPage.tsx | 14 +- src/pages/system/user/SysUserPage.tsx | 4 +- .../ballcat/notify/announcement/index.ts | 31 ++-- src/utils/SrcUtils.ts | 8 - src/utils/UrlUtils.ts | 13 ++ 11 files changed, 140 insertions(+), 239 deletions(-) delete mode 100644 src/components/Editor/BraftEditor.tsx delete mode 100644 src/utils/SrcUtils.ts create mode 100644 src/utils/UrlUtils.ts diff --git a/package.json b/package.json index 49b7d76..aeed66a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "@ant-design/pro-table": "^2.54.3", "@umijs/route-utils": "^1.0.36", "antd": "^4.16.13", - "braft-editor": "^2.3.9", "classnames": "^2.2.6", "crypto-js": "^4.0.0", "lodash": "^4.17.11", @@ -56,7 +55,8 @@ "react-dom": "^17.0.0", "react-helmet-async": "^1.0.4", "react-transition-group": "^4.4.2", - "umi": "^v3.4.0" + "umi": "^v3.4.0", + "wangeditor": "^4.7.8" }, "devDependencies": { "@ant-design/pro-cli": "^2.0.2", diff --git a/src/components/Editor/BraftEditor.tsx b/src/components/Editor/BraftEditor.tsx deleted file mode 100644 index bdefef6..0000000 --- a/src/components/Editor/BraftEditor.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* eslint-disable */ - -import type { EditorProps } from '.'; -import React, { useState, useEffect, useMemo } from 'react'; -// 引入编辑器组件 -import type { EditorState } from 'braft-editor'; -import { ContentUtils } from 'braft-utils'; -import BraftEditor from 'braft-editor'; -// 引入编辑器样式 -import 'braft-editor/dist/index.css'; -import { debounce } from 'lodash'; -import I18n from '@/utils/I18nUtils'; -import { Button, Modal, Spin, Tabs, Upload } from 'antd'; -import { ImageUtils } from 'braft-finder'; -import Icon from '../Icon'; -import type { UploadFile } from 'antd/lib/upload/interface'; -import { PlusOutlined } from '@ant-design/icons'; -import FileUtils from '@/utils/FileUtils'; - -const defaultLanguage = 'zh'; - -type languageType = 'zh' | 'zh-hant' | 'en' | 'tr' | 'ru' | 'jpn' | 'kr' | 'pl' | 'fr' | 'vi-vn'; - -const languages: string[] = ['zh', 'zh-hant', 'en', 'tr', 'ru', 'jpn', 'kr', 'pl', 'fr', 'vi-vn']; - -const uploadButton = ( -
-
Upload
-
-); - -export default ({ - value, - onChange, - uploadImage, - uploadVideo, - uploadAudio, - readOnly, -}: EditorProps) => { - const [es, setEs] = useState(BraftEditor.createEditorState(null)); - const [loading, setLoading] = useState(false); - const [imageVisible, setImageVisible] = useState(false); - const [imageList, setImageList] = useState([]); - - const debounceChange = useMemo( - () => - debounce((nes: EditorState) => { - setEs(nes); - if (onChange) { - onChange(nes.toHTML()); - } - }, 500), - [onChange], - ); - - const sl = I18n.getLocal().split('-')[0]; - const language = languages.indexOf(sl) === -1 ? defaultLanguage : sl; - - useEffect(() => { - if (es.toHTML() !== value) { - setEs(BraftEditor.createEditorState(value)); - } - }, [value]); - - return ( - <> - - setImageVisible(true)} - // > - // - // - // ), - // }, - ] - } - onChange={debounceChange} - /> - - - - - 添加远程资源 - - - - - - } - onCancel={() => setImageVisible(false)} - > - {imageList.length === 0 ? ( - { - const is: UploadFile[] = []; - fileList.forEach((file) => is.push({ ...file, status: 'done' })); - setImageList(is); - }} - > -

- -

-

单击或拖动文件到此区域进行上传

-

支持单次或批量上传。

-
- ) : ( - setImageList(fileList)} - > - {uploadButton} - - )} -
- - ); -}; diff --git a/src/components/Editor/WangEditor.tsx b/src/components/Editor/WangEditor.tsx index 7f2f26e..2929065 100644 --- a/src/components/Editor/WangEditor.tsx +++ b/src/components/Editor/WangEditor.tsx @@ -1,41 +1,89 @@ -// import type { EditorProps } from '.'; -// import WangEditor from 'wangeditor'; -// import { useState, useEffect } from 'react'; - -// const editorId = 'wang-editor-dom'; -// export default ({ value, onChange, uploadImage }: EditorProps) => { -// const [editor, setEditor] = useState(); -// const [content, setContent] = useState(); - -// useEffect(() => { -// const we = new WangEditor(`#${editorId}`); -// setEditor(we); - -// we.config.onchange = (val: string) => { -// if (onChange) { -// onChange(val); -// } -// }; - -// we.config.customUploadImg = (files, insertImg)=>{ -// } - -// we.create(); - -// return () => { -// we.destroy(); -// }; -// }, [onChange]); - -// useEffect(() => { -// if (value !== editor?.txt.html()) { -// editor?.txt.html(value); -// } -// }, [editor, value]); - -// return ( -// <> -//
-// -// ); -// }; +import type { WangEditorProps } from '.'; +import WangEditor from 'wangeditor'; +import { useState, useEffect, useCallback } from 'react'; +import FileUtils from '@/utils/FileUtils'; +import UrlUtils from '@/utils/UrlUtils'; + +export default ({ + readOnly, + value, + onChange, + uploadImage, + uploadVideo, + uploadAudio, +}: WangEditorProps) => { + const [editor, setEditor] = useState(); + + const update = useCallback( + (val: string) => { + if (onChange) { + onChange(val); + } + }, + [onChange], + ); + + useEffect(() => { + const we = new WangEditor(`#wang-editor-dom`); + setEditor(we); + we.config.onchange = (val: string) => update(val); + + // if(uploadImage){ + we.config.customUploadImg = (files: File[], insertFn: (url: string) => void) => { + if (uploadImage) { + // 自定义上传 + uploadImage(files).then((urls) => { + urls.forEach((url) => { + insertFn(UrlUtils.resolveImage(url)); + }); + }); + } else { + // base64解析上传 + files.forEach((file) => { + FileUtils.getBase64(file).then((b6) => { + insertFn(b6 as string); + }); + }); + } + }; + + if (uploadVideo) { + we.config.customUploadVideo = (files: File[], insertFn: (url: string) => void) => { + uploadVideo(files).then((urls) => { + urls.forEach((url) => { + insertFn(UrlUtils.resolveVideo(url)); + }); + }); + }; + } + + // 用户无操作 200 毫秒更新值 + we.config.onchangeTimeout = 200; + we.config.zIndex = 100; + we.create(); + + return () => { + we.destroy(); + }; + }, [uploadImage, uploadVideo, uploadAudio]); + + useEffect(() => { + if (readOnly) { + editor?.disable(); + } else { + editor?.enable(); + } + }, [editor, readOnly]); + + useEffect(() => { + if (value !== editor?.txt.html()) { + editor?.txt.html(value); + } + }, [editor, value]); + + return ( + <> +
+ + ); +}; diff --git a/src/components/Editor/index.ts b/src/components/Editor/index.ts index dda7f7b..e7b2fe9 100644 --- a/src/components/Editor/index.ts +++ b/src/components/Editor/index.ts @@ -1,5 +1,5 @@ -import Editor from './BraftEditor'; +import WangEditor from './WangEditor'; export * from './typings'; -export default Editor; +export default WangEditor; diff --git a/src/components/Editor/typings.ts b/src/components/Editor/typings.ts index 00000f6..898f00b 100644 --- a/src/components/Editor/typings.ts +++ b/src/components/Editor/typings.ts @@ -2,10 +2,14 @@ export type EditorProps = { value?: string; onChange?: (val: string) => void; // 上传图片, 返回图片完整地址 例: http://www.baidu.com/1.jpg - uploadImage?: (blob: Blob) => Promise; + uploadImage?: (blobs: Blob[]) => Promise; // 上传视频, 返回视频完整地址 例: http://www.baidu.com/1.mp3, 未指定时不允许上传视频 - uploadVideo?: (blob: Blob) => Promise; + uploadVideo?: (blobs: Blob[]) => Promise; // 上传音频, 返回音频完整地址 例: http://www.baidu.com/1.mp3, 未指定时不允许上传音频 - uploadAudio?: (blob: Blob) => Promise; + uploadAudio?: (blobs: Blob[]) => Promise; readOnly?: boolean; }; + +export type WangEditorProps = { + // editorId?: string; +} & EditorProps; diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index 40bfc9d..a4e5b8e 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -9,7 +9,7 @@ import styles from './index.less'; import { outLogin } from '@/services/ant-design-pro/api'; import { User, Token } from '@/utils/Ballcat'; import I18n from '@/utils/I18nUtils'; -import SrcUtils from '@/utils/SrcUtils'; +import UrlUtils from '@/utils/UrlUtils'; export type GlobalHeaderRightProps = { exitConfirm?: boolean; @@ -102,7 +102,7 @@ const AvatarDropdown: React.FC = ({ exitConfirm }) => { {user?.info?.nickname} diff --git a/src/pages/notify/announcement/AnnouncementPage.tsx b/src/pages/notify/announcement/AnnouncementPage.tsx index 534e73e..c40e50c 100644 --- a/src/pages/notify/announcement/AnnouncementPage.tsx +++ b/src/pages/notify/announcement/AnnouncementPage.tsx @@ -269,18 +269,18 @@ export default () => { rules={[{ required: true, message: '请输入公告标题!' }]} /> - {/* */} - - + { + return announcement.uploadImage(blobs).then(({ data }) => { + return data; + }); + }} + /> { size="large" style={{ cursor: 'pointer' }} icon={} - src={SrcUtils.resolve(record.avatar)} + src={UrlUtils.resolveImage(record.avatar)} /> ); diff --git a/src/services/ballcat/notify/announcement/index.ts b/src/services/ballcat/notify/announcement/index.ts index a17c709..a006bff 100644 --- a/src/services/ballcat/notify/announcement/index.ts +++ b/src/services/ballcat/notify/announcement/index.ts @@ -1,5 +1,4 @@ import type { PageResult, QueryParam, R } from '@/typings'; -import type { UploadFile } from 'antd/lib/upload/interface'; import { request } from 'umi'; import type { AnnouncementDto, AnnouncementQo, AnnouncementVo } from './typings'; @@ -31,35 +30,37 @@ export async function del(body: AnnouncementVo) { } export function publish(body: AnnouncementVo) { - return request(`notify/announcement/publish/${body.id}`, { - method: 'patch', + return request>(`notify/announcement/publish/${body.id}`, { + method: 'PATCH', }); } export function close(body: AnnouncementVo) { - return request(`notify/announcement/close/${body.id}`, { - method: 'patch', + return request>(`notify/announcement/close/${body.id}`, { + method: 'PATCH', }); } -export function uploadImage(resultFiles: UploadFile[]) { - const formData = new FormData(); - resultFiles.forEach((file) => { - formData.append('files', file.originFileObj as Blob); +export function uploadImage(blobs: Blob[]) { + const fd = new FormData(); + blobs.forEach((blob) => { + fd.append('files', blob); }); - return request('notify/announcement/image', { - body: formData, + + return request>('notify/announcement/image', { + method: 'POST', + body: fd, }); } export function getUserAnnouncements() { - return request('notify/announcement/user', { - method: 'get', + return request>('notify/announcement/user', { + method: 'GET', }); } export function readAnnouncement(id: string) { - return request(`notify/user-announcement/read/${id}`, { - method: 'patch', + return request>(`notify/user-announcement/read/${id}`, { + method: 'PATCH', }); } diff --git a/src/utils/SrcUtils.ts b/src/utils/SrcUtils.ts deleted file mode 100644 index 620cb6b..0000000 --- a/src/utils/SrcUtils.ts +++ /dev/null @@ -1,8 +0,0 @@ -function resolve(url?: string) { - if (url && !url.startsWith('http')) { - return `https://hccake-img.oss-cn-shanghai.aliyuncs.com/${url}`; - } - return url; -} - -export default { resolve }; diff --git a/src/utils/UrlUtils.ts b/src/utils/UrlUtils.ts new file mode 100644 index 0000000..78bb8fc --- /dev/null +++ b/src/utils/UrlUtils.ts @@ -0,0 +1,13 @@ +const resolveImage = (url: string) => { + if (url && !url.startsWith('http')) { + return `https://hccake-img.oss-cn-shanghai.aliyuncs.com/${url}`; + } + return url; +}; + +const resolveVideo = (url: string) => url; + +const resolveAudio = (url: string) => url; + +const UrlUtils = { resolveImage, resolveVideo, resolveAudio }; +export default UrlUtils;