From 4d48eb2cde7b749b80d53719af81115a6edc9105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Fri, 15 Apr 2022 17:49:55 +0800 Subject: [PATCH 1/4] feat:add checkbox control interface --- src/editor/core/draw/Draw.ts | 11 ++- src/editor/core/draw/control/Control.ts | 3 + .../draw/control/checkbox/CheckboxControl.ts | 85 +++++++++++++++++++ src/editor/core/event/CanvasEvent.ts | 1 + src/editor/core/position/Position.ts | 9 +- src/editor/dataset/enum/Control.ts | 6 +- src/editor/interface/Control.ts | 14 ++- src/editor/interface/Position.ts | 1 + src/editor/utils/element.ts | 77 ++++++++++++----- src/mock.ts | 22 ++--- 10 files changed, 191 insertions(+), 38 deletions(-) create mode 100644 src/editor/core/draw/control/checkbox/CheckboxControl.ts diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 6b04f6a..4cf93b6 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -39,6 +39,7 @@ import { Control } from './control/Control' import { zipElementList } from '../../utils/element' import { CheckboxParticle } from './particle/CheckboxParticle' import { DeepRequired } from '../../interface/Common' +import { ControlComponent } from '../../dataset/enum/Control' export class Draw { @@ -567,7 +568,10 @@ export class Draw { element.width = innerWidth metrics.width = innerWidth metrics.height = this.options.defaultSize - } else if (element.type === ElementType.CHECKBOX) { + } else if ( + element.type === ElementType.CHECKBOX || + element.controlComponent === ControlComponent.CHECKBOX + ) { const { width, height, gap } = this.options.checkbox const elementWidth = (width + gap * 2) * scale element.width = elementWidth @@ -707,7 +711,10 @@ export class Draw { if (this.mode !== EditorMode.CLEAN) { this.pageBreakParticle.render(ctx, element, x, y) } - } else if (element.type === ElementType.CHECKBOX) { + } else if ( + element.type === ElementType.CHECKBOX || + element.controlComponent === ControlComponent.CHECKBOX + ) { this.textParticle.complete() this.checkboxParticle.render(ctx, element, x, y + offsetY) } else { diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index e71e951..8dd242c 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -7,6 +7,7 @@ import { pickElementAttr, zipElementList } from '../../../utils/element' import { Listener } from '../../listener/Listener' import { RangeManager } from '../../range/RangeManager' import { Draw } from '../Draw' +import { CheckboxControl } from './checkbox/CheckboxControl' import { SelectControl } from './select/SelectControl' import { TextControl } from './text/TextControl' @@ -103,6 +104,8 @@ export class Control { const selectControl = new SelectControl(element, this) this.activeControl = selectControl selectControl.awake() + } else if (control.type === ControlType.CHECKBOX) { + this.activeControl = new CheckboxControl(element, this) } // 激活控件回调 setTimeout(() => { diff --git a/src/editor/core/draw/control/checkbox/CheckboxControl.ts b/src/editor/core/draw/control/checkbox/CheckboxControl.ts new file mode 100644 index 0000000..d520ab9 --- /dev/null +++ b/src/editor/core/draw/control/checkbox/CheckboxControl.ts @@ -0,0 +1,85 @@ +import { ControlComponent } from '../../../../dataset/enum/Control' +import { KeyMap } from '../../../../dataset/enum/Keymap' +import { IControlInstance } from '../../../../interface/Control' +import { IElement } from '../../../../interface/Element' +import { Control } from '../Control' + +export class CheckboxControl implements IControlInstance { + + private element: IElement + private control: Control + + constructor(element: IElement, control: Control) { + this.element = element + this.control = control + } + + public getElement(): IElement { + return this.element + } + + public getCode(): string | null { + return this.element.control?.code || null + } + + public getValue(): IElement[] { + const elementList = this.control.getElementList() + const { startIndex } = this.control.getRange() + const startElement = elementList[startIndex] + const data: IElement[] = [] + // 向左查找 + let preIndex = startIndex + while (preIndex > 0) { + const preElement = elementList[preIndex] + if ( + preElement.controlId !== startElement.controlId || + preElement.controlComponent === ControlComponent.PREFIX + ) { + break + } + if (preElement.controlComponent === ControlComponent.VALUE) { + data.unshift(preElement) + } + preIndex-- + } + // 向右查找 + let nextIndex = startIndex + 1 + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex] + if ( + nextElement.controlId !== startElement.controlId || + nextElement.controlComponent === ControlComponent.POSTFIX + ) { + break + } + if (nextElement.controlComponent === ControlComponent.VALUE) { + data.push(nextElement) + } + nextIndex++ + } + return data + } + + public setValue(): number { + const { endIndex } = this.control.getRange() + return endIndex + } + + public keydown(evt: KeyboardEvent): number { + const range = this.control.getRange() + // 收缩边界到Value内 + this.control.shrinkBoundary() + const { startIndex, endIndex } = range + // 删除 + if (evt.key === KeyMap.Backspace || evt.key === KeyMap.Delete) { + return this.control.removeControl(startIndex) + } + return endIndex + } + + public cut(): number { + const { endIndex } = this.control.getRange() + return endIndex + } + +} \ No newline at end of file diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index 13b672d..c03102c 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -202,6 +202,7 @@ export class CanvasEvent { // 设置位置上下文 this.position.setPositionContext({ isTable: isTable || false, + isCheckbox: isCheckbox || false, isControl: isControl || false, index, trIndex, diff --git a/src/editor/core/position/Position.ts b/src/editor/core/position/Position.ts index 8fd02b5..be0c423 100644 --- a/src/editor/core/position/Position.ts +++ b/src/editor/core/position/Position.ts @@ -1,5 +1,6 @@ import { ElementType } from '../..' import { ZERO } from '../../dataset/constant/Common' +import { ControlComponent } from '../../dataset/enum/Control' import { IEditorOption } from '../../interface/Editor' import { IElementPosition } from '../../interface/Element' import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position' @@ -97,7 +98,8 @@ export class Position { const tdValueElement = td.value[tdValueIndex] return { index, - isCheckbox: tdValueElement.type === ElementType.CHECKBOX, + isCheckbox: tdValueElement.type === ElementType.CHECKBOX || + tdValueElement.controlComponent === ControlComponent.CHECKBOX, isControl: tdValueElement.type === ElementType.CONTROL, isImage: tablePosition.isImage, isDirectHit: tablePosition.isDirectHit, @@ -121,7 +123,10 @@ export class Position { isImage: true } } - if (element.type === ElementType.CHECKBOX) { + if ( + element.type === ElementType.CHECKBOX || + element.controlComponent === ControlComponent.CHECKBOX + ) { return { index: curPositionIndex, isDirectHit: true, diff --git a/src/editor/dataset/enum/Control.ts b/src/editor/dataset/enum/Control.ts index 00670db..c40a8d4 100644 --- a/src/editor/dataset/enum/Control.ts +++ b/src/editor/dataset/enum/Control.ts @@ -1,11 +1,13 @@ export enum ControlType { TEXT = 'text', - SELECT = 'select' + SELECT = 'select', + CHECKBOX = 'checkbox' } export enum ControlComponent { PREFIX = 'prefix', POSTFIX = 'postfix', PLACEHOLDER = 'placeholder', - VALUE = 'value' + VALUE = 'value', + CHECKBOX = 'checkbox' } \ No newline at end of file diff --git a/src/editor/interface/Control.ts b/src/editor/interface/Control.ts index b0d773f..a691311 100644 --- a/src/editor/interface/Control.ts +++ b/src/editor/interface/Control.ts @@ -1,4 +1,5 @@ import { ControlType } from '../dataset/enum/Control' +import { ICheckbox } from './Checkbox' import { IElement } from './Element' export interface IValueSet { @@ -11,6 +12,14 @@ export interface IControlSelect { valueSets: IValueSet[]; } +export interface IControlCheckbox { + code: string | null; + min?: number; + max?: number; + valueSets: IValueSet[]; + checkbox?: ICheckbox; +} + export interface IControlBasic { type: ControlType; value: IElement[] | null; @@ -20,7 +29,10 @@ export interface IControlBasic { postfix?: string; } -export type IControl = IControlBasic & Partial +export type IControl = IControlBasic + & Partial + & Partial + export interface IControlOption { placeholderColor?: string; diff --git a/src/editor/interface/Position.ts b/src/editor/interface/Position.ts index 1d15f1d..5b94abe 100644 --- a/src/editor/interface/Position.ts +++ b/src/editor/interface/Position.ts @@ -29,6 +29,7 @@ export interface IGetPositionByXYPayload { export interface IPositionContext { isTable: boolean; + isCheckbox?: boolean; isControl?: boolean; index?: number; trIndex?: number; diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index 3614252..d77cd43 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -97,33 +97,68 @@ export function formatElementList(elementList: IElement[], options: IFormatEleme // 值 if ( (value && value.length) || + type === ControlType.CHECKBOX || (type === ControlType.SELECT && code && (!value || !value.length)) ) { let valueList: IElement[] = value || [] - if (!value || !value.length) { + if (type === ControlType.CHECKBOX) { + const codeList = code ? code.split(',') : [] if (Array.isArray(valueSets) && valueSets.length) { - const valueSet = valueSets.find(v => v.code === code) - if (valueSet) { - valueList = [{ - value: valueSet.value - }] + for (let v = 0; v < valueSets.length; v++) { + const valueSet = valueSets[v] + // checkbox组件 + elementList.splice(i, 0, { + controlId, + value: '', + type: el.type, + control: el.control, + controlComponent: ControlComponent.CHECKBOX, + checkbox: { + value: codeList.includes(valueSet.code) + } + }) + i++ + // 文本 + const valueStrList = valueSet.value.split('') + for (let e = 0; e < valueStrList.length; e++) { + const value = valueStrList[e] + elementList.splice(i, 0, { + controlId, + value, + type: el.type, + control: el.control, + controlComponent: ControlComponent.VALUE + }) + i++ + } } } - } - for (let v = 0; v < valueList.length; v++) { - const element = valueList[v] - const valueStrList = element.value.split('') - for (let e = 0; e < valueStrList.length; e++) { - const value = valueStrList[e] - elementList.splice(i, 0, { - ...element, - controlId, - value, - type: el.type, - control: el.control, - controlComponent: ControlComponent.VALUE - }) - i++ + } else { + if (!value || !value.length) { + if (Array.isArray(valueSets) && valueSets.length) { + const valueSet = valueSets.find(v => v.code === code) + if (valueSet) { + valueList = [{ + value: valueSet.value + }] + } + } + } + for (let v = 0; v < valueList.length; v++) { + const element = valueList[v] + const valueStrList = element.value.split('') + for (let e = 0; e < valueStrList.length; e++) { + const value = valueStrList[e] + elementList.splice(i, 0, { + ...element, + controlId, + value, + type: el.type, + control: el.control, + controlComponent: ControlComponent.VALUE + }) + i++ + } } } } else { diff --git a/src/mock.ts b/src/mock.ts index 8858643..ea78682 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -250,18 +250,20 @@ elementList.push({ elementList.push(...[{ value: '是否同意以上内容:' }, { - type: ElementType.CHECKBOX, - checkbox: { - value: true + type: ElementType.CONTROL, + control: { + type: ControlType.CHECKBOX, + code: '98175', + value: '', + valueSets: [{ + value: '同意', + code: '98175' + }, { + value: '否定', + code: '98176' + }] }, value: '' -}, { - value: '同意' -}, { - type: ElementType.CHECKBOX, - value: '' -}, { - value: '否定' }, { value: '\n' }]) From 1315ce070b1f7f93f171a32110034e24bafa5654 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 15 Apr 2022 21:27:19 +0800 Subject: [PATCH 2/4] feat:checkbox control set select --- src/editor/core/draw/control/Control.ts | 3 +- .../draw/control/checkbox/CheckboxControl.ts | 45 +++++++++++++++++++ src/editor/core/event/CanvasEvent.ts | 5 +++ src/editor/interface/Checkbox.ts | 3 +- src/editor/utils/element.ts | 1 + 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 8dd242c..0073a2a 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -2,6 +2,7 @@ import { ControlComponent, ControlType } from '../../../dataset/enum/Control' import { ElementType } from '../../../dataset/enum/Element' import { IControl, IControlInitOption, IControlInstance, IControlOption } from '../../../interface/Control' import { IElement, IElementPosition } from '../../../interface/Element' +import { IRange } from '../../../interface/Range' import { deepClone } from '../../../utils' import { pickElementAttr, zipElementList } from '../../../utils/element' import { Listener } from '../../listener/Listener' @@ -67,7 +68,7 @@ export class Control { return this.draw.getPageNo() * (height + pageGap) } - public getRange() { + public getRange(): IRange { return this.range.getRange() } diff --git a/src/editor/core/draw/control/checkbox/CheckboxControl.ts b/src/editor/core/draw/control/checkbox/CheckboxControl.ts index d520ab9..fc1139b 100644 --- a/src/editor/core/draw/control/checkbox/CheckboxControl.ts +++ b/src/editor/core/draw/control/checkbox/CheckboxControl.ts @@ -65,6 +65,51 @@ export class CheckboxControl implements IControlInstance { return endIndex } + public setSelect() { + const { control } = this.element + const elementList = this.control.getElementList() + const { startIndex } = this.control.getRange() + const startElement = elementList[startIndex] + const data: string[] = [] + // 向左查找 + let preIndex = startIndex + while (preIndex > 0) { + const preElement = elementList[preIndex] + if ( + preElement.controlId !== startElement.controlId || + preElement.controlComponent === ControlComponent.PREFIX + ) { + break + } + if (preElement.controlComponent === ControlComponent.CHECKBOX) { + const checkbox = preElement.checkbox + if (checkbox && checkbox.value && checkbox.code) { + data.unshift(checkbox.code) + } + } + preIndex-- + } + // 向右查找 + let nextIndex = startIndex + 1 + while (nextIndex < elementList.length) { + const nextElement = elementList[nextIndex] + if ( + nextElement.controlId !== startElement.controlId || + nextElement.controlComponent === ControlComponent.POSTFIX + ) { + break + } + if (nextElement.controlComponent === ControlComponent.CHECKBOX) { + const checkbox = nextElement.checkbox + if (checkbox && checkbox.value && checkbox.code) { + data.push(checkbox.code) + } + } + nextIndex++ + } + control!.code = data.join(',') + } + public keydown(evt: KeyboardEvent): number { const range = this.control.getRange() // 收缩边界到Value内 diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index c03102c..9a20142 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -18,6 +18,7 @@ import { Position } from '../position/Position' import { RangeManager } from '../range/RangeManager' import { LETTER_REG, NUMBER_LIKE_REG } from '../../dataset/constant/Regular' import { Control } from '../draw/control/Control' +import { CheckboxControl } from '../draw/control/checkbox/CheckboxControl' export class CanvasEvent { @@ -236,6 +237,10 @@ export class CanvasEvent { value: true } } + const activeControl = this.control.getActiveControl() + if (activeControl instanceof CheckboxControl) { + activeControl.setSelect() + } } this.draw.render({ curIndex, diff --git a/src/editor/interface/Checkbox.ts b/src/editor/interface/Checkbox.ts index 055eedf..ee90b22 100644 --- a/src/editor/interface/Checkbox.ts +++ b/src/editor/interface/Checkbox.ts @@ -1,6 +1,7 @@ export interface ICheckbox { - disabled?: boolean; value: boolean | null; + code?: string; + disabled?: boolean; } export interface ICheckboxOption { diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index d77cd43..88bf7f1 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -114,6 +114,7 @@ export function formatElementList(elementList: IElement[], options: IFormatEleme control: el.control, controlComponent: ControlComponent.CHECKBOX, checkbox: { + code: valueSet.code, value: codeList.includes(valueSet.code) } }) From 440db738cb6723a621c5c083c630edd55489e184 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 15 Apr 2022 21:45:19 +0800 Subject: [PATCH 3/4] feat:add checkbox control menu --- index.html | 5 ++-- src/editor/core/draw/control/Control.ts | 1 + src/editor/interface/Control.ts | 2 +- src/editor/utils/element.ts | 2 +- src/main.ts | 36 +++++++++++++++++++++++-- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 68a38c4..d72c7e6 100644 --- a/index.html +++ b/index.html @@ -164,8 +164,9 @@
    -
  • 文本型
  • -
  • 列举型
  • +
  • 文本
  • +
  • 列举
  • +
  • 复选框
diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 0073a2a..a2ce8e8 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -274,6 +274,7 @@ export class Control { const elementList = this.getElementList() const startElement = elementList[startIndex] const control = startElement.control! + if (!control.placeholder) return const placeholderStrList = control.placeholder.split('') for (let p = 0; p < placeholderStrList.length; p++) { const value = placeholderStrList[p] diff --git a/src/editor/interface/Control.ts b/src/editor/interface/Control.ts index a691311..d14d7f6 100644 --- a/src/editor/interface/Control.ts +++ b/src/editor/interface/Control.ts @@ -23,7 +23,7 @@ export interface IControlCheckbox { export interface IControlBasic { type: ControlType; value: IElement[] | null; - placeholder: string; + placeholder?: string; conceptId?: string; prefix?: string; postfix?: string; diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index 88bf7f1..dcaa900 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -162,7 +162,7 @@ export function formatElementList(elementList: IElement[], options: IFormatEleme } } } - } else { + } else if (placeholder) { // placeholder const thePlaceholderArgs: Pick = {} if (editorOptions && editorOptions.control) { diff --git a/src/main.ts b/src/main.ts index 6e28da9..b8969ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -415,7 +415,7 @@ window.onload = function () { switch (type) { case ControlType.TEXT: new Dialog({ - title: '文本型控件', + title: '文本控件', data: [{ type: 'text', label: '占位符', @@ -449,7 +449,7 @@ window.onload = function () { break case ControlType.SELECT: new Dialog({ - title: '列举型控件', + title: '列举控件', data: [{ type: 'text', label: '占位符', @@ -487,6 +487,38 @@ window.onload = function () { } }) break + case ControlType.CHECKBOX: + new Dialog({ + title: '复选框控件', + data: [{ + type: 'text', + label: '默认值', + name: 'code', + placeholder: '请输入默认值,多个值以英文逗号分割' + }, { + type: 'textarea', + label: '值集', + name: 'valueSets', + height: 100, + placeholder: `请输入值集JSON,例:\n[{\n"value":"有",\n"code":"98175"\n}]` + }], + onConfirm: (payload) => { + const valueSets = payload.find(p => p.name === 'valueSets')?.value + if (!valueSets) return + const code = payload.find(p => p.name === 'code')?.value + instance.command.executeInsertElementList([{ + type: ElementType.CONTROL, + value: '', + control: { + type, + code, + value: null, + valueSets: JSON.parse(valueSets) + } + }]) + } + }) + break default: break } From 21de02f09361299adccf8d4c3b6286cbe03c5603 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 15 Apr 2022 21:50:35 +0800 Subject: [PATCH 4/4] feat:add checkbox control test case --- cypress/integration/control/checkbox.spec.ts | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 cypress/integration/control/checkbox.spec.ts diff --git a/cypress/integration/control/checkbox.spec.ts b/cypress/integration/control/checkbox.spec.ts new file mode 100644 index 0000000..f7d43cd --- /dev/null +++ b/cypress/integration/control/checkbox.spec.ts @@ -0,0 +1,47 @@ +import Editor, { ControlType, ElementType } from '../../../src/editor' + +describe('控件-复选框', () => { + + beforeEach(() => { + cy.visit('http://localhost:3000/canvas-editor/') + + cy.get('canvas').first().as('canvas').should('have.length', 1) + }) + + const elementType: ElementType = 'control' + const controlType: ControlType = 'checkbox' + + it('复选框', () => { + cy.getEditor().then((editor: Editor) => { + editor.listener.saved = function (payload) { + const data = payload.data[0] + + expect(data.control!.code).to.be.eq('98175') + } + + editor.command.executeSelectAll() + + editor.command.executeBackspace() + + editor.command.executeInsertElementList([{ + type: elementType, + value: '', + control: { + code: '98175', + type: controlType, + value: null, + valueSets: [{ + value: '有', + code: '98175' + }, { + value: '无', + code: '98176' + }] + } + }]) + + cy.get('@canvas').type('{ctrl}s') + }) + }) + +})