diff --git a/CHANGELOG.md b/CHANGELOG.md index fb7cf14e259e2a83d73bcae4e24a5508151a8329..d1ae2e6c6749d54e37c6fcd57ecaa804d7d199b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +- 新增富文本编辑器编辑态和只读态切换功能、全屏功能,均由编辑器参数控制 +- 新增滑动输入条编辑器文本显示功能,均由编辑器参数控制 +- 更新看板部件批量操作工具栏打开方式 + ## [0.4.15] - 2023-12-20 ### Added diff --git a/src/control/kanban/kanban.scss b/src/control/kanban/kanban.scss index 9a4c485f3a2baf1caf3109e6f019d753f1be7bde..45a6f29e254b92436e3de60d30cc26cf5bf90ca7 100644 --- a/src/control/kanban/kanban.scss +++ b/src/control/kanban/kanban.scss @@ -138,10 +138,35 @@ $control-kanban: ( padding: getCssVar(spacing, tight); font-size: getCssVar(control-kanban, font-size); color: getCssVar(color, primary); + cursor: pointer; &:hover { background-color: getCssVar(color, fill, 0); } } + @include e(actions-dropdown) { + padding: getCssVar(spacing, extra, tight) 0; + .#{bem(action-toolbar)} { + @include flex(column) + } + .el-button { + --el-button-size: #{getCssVar(height-control, large)}; + margin: 0; + width: 100%; + justify-content: flex-start; + font-size: getCssVar('font-size', 'regular'); + padding: getCssVar(spacing, tight) getCssVar(spacing, base); + color: getCssVar(color, primary, text); + ion-icon { + margin-right: getCssVar(spacing, extra, tight); + } + } + .el-button.is-text:not(.is-disabled) { + &:hover { + border-color: var(--el-button-hover-border-color); + background-color: var(--el-button-hover-bg-color); + } + } + } } @include b(control-kanban-item) { diff --git a/src/control/kanban/kanban.tsx b/src/control/kanban/kanban.tsx index 5d04fc0bde53d1712fe4294d44d3f794fcb1a5d7..61ae22976336508d85adfb2c7137626be0b7c530 100644 --- a/src/control/kanban/kanban.tsx +++ b/src/control/kanban/kanban.tsx @@ -53,6 +53,17 @@ export const KanbanControl = defineComponent({ return c.state.batching ? c.state.selectGroupKey : ''; }); + const quickToolbarModel = c.model.controls?.find(item => { + return ( + item.name === `${c.model.name!}_quicktoolbar` || + item.name === `${c.model.name!}_groupquicktoolbar` + ); + }); + + const batchToolbarModel = c.model.controls?.find(item => { + return item.name === `${c.model.name!}_batchtoolbar`; + }); + const groupClass = c.model.groupSysCss?.cssName || ''; // 分组样式表 const collapseMap: Ref = ref({}); @@ -167,19 +178,16 @@ export const KanbanControl = defineComponent({ const renderQuickToolBar = ( group: IKanbanGroupState, ): VNode | undefined => { - const ctrlModel = c.model.controls?.find(item => { - return ( - item.name === `${c.model.name!}_quicktoolbar` || - item.name === `${c.model.name!}_groupquicktoolbar` - ); - }); - if (!ctrlModel) { + if (!quickToolbarModel) { return; } return ( @@ -189,16 +197,16 @@ export const KanbanControl = defineComponent({ const renderBatchToolBar = ( group: IKanbanGroupState, ): VNode | undefined => { - const ctrlModel = c.model.controls?.find(item => { - return item.name === `${c.model.name!}_batchtoolbar`; - }); - if (!ctrlModel) { + if (!batchToolbarModel) { return; } return (
@@ -350,6 +358,9 @@ export const KanbanControl = defineComponent({ }; const renderGroupToolbar = (group: IKanbanGroupState) => { + const showActionBar = + (c.model.groupUIActionGroup && group.groupActionGroupState) || + batchToolbarModel; if (batchKey.value === group.key) { return ( )} - {c.model.groupUIActionGroup && group.groupActionGroupState && ( - { - c.onGroupToolbarClick(detail, event, group); + trigger='click' + > + {{ + default: (): VNode => ···, + dropdown: (): VNode => ( +
+ {c.model.groupUIActionGroup && + group.groupActionGroupState && ( + { + c.onGroupToolbarClick(detail, event, group); + }} + > + )} + {batchToolbarModel && ( + { + c.openBatch(group.key); + }} + > + + 批量操作 + + )} +
+ ), }} - >
+ )}
); diff --git a/src/editor/html/wang-editor/wang-editor.scss b/src/editor/html/wang-editor/wang-editor.scss index e7e379b592732ba4fc57d9e0065cc79634510644..8b658b3ed2c34f38f7a17a748ce8e59fabf7542d 100644 --- a/src/editor/html/wang-editor/wang-editor.scss +++ b/src/editor/html/wang-editor/wang-editor.scss @@ -48,4 +48,104 @@ $html: ( z-index: 9999; } } +@include b('html-editor-readonly') { + @include b('html-toolbar') { + display: none; + } + @include b('html-editor') { + border: none; + } +} +@include b('html-footer') { + display: flex; + justify-content: end; + align-items: center; + margin-top: 12px; + gap: 16px; + margin-right: 8px; + @include e('cancel') { + height: 36px; + line-height: 36px; + color: #{getCssVar(color, text, 1)}; + opacity: 0.7; + cursor: pointer; + &:hover { + color: #{getCssVar(color, primary)}; + opacity: 1; + } + } + @include e('save') { + background-color: #{getCssVar(color, primary)}; + color: #{getCssVar(color, primary, active, text)}; + width: 96px; + height: 36px; + text-align: center; + line-height: 36px; + border-radius: 5px; + cursor: pointer; + &:hover { + box-shadow: 0px 2px 5px 1px #{getCssVar(color, primary)}; + } + } +} +@include b('html-custom-toolbar') { + display: flex; + justify-content: end; + width: 100%; + gap: 20px; + padding-right: 16px; + height: 32px; + font-size: 16px; + align-items: center; + i { + cursor: pointer; + &:hover { + color: #{getCssVar(color, primary)}; + } + } +} +@include b('html-message') { + width: 500px; + max-width: unset; + @include e('message-content') { + @include m('message-tip') { + color: #{getCssVar(color, text, 3)}; + } + } + @include e('message-cancel') { + color: #{getCssVar(color, text, 1)}; + background-color: transparent; + &:hover { + color: #{getCssVar(color, primary)}; + background-color: transparent; + } + } + @include e('message-comfire') { + background-color: #{getCssVar(color, danger)} !important; + &:hover { + box-shadow: 0px 2px 5px 1px #{getCssVar(color, danger)}; + } + } +} +@include b('html-dialog-full-screen') { + height: 80%; + @include b('html') { + padding: 0 32px; + --w-e-toolbar-bg-color: #{getCssVar(color, bg, 0)}; + } + @include b('html-custom-toolbar') { + height: 56px; + } + @include b('html-content') { + height: calc(100% - 124px); + @include b('html-editor') { + height: 100% !important; + } + } +} +@include b('html-footer-dialog') { + height: 68px; + margin-top: 0; +} + diff --git a/src/editor/html/wang-editor/wang-editor.tsx b/src/editor/html/wang-editor/wang-editor.tsx index 777a3c117ed083307ee6aa3b59293a0b01b6416c..0f364afb5314675fc2b913b061de7d0bbaa89ff6 100644 --- a/src/editor/html/wang-editor/wang-editor.tsx +++ b/src/editor/html/wang-editor/wang-editor.tsx @@ -6,6 +6,7 @@ import { watch, Ref, defineComponent, + nextTick, } from 'vue'; import { Editor, Toolbar } from '@wangeditor/editor-for-vue'; import { IEditorConfig, IToolbarConfig } from '@wangeditor/editor'; @@ -20,6 +21,7 @@ import { import { CoreConst } from '@ibiz-template/core'; import { HtmlEditorController } from '../html-editor.controller'; import './wang-editor.scss'; +import { ElMessageBox } from 'element-plus'; type InsertFnType = (_url: string, _alt: string, _href: string) => void; @@ -64,6 +66,38 @@ const IBizHtml = defineComponent({ // 下载文件路径 const downloadUrl: Ref = ref(''); + // 允许编辑 + const enableEdit = ref(true); + + // 是否存在编辑器参数enableEdit + const hasEnableEdit = ref(false); + + // 只读状态 + const readonlyState = ref(false); + + //允许全屏打开 + const enableFullScreen = ref(false); + + // 是否全屏 + const isFullScreen = ref(false); + + const editorModel = c.model; + if (editorModel.editorParams) { + if (editorModel.editorParams.enableEdit) { + hasEnableEdit.value = true; + readonlyState.value = true; + enableEdit.value = + c.toBoolean(editorModel.editorParams.enableEdit) && + !props.readonly && + !props.disabled; + } + if (editorModel.editorParams.enableFullScreen) { + enableFullScreen.value = c.toBoolean( + editorModel.editorParams.enableFullScreen, + ); + } + } + // data响应式变更基础路径 watch( () => props.data, @@ -112,7 +146,7 @@ const IBizHtml = defineComponent({ // 编辑器配置 const editorConfig: Partial = { placeholder: c.placeHolder, - readOnly: props.readonly, + readOnly: hasEnableEdit.value ? readonlyState.value : props.readonly, MENU_CONF: { // 图片上传 uploadImage: { @@ -222,7 +256,9 @@ const IBizHtml = defineComponent({ ) { return; } - emit('change', emitValue); + if (!hasEnableEdit.value) { + emit('change', emitValue); + } }; // 编辑器销毁时的回调函数。调用 editor.destroy() 即可销毁编辑器 const handleDestroyed = (_editor: IDomEditor) => { @@ -338,6 +374,175 @@ const IBizHtml = defineComponent({ } }; + // 更改编辑状态 + const changeEditState = () => { + readonlyState.value = !readonlyState.value; + if (!readonlyState.value) { + enable(); + editorRef.value.focus(); + moveToLastStr(); + } else { + disable(); + } + }; + + // 光标移动到第一行末尾 + const moveToLastStr = () => { + if (props.value) { + const index = props.value.indexOf('

'); + if (index >= 0) { + const offset = editorRef.value.selection.anchor?.offset; + const path = editorRef.value.selection.anchor?.path; + if (offset === 0 && path.length > 0 && path[0] === 0) { + editorRef.value.move(index - 3); + } + } + } + }; + + // 绘制取消消息盒子 + const renderCancelMessage = () => { + return ( +
+

确定要取消编辑吗?

+

+ 取消编辑将无法保存修改的内容,且不能找回。 +

+
+ ); + }; + + // 取消编辑 + const cancelEdit = () => { + if (props.value !== valueHtml.value) { + ElMessageBox({ + title: '确认取消', + type: 'warning', + customClass: ns.b('message'), + message: renderCancelMessage(), + showCancelButton: true, + cancelButtonClass: ns.be('message', 'message-cancel'), + confirmButtonClass: ns.be('message', 'message-comfire'), + }) + .then(() => { + valueHtml.value = props.value || ''; + changeEditState(); + }) + .catch(() => { + // 重新聚焦 + editorRef.value.focus(); + }); + } else { + changeEditState(); + } + }; + + // 确认保存 + const save = () => { + readonlyState.value = true; + editorRef.value.disable(); + emit('change', valueHtml.value); + if (isFullScreen.value) { + isFullScreen.value = false; + } + }; + + // 绘制底部取消确认按钮 + const renderFooter = () => { + if (hasEnableEdit.value) { + return ( +
+
cancelEdit()}> + 取消 +
+
save()}> + 保存 +
+
+ ); + } + return null; + }; + + // 更新全屏状态 + const changeFullScreenState = () => { + isFullScreen.value = !isFullScreen.value; + nextTick(() => { + if (readonlyState.value) { + disable(); + } else { + enable(); + editorRef.value.focus(); + } + }); + }; + + // 绘制头部工具栏 + const renderHeaserToolbar = () => { + if (hasEnableEdit.value || enableFullScreen.value) { + return ( +
+ {hasEnableEdit.value && enableEdit.value && readonlyState.value ? ( + + ) : null} + {enableFullScreen.value ? ( + isFullScreen.value ? ( + + ) : ( + + ) + ) : null} +
+ ); + } + return null; + }; + + // 绘制编辑器内容 + const renderEditorContent = () => { + return ( +
+ + +
+ ); + }; + onMounted(() => { calcHtmlStyle(); }); @@ -360,41 +565,49 @@ const IBizHtml = defineComponent({ printHtml, disable, enable, + renderHeaserToolbar, + renderEditorContent, + renderFooter, htmlContent, + hasEnableEdit, cssVars, toolbarRef, + isFullScreen, + readonlyState, }; }, render() { - return ( -
+ return !this.isFullScreen ? ( +
+ {this.renderHeaserToolbar()} + {this.renderEditorContent()} + {this.hasEnableEdit && !this.readonlyState ? this.renderFooter() : null} +
+ ) : ( +
- - + {this.renderHeaserToolbar()} + {this.renderEditorContent()} + {this.hasEnableEdit && !this.readonlyState + ? this.renderFooter() + : null}
-
+ ); }, }); diff --git a/src/editor/slider/ibiz-slider/ibiz-slider.scss b/src/editor/slider/ibiz-slider/ibiz-slider.scss new file mode 100644 index 0000000000000000000000000000000000000000..03f20dacc8486056291fb519d795dbbb772b29b6 --- /dev/null +++ b/src/editor/slider/ibiz-slider/ibiz-slider.scss @@ -0,0 +1,16 @@ +@include b('slider') { + width: 100%; + display: flex; + align-items: center; + @include e(text) { + .el-slider__button-wrapper { + display: none; + } + .el-slider__runway.is-disabled .el-slider__bar { + background-color: getCssVar(color, primary); + } + @include m(val) { + margin-left: getCssVar(spacing, tight); + } + } +} diff --git a/src/editor/slider/ibiz-slider/ibiz-slider.tsx b/src/editor/slider/ibiz-slider/ibiz-slider.tsx index 6f9e8b9baf04ad0a4b5d5ed4fdaae73d71000f4d..30991bec5b6fb933ff045453c4566d57daa24413 100644 --- a/src/editor/slider/ibiz-slider/ibiz-slider.tsx +++ b/src/editor/slider/ibiz-slider/ibiz-slider.tsx @@ -1,4 +1,4 @@ -import { defineComponent, ref, watch } from 'vue'; +import { computed, defineComponent, ref, watch } from 'vue'; import { getEditorEmits, getSliderProps, @@ -7,6 +7,7 @@ import { } from '@ibiz-template/vue3-util'; import { toNumber } from 'lodash-es'; import { SliderEditorController } from '../slider-editor.controller'; +import './ibiz-slider.scss'; export const IBizSlider = defineComponent({ name: 'IBizSlider', @@ -29,6 +30,10 @@ export const IBizSlider = defineComponent({ let range = false; // 是否显示输入框,仅在非范围选择时有效 let showInput = false; + // 是否显示百分比 + let showText = false; + // 格式化 + let format = '0%'; if (editorModel.editorParams) { if (editorModel.editorParams.stepvalue) { step = toNumber(editorModel.editorParams.stepvalue); @@ -48,6 +53,12 @@ export const IBizSlider = defineComponent({ if (editorModel.editorParams.showinput) { showInput = c.toBoolean(editorModel.editorParams.showinput); } + if (editorModel.editorParams.showText) { + showText = c.toBoolean(editorModel.editorParams.showText); + } + if (editorModel.editorParams.format) { + format = editorModel.editorParams.format; + } } // 当前值 @@ -77,6 +88,14 @@ export const IBizSlider = defineComponent({ { immediate: true }, ); + // 计算文本显示值 + const textVal = computed(() => { + const tempCurVal = Number(currentVal.value); + const value = Number(tempCurVal / max); + const formatValue = ibiz.util.text.format(`${value}`, format); + return formatValue; + }); + const handleChange = (currentValue: number | undefined | Array) => { if (Array.isArray(currentValue)) { emit('change', JSON.stringify(currentValue)); @@ -102,12 +121,18 @@ export const IBizSlider = defineComponent({ range, showInput, editorRef, + showText, + textVal, }; }, render() { return (
+ {this.showText ? ( + {this.textVal} + ) : null}
); },