From 764273029f4d15aabcb614b3ab5d833df22f706f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Mon, 30 Sep 2024 14:11:48 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0quill=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ibiz.config.ts | 1 + package.json | 1 + pnpm-lock.yaml | 41 ++++++++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ibiz.config.ts b/ibiz.config.ts index 56e7040acf..8dca4ea034 100644 --- a/ibiz.config.ts +++ b/ibiz.config.ts @@ -40,6 +40,7 @@ export default defineConfig({ 'qx-util', 'pinia', 'cherry-markdown', + 'quill', '@ibiz-template/mob-theme', '@ibiz-template-package/vs-tree-ex', '@ibiz-template/core', diff --git a/package.json b/package.json index 980a80f866..54af765d9f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "pinia": "^2.1.7", "qs": "^6.11.2", "qx-util": "^0.4.8", + "quill": "^2.0.2", "ramda": "^0.29.1", "rolldate": "^3.1.3", "vant": "^4.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd8ebef8d7..d8ddb316b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ dependencies: version: 0.7.35-alpha.1(axios@1.6.2)(lodash-es@4.17.21)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1) '@ibiz-template/mob-theme': specifier: ^0.2.13 - version: 0.2.13(@ibiz-template/core@0.7.35-alpha.1)(@ibiz/model-core@0.1.55)(async-validator@4.2.5)(dayjs@1.11.10)(echarts@5.4.3)(handlebars@4.7.8)(lodash-es@4.17.21)(mqtt@2.18.9)(path-browserify@1.0.1)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1) + version: 0.2.14(@ibiz-template/core@0.7.35-alpha.1)(@ibiz/model-core@0.1.55)(async-validator@4.2.5)(dayjs@1.11.10)(echarts@5.4.3)(handlebars@4.7.8)(lodash-es@4.17.21)(mqtt@2.18.9)(path-browserify@1.0.1)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1) '@ibiz-template/model-helper': specifier: 0.7.38-alpha.13 version: 0.7.38-alpha.13(@ibiz-template/runtime@0.7.38-alpha.13)(ramda@0.29.1) @@ -50,6 +50,9 @@ dependencies: qs: specifier: ^6.11.2 version: 6.11.2 + quill: + specifier: ^2.0.2 + version: 2.0.2 qx-util: specifier: ^0.4.8 version: 0.4.8 @@ -1164,7 +1167,7 @@ packages: dev: true /@ibiz-template/core@0.7.35-alpha.1(axios@1.6.2)(lodash-es@4.17.21)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1): - resolution: {integrity: sha512-EjFKX9vMkM61kfEQRG1cBy1bU9vAPFoPN2rDdqB2jzKdiEQXp6jtSSUZn6nJhWyZu18CiEHyAGFlZ3B50pm53A==} + resolution: {integrity: sha512-GVPvZLfrxPGnZtbp3FOoU6p8tO2r/0eMHUH4SaQrQJBNE3eSlpkEO2NjRE1CnHlSQO9C7C7PvlRTzgtBD0TiTA==} peerDependencies: axios: ^1.4.0 lodash-es: ^4.17.21 @@ -1182,8 +1185,8 @@ packages: ramda: 0.29.1 dev: false - /@ibiz-template/mob-theme@0.2.13(@ibiz-template/core@0.7.35-alpha.1)(@ibiz/model-core@0.1.55)(async-validator@4.2.5)(dayjs@1.11.10)(echarts@5.4.3)(handlebars@4.7.8)(lodash-es@4.17.21)(mqtt@2.18.9)(path-browserify@1.0.1)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1): - resolution: {integrity: sha512-B80HAxirLHkNF7QEwxRivFk9KarzaGhy9hIqvn8DU7V1yPNbnqVcCYycBoHyJu5riDxSul4db207CkaoOBjeSg==} + /@ibiz-template/mob-theme@0.2.14(@ibiz-template/core@0.7.35-alpha.1)(@ibiz/model-core@0.1.55)(async-validator@4.2.5)(dayjs@1.11.10)(echarts@5.4.3)(handlebars@4.7.8)(lodash-es@4.17.21)(mqtt@2.18.9)(path-browserify@1.0.1)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1): + resolution: {integrity: sha512-sywecC1JgL5yYkOhMm0snn4D/aSQ9lFHbuuWKpF6juiksbSKI3/WeSDOrzML5gxqXS6Mfj9dg+G0intkcQthvw==} dependencies: '@ibiz-template/runtime': 0.4.16(@ibiz-template/core@0.7.35-alpha.1)(@ibiz/model-core@0.1.55)(async-validator@4.2.5)(dayjs@1.11.10)(echarts@5.4.3)(handlebars@4.7.8)(lodash-es@4.17.21)(mqtt@2.18.9)(path-browserify@1.0.1)(qs@6.11.2)(qx-util@0.4.8)(ramda@0.29.1) transitivePeerDependencies: @@ -4156,7 +4159,6 @@ packages: /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - dev: true /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} @@ -4280,7 +4282,6 @@ packages: /fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: true /fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} @@ -5822,7 +5823,10 @@ packages: /lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - dev: true + + /lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + dev: false /lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} @@ -6498,6 +6502,10 @@ packages: engines: {node: '>=6'} dev: true + /parchment@3.0.0: + resolution: {integrity: sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==} + dev: false + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -6835,6 +6843,25 @@ packages: engines: {node: '>=10'} dev: true + /quill-delta@5.1.0: + resolution: {integrity: sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==} + engines: {node: '>= 12.0.0'} + dependencies: + fast-diff: 1.3.0 + lodash.clonedeep: 4.5.0 + lodash.isequal: 4.5.0 + dev: false + + /quill@2.0.2: + resolution: {integrity: sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==} + engines: {npm: '>=8.2.3'} + dependencies: + eventemitter3: 5.0.1 + lodash-es: 4.17.21 + parchment: 3.0.0 + quill-delta: 5.1.0 + dev: false + /qx-util@0.4.8: resolution: {integrity: sha512-QSaMIyccyPEZZytdHDqadsFp06m36FBX9nkwUmfD15EZ7hibSj4yO9bLBKuVwCDKnMV2w0QL2qHDsLV5b5rY1Q==} dev: false -- Gitee From 2f4f4b91d3de065cf3f516ded6eaf30b4d2b6bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Mon, 30 Sep 2024 14:12:11 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=8C?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/html/html-editor.controller.ts | 114 ++++++++++++ src/editor/html/html-editor.provider.ts | 31 ++++ src/editor/html/index.ts | 2 + .../html/quill-editor/quill-editor.scss | 75 ++++++++ src/editor/html/quill-editor/quill-editor.tsx | 173 ++++++++++++++++++ src/editor/index.ts | 9 + 6 files changed, 404 insertions(+) create mode 100644 src/editor/html/html-editor.controller.ts create mode 100644 src/editor/html/html-editor.provider.ts create mode 100644 src/editor/html/index.ts create mode 100644 src/editor/html/quill-editor/quill-editor.scss create mode 100644 src/editor/html/quill-editor/quill-editor.tsx diff --git a/src/editor/html/html-editor.controller.ts b/src/editor/html/html-editor.controller.ts new file mode 100644 index 0000000000..55e4f79a69 --- /dev/null +++ b/src/editor/html/html-editor.controller.ts @@ -0,0 +1,114 @@ +import { EditorController, ScriptFactory } from '@ibiz-template/runtime'; +import { IHtml } from '@ibiz/model-core'; + +/** + * html框编辑器控制器 + * + * @export + * @class HtmlEditorController + * @extends {EditorController} + */ +export class HtmlEditorController extends EditorController { + /** + * 上传参数 + */ + public uploadParams?: IParams; + + /** + * 下载参数 + */ + public exportParams?: IParams; + + /** + * @description 是否显示工具栏 + * @type {boolean} + * @memberof HtmlEditorController + */ + public showToolbar: boolean = true; + + /** + * @description 值模式(暂时只支持html模式,text模式存在问题) + * @type {('text' | 'html')} + * @memberof HtmlEditorController + */ + public valueMode: 'text' | 'html' = 'html'; + + /** + * @description 图片模式 + * @type {('base64' | 'file')} + * @memberof HtmlEditorController + */ + public imageMode: 'base64' | 'file' = 'file'; + + /** + * @description quill配置 + * @type {IData} + * @memberof HtmlEditorController + */ + public modules: IData = { + toolbar: [ + [{ header: [1, 2, 3, 4, false] }], + ['bold', 'italic', 'underline', 'strike'], + ['link', 'image', 'code-block'], + ], + }; + + /** + * 初始化 + * + * @protected + * @return {*} {Promise} + * @memberof HtmlEditorController + */ + protected async onInit(): Promise { + await super.onInit(); + if (this.editorParams) { + const { + uploadParams, + exportParams, + SHOWTOOLBAR, + VALUEMODE, + IMAGEMODE, + MODULES, + } = this.editorParams; + + if (uploadParams) { + try { + this.uploadParams = JSON.parse(uploadParams); + } catch (error) { + ibiz.log.error( + `编辑器[${ibiz.log.error( + error, + )}]编辑器参数 uploadParams 非 json 格式`, + ); + } + } + if (exportParams) { + try { + this.exportParams = JSON.parse(exportParams); + } catch (error) { + ibiz.log.error( + `编辑器[${ibiz.log.error( + error, + )}]编辑器参数 exportParams 非 json 格式`, + ); + } + } + if (SHOWTOOLBAR) { + this.showToolbar = this.toBoolean(SHOWTOOLBAR); + } + if (VALUEMODE) { + this.valueMode = VALUEMODE.toLowerCase(); + } + if (IMAGEMODE) { + this.imageMode = IMAGEMODE.toLowerCase(); + } + if (MODULES) { + this.modules = ScriptFactory.execScriptFn( + { controller: this }, + MODULES, + ) as IData; + } + } + } +} diff --git a/src/editor/html/html-editor.provider.ts b/src/editor/html/html-editor.provider.ts new file mode 100644 index 0000000000..8baa9374ae --- /dev/null +++ b/src/editor/html/html-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IHtml } from '@ibiz/model-core'; +import { HtmlEditorController } from './html-editor.controller'; + +/** + * html框编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class HtmlEditorProvider + * @implements {EditorProvider} + */ +export class HtmlEditorProvider implements IEditorProvider { + formEditor: string = 'IBizQuill'; + + gridEditor: string = 'IBizQuill'; + + async createController( + editorModel: IHtml, + parentController: IEditorContainerController, + ): Promise { + const c = new HtmlEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/html/index.ts b/src/editor/html/index.ts new file mode 100644 index 0000000000..4ae95a9b9d --- /dev/null +++ b/src/editor/html/index.ts @@ -0,0 +1,2 @@ +export * from './html-editor.controller'; +export * from './html-editor.provider'; diff --git a/src/editor/html/quill-editor/quill-editor.scss b/src/editor/html/quill-editor/quill-editor.scss new file mode 100644 index 0000000000..3d604afc12 --- /dev/null +++ b/src/editor/html/quill-editor/quill-editor.scss @@ -0,0 +1,75 @@ +@include b(quill) { + // quill多语言特殊处理,后续补充多语言 + @include m(zh-cn) { + .ql-picker.ql-size { + .ql-picker-label::before, + .ql-picker-item::before { + content: '默认' + } + .ql-picker-label[data-value=small]::before, + .ql-picker-item[data-value=small]::before { + content: '小' + } + .ql-picker-label[data-value=large]::before, + .ql-picker-item[data-value=large]::before { + content: '大' + } + .ql-picker-label[data-value=huge]::before, + .ql-picker-item[data-value=huge]::before { + content: '超大' + } + } + + .ql-picker.ql-font { + .ql-picker-label::before, + .ql-picker-item::before { + content: 'Sans Serif' + } + .ql-picker-label[data-value=serif]::before, + .ql-picker-item[data-value=serif]::before { + content: 'Serif' + } + .ql-picker-label[data-value=monospace]::before, + .ql-picker-item[data-value=monospace]::before { + content: 'Monospace' + } + } + + .ql-picker.ql-header { + .ql-picker-label::before, + .ql-picker-item::before { + content: '正文' + } + .ql-picker-label[data-value="1"]::before, + .ql-picker-item[data-value="1"]::before { + content: 'H 1'; + } + .ql-picker-label[data-value="2"]::before, + .ql-picker-item[data-value="2"]::before { + content: 'H 2'; + } + .ql-picker-label[data-value="3"]::before, + .ql-picker-item[data-value="3"]::before { + content: 'H 3'; + } + .ql-picker-label[data-value="4"]::before, + .ql-picker-item[data-value="4"]::before { + content: 'H 4'; + } + .ql-picker-label[data-value="5"]::before, + .ql-picker-item[data-value="5"]::before { + content: 'H 5'; + } + .ql-picker-label[data-value="6"]::before, + .ql-picker-item[data-value="6"]::before { + content: 'H 6'; + } + } + .ql-picker.ql-header, .ql-picker.ql-font, .ql-picker.ql-size + .ql-picker-label[data-label]:not([data-label='']), + .ql-picker-item[data-label]:not([data-label='']) + &::before{ + content: attr(data-label) + } + } +} \ No newline at end of file diff --git a/src/editor/html/quill-editor/quill-editor.tsx b/src/editor/html/quill-editor/quill-editor.tsx new file mode 100644 index 0000000000..389e68149d --- /dev/null +++ b/src/editor/html/quill-editor/quill-editor.tsx @@ -0,0 +1,173 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { defineComponent, onMounted, Ref, ref, watch } from 'vue'; +import { + getMarkDownProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import 'quill/dist/quill.core.css'; +import 'quill/dist/quill.snow.css'; +import 'quill/dist/quill.bubble.css'; +import Quill from 'quill'; +import { Delta } from 'quill/core'; +import { base64ToBlob, CoreConst } from '@ibiz-template/core'; +import { getCookie } from 'qx-util'; +import { HtmlEditorController } from '../html-editor.controller'; +import './quill-editor.scss'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const IBizQuill: any = defineComponent({ + name: 'IBizQuill', + props: getMarkDownProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('quill'); + const c: HtmlEditorController = props.controller; + + const editorRef = ref(null); + + const lang = ibiz.i18n.getLang(); + + // 编辑器对象 + let quill: Quill | null = null; + + // 请求头 + const headers: Ref = ref({ + Authorization: `Bearer ${getCookie(CoreConst.TOKEN)}`, + }); + + // 上传文件路径 + const uploadUrl: Ref = ref(''); + + // 下载文件路径 + const downloadUrl: Ref = ref(''); + + const getImage = (delta: Delta) => { + const item = delta.ops.find(x => x.insert && (x.insert as IData).image); + if (item) { + return (item.insert as IData).image; + } + }; + + const getValue = () => { + if (c.valueMode === 'text') { + return quill!.getText(); + } + return quill!.getSemanticHTML(); + }; + + // 处理图片上传(文件模式) + const handleUpload = async (image: string) => { + const blob = base64ToBlob(image); + const file = await ibiz.util.file.fileUpload( + uploadUrl.value, + blob, + headers.value, + ); + const url = downloadUrl.value.replace('%fileId%', file.fileid); + const value = getValue(); + emit('change', value.replace(image, url)); + }; + + const init = () => { + if (!editorRef.value) { + return; + } + const theme = c.showToolbar ? 'snow' : 'bubble'; + quill = new Quill(editorRef.value, { + theme, + modules: c.modules, + readOnly: props.disabled || props.readonly, + placeholder: c.placeHolder, + }); + quill.on('text-change', (delta, oldDelta, source) => { + if (source === 'user') { + const image = getImage(delta); + if (image && c.imageMode === 'file') { + handleUpload(image); + return; + } + emit('change', getValue()); + } + }); + if (c.valueMode === 'text') { + quill.setText(props.value); + } + }; + + onMounted(() => { + init(); + }); + + watch( + () => [props.disable, props.readonly], + () => { + if (!quill) { + return; + } + if (props.disabled || props.readonly) { + quill.enable(false); + } else { + quill.enable(); + } + }, + { immediate: true }, + ); + + // data响应式变更基础路径 + watch( + () => props.data, + newVal => { + if (newVal) { + const urls = ibiz.util.file.calcFileUpDownUrl( + c.context, + c.params, + newVal, + c.editorParams, + ); + uploadUrl.value = urls.uploadUrl; + downloadUrl.value = urls.downloadUrl; + } + }, + { immediate: true, deep: true }, + ); + + if (c.valueMode === 'text') { + watch( + () => props.value, + () => { + if (quill) { + quill.setText(props.value); + } + }, + { immediate: true }, + ); + } + + return { + ns, + lang, + editorRef, + }; + }, + render() { + return ( +
+
+ {this.controller.valueMode === 'html' ? ( +
+ ) : null} +
+
+ ); + }, +}); + +export default IBizQuill; diff --git a/src/editor/index.ts b/src/editor/index.ts index e53b05f254..a33e052ba9 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -32,6 +32,7 @@ import { DateRangeEditorProvider, IBizDateRangePicker } from './date-range'; import { IBizCascader, CascaderEditorProvider } from './cascader'; import { ColorPickerEditorProvider, IBizColorPicker } from './color-picker'; import { MarkDownEditorProvider } from './markdown'; +import { HtmlEditorProvider } from './html'; import { IBizDropdownList } from './dropdown-list/ibiz-dropdown-list/ibiz-dropdown-list'; export const IBizEditor = { @@ -69,6 +70,11 @@ export const IBizEditor = { ), ); + v.component( + 'IBizQuill', + defineAsyncComponent(() => import('./html/quill-editor/quill-editor')), + ); + // 标签 registerEditorProvider('SPAN', () => new SpanEditorProvider()); registerEditorProvider( @@ -119,6 +125,8 @@ export const IBizEditor = { registerEditorProvider('MOBRATING', () => new RateEditorProvider()); // 评分器 registerEditorProvider('MOBMARKDOWN', () => new MarkDownEditorProvider()); + // 富文本 + registerEditorProvider('MOBHTMLTEXT', () => new HtmlEditorProvider()); // 单选框列表 registerEditorProvider( @@ -262,6 +270,7 @@ export * from './date-picker'; export * from './date-range'; export * from './dropdown-list'; export * from './markdown'; +export * from './html'; export * from './number-range'; export * from './radio-button-list'; export * from './radio-button-list'; -- Gitee From 1df03779235bead570fae9a153da2e48921072ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Mon, 30 Sep 2024 14:13:35 +0800 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8714ed8e80..8e05362096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 添加富文本编辑器 + ## [0.0.24] - 2024-09-29 ### Added -- Gitee From c89231c1a96b5fc09c6c75981241b75267be15f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Mon, 30 Sep 2024 17:59:47 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E6=A8=A1=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quill-editor-preview.scss | 9 ++ .../quill-editor-preview.tsx | 82 +++++++++++++ .../html/quill-editor/quill-editor.scss | 55 +++++---- src/editor/html/quill-editor/quill-editor.tsx | 109 ++++++++++++++---- src/editor/index.ts | 6 + 5 files changed, 215 insertions(+), 46 deletions(-) create mode 100644 src/editor/html/quill-editor-preview/quill-editor-preview.scss create mode 100644 src/editor/html/quill-editor-preview/quill-editor-preview.tsx diff --git a/src/editor/html/quill-editor-preview/quill-editor-preview.scss b/src/editor/html/quill-editor-preview/quill-editor-preview.scss new file mode 100644 index 0000000000..8156f44a35 --- /dev/null +++ b/src/editor/html/quill-editor-preview/quill-editor-preview.scss @@ -0,0 +1,9 @@ +@include b(quill-preview) { + position: relative; + @include e(edit) { + position: absolute; + right: 15px; + top: 12px; + font-size: getCssVar(font-size, header-4); + } +} \ No newline at end of file diff --git a/src/editor/html/quill-editor-preview/quill-editor-preview.tsx b/src/editor/html/quill-editor-preview/quill-editor-preview.tsx new file mode 100644 index 0000000000..b2f2ea4bfa --- /dev/null +++ b/src/editor/html/quill-editor-preview/quill-editor-preview.tsx @@ -0,0 +1,82 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { defineComponent, onMounted, ref } from 'vue'; +import { + getHtmlProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import Quill from 'quill'; +import { HtmlEditorController } from '../html-editor.controller'; +import './quill-editor-preview.scss'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const IBizQuillPreview: any = defineComponent({ + name: 'IBizQuillPreview', + props: getHtmlProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('quill-preview'); + const c: HtmlEditorController = props.controller; + + const editorRef = ref(null); + + const lang = ibiz.i18n.getLang(); + + // 编辑器对象 + let quill: Quill | null = null; + + const init = () => { + if (!editorRef.value) { + return; + } + quill = new Quill(editorRef.value, { + theme: 'bubble', + modules: c.modules, + readOnly: true, + }); + if (c.valueMode === 'text') { + quill.setText(props.value); + } + }; + + onMounted(() => { + init(); + }); + + const handleClick = () => { + emit('edit'); + }; + + return { + ns, + lang, + editorRef, + handleClick, + }; + }, + render() { + return ( +
+
+ {this.controller.valueMode === 'html' ? ( +
+ ) : null} +
+ {!this.readOnly && !this.disable ? ( +
+ +
+ ) : null} +
+ ); + }, +}); + +export default IBizQuillPreview; diff --git a/src/editor/html/quill-editor/quill-editor.scss b/src/editor/html/quill-editor/quill-editor.scss index 3d604afc12..68399af61a 100644 --- a/src/editor/html/quill-editor/quill-editor.scss +++ b/src/editor/html/quill-editor/quill-editor.scss @@ -1,4 +1,38 @@ @include b(quill) { + .van-action-sheet { + --van-action-sheet-max-height: 100%; + height: 100%; + border-radius: 0; + } + + .van-action-sheet__content { + position: relative; + .content { + height: 100%; + } + .ql-toolbar { + padding: 8px 50px; + } + .#{bem(quill, cancel)} { + position: absolute; + left: 0px; + top: 0; + padding: 8px 12px; + line-height: 26px; + cursor: pointer; + } + .#{bem(quill, confirm)} { + position: absolute; + right: 0px; + top: 0; + padding: 8px 12px; + line-height: 26px; + cursor: pointer; + } + .ql-toolbar.ql-snow + .ql-container.ql-snow { + height: calc(100% - 42px); + } + } // quill多语言特殊处理,后续补充多语言 @include m(zh-cn) { .ql-picker.ql-size { @@ -20,21 +54,6 @@ } } - .ql-picker.ql-font { - .ql-picker-label::before, - .ql-picker-item::before { - content: 'Sans Serif' - } - .ql-picker-label[data-value=serif]::before, - .ql-picker-item[data-value=serif]::before { - content: 'Serif' - } - .ql-picker-label[data-value=monospace]::before, - .ql-picker-item[data-value=monospace]::before { - content: 'Monospace' - } - } - .ql-picker.ql-header { .ql-picker-label::before, .ql-picker-item::before { @@ -65,11 +84,5 @@ content: 'H 6'; } } - .ql-picker.ql-header, .ql-picker.ql-font, .ql-picker.ql-size - .ql-picker-label[data-label]:not([data-label='']), - .ql-picker-item[data-label]:not([data-label='']) - &::before{ - content: attr(data-label) - } } } \ No newline at end of file diff --git a/src/editor/html/quill-editor/quill-editor.tsx b/src/editor/html/quill-editor/quill-editor.tsx index 389e68149d..b22f9ba72a 100644 --- a/src/editor/html/quill-editor/quill-editor.tsx +++ b/src/editor/html/quill-editor/quill-editor.tsx @@ -1,7 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { defineComponent, onMounted, Ref, ref, watch } from 'vue'; +import { defineComponent, nextTick, Ref, ref, watch } from 'vue'; import { - getMarkDownProps, + getHtmlProps, getEditorEmits, useNamespace, } from '@ibiz-template/vue3-util'; @@ -18,7 +18,7 @@ import './quill-editor.scss'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const IBizQuill: any = defineComponent({ name: 'IBizQuill', - props: getMarkDownProps(), + props: getHtmlProps(), emits: getEditorEmits(), setup(props, { emit }) { const ns = useNamespace('quill'); @@ -42,6 +42,15 @@ const IBizQuill: any = defineComponent({ // 下载文件路径 const downloadUrl: Ref = ref(''); + // 编辑状态 + const editing: Ref = ref(false); + + // 更新中 + const updating: Ref = ref(false); + + // 临时数据 + const tempValue: Ref = ref(''); + const getImage = (delta: Delta) => { const item = delta.ops.find(x => x.insert && (x.insert as IData).image); if (item) { @@ -66,7 +75,7 @@ const IBizQuill: any = defineComponent({ ); const url = downloadUrl.value.replace('%fileId%', file.fileid); const value = getValue(); - emit('change', value.replace(image, url)); + tempValue.value = value.replace(image, url); }; const init = () => { @@ -87,7 +96,7 @@ const IBizQuill: any = defineComponent({ handleUpload(image); return; } - emit('change', getValue()); + tempValue.value = getValue(); } }); if (c.valueMode === 'text') { @@ -95,10 +104,6 @@ const IBizQuill: any = defineComponent({ } }; - onMounted(() => { - init(); - }); - watch( () => [props.disable, props.readonly], () => { @@ -132,22 +137,52 @@ const IBizQuill: any = defineComponent({ { immediate: true, deep: true }, ); - if (c.valueMode === 'text') { - watch( - () => props.value, - () => { - if (quill) { - quill.setText(props.value); - } - }, - { immediate: true }, - ); - } + watch( + () => props.value, + () => { + if (!quill) { + return; + } + tempValue.value = props.value; + if (c.valueMode === 'text') { + quill.setText(props.value); + } else { + updating.value = true; + nextTick(() => { + updating.value = false; + }); + } + }, + { immediate: true }, + ); + + // 展开编辑 + const handleEdit = () => { + if (!quill) { + init(); + } + }; + + // 取消编辑 + const handleCancel = () => { + editing.value = false; + }; + + // 确认编辑 + const handleConfirm = () => { + emit('change', tempValue.value); + editing.value = false; + }; return { ns, lang, + editing, + updating, editorRef, + handleEdit, + handleCancel, + handleConfirm, }; }, render() { @@ -160,11 +195,35 @@ const IBizQuill: any = defineComponent({ this.ns.m(this.lang.toLowerCase()), ]} > -
- {this.controller.valueMode === 'html' ? ( -
- ) : null} -
+ {!this.updating && ( + { + this.editing = true; + }} + /> + )} + +
+
+ {ibiz.i18n.t('editor.common.cancel')} +
+
+ {this.controller.valueMode === 'html' ? ( +
+ ) : null} +
+
+ {ibiz.i18n.t('editor.common.confirm')} +
+
+
); }, diff --git a/src/editor/index.ts b/src/editor/index.ts index a33e052ba9..70f242e39b 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -74,6 +74,12 @@ export const IBizEditor = { 'IBizQuill', defineAsyncComponent(() => import('./html/quill-editor/quill-editor')), ); + v.component( + 'IBizQuillPreview', + defineAsyncComponent( + () => import('./html/quill-editor-preview/quill-editor-preview'), + ), + ); // 标签 registerEditorProvider('SPAN', () => new SpanEditorProvider()); -- Gitee From ac7c681311ce67b92615c2f99b450842b06b7bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=94=BF=E6=9D=83?= <1978141412@qq.com> Date: Mon, 30 Sep 2024 18:28:58 +0800 Subject: [PATCH 5/5] =?UTF-8?q?style:=20=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quill-editor-preview.scss | 4 ++-- src/editor/html/quill-editor/quill-editor.scss | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/editor/html/quill-editor-preview/quill-editor-preview.scss b/src/editor/html/quill-editor-preview/quill-editor-preview.scss index 8156f44a35..42c6794762 100644 --- a/src/editor/html/quill-editor-preview/quill-editor-preview.scss +++ b/src/editor/html/quill-editor-preview/quill-editor-preview.scss @@ -2,8 +2,8 @@ position: relative; @include e(edit) { position: absolute; - right: 15px; - top: 12px; + right: rem(15px); + top: rem(12px); font-size: getCssVar(font-size, header-4); } } \ No newline at end of file diff --git a/src/editor/html/quill-editor/quill-editor.scss b/src/editor/html/quill-editor/quill-editor.scss index 68399af61a..98312656c0 100644 --- a/src/editor/html/quill-editor/quill-editor.scss +++ b/src/editor/html/quill-editor/quill-editor.scss @@ -8,30 +8,29 @@ .van-action-sheet__content { position: relative; .content { + display: flex; height: 100%; + flex-direction: column; } .ql-toolbar { - padding: 8px 50px; + padding: rem(8px) rem(50px); } .#{bem(quill, cancel)} { position: absolute; left: 0px; top: 0; - padding: 8px 12px; - line-height: 26px; + padding: rem(8px) rem(12px); + line-height: rem(26px); cursor: pointer; } .#{bem(quill, confirm)} { position: absolute; right: 0px; top: 0; - padding: 8px 12px; - line-height: 26px; + padding: rem(8px) rem(12px); + line-height: rem(26px); cursor: pointer; } - .ql-toolbar.ql-snow + .ql-container.ql-snow { - height: calc(100% - 42px); - } } // quill多语言特殊处理,后续补充多语言 @include m(zh-cn) { -- Gitee