diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md
index eedf745..bd30ae8 100644
--- a/docs/en/guide/option.md
+++ b/docs/en/guide/option.md
@@ -61,7 +61,8 @@ interface IEditorOption {
wordBreak?: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD
watermark?: IWatermark // Watermark{data:string; color?:string; opacity?:number; size?:number; font?:string;}
control?: IControlOption // Control {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;}
- checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; fontStyle?: string;}
+ checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
+ radio?: IRadioOption // Radio {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;}
title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
placeholder?: IPlaceholder // Placeholder text
diff --git a/docs/en/guide/schema.md b/docs/en/guide/schema.md
index 65950c6..c56bb06 100644
--- a/docs/en/guide/schema.md
+++ b/docs/en/guide/schema.md
@@ -15,6 +15,7 @@ interface IElement {
PAGE_BREAK = 'pageBreak',
CONTROL = 'control',
CHECKBOX = 'checkbox',
+ RADIO = 'radio',
LATEX = 'latex',
TAB = 'tab',
DATE = 'date',
@@ -76,7 +77,8 @@ interface IElement {
type: {
TEXT = 'text',
SELECT = 'select',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
};
value: IElement[] | null;
placeholder?: string;
@@ -98,6 +100,13 @@ interface IElement {
code: string;
}[];
checkbox?: {
+ value: boolean | null;
+ code?: string;
+ min?: number;
+ max?: number;
+ disabled?: boolean;
+ };
+ radio?: {
value: boolean | null;
code?: string;
disabled?: boolean;
@@ -115,7 +124,8 @@ interface IElement {
POSTFIX = 'postfix',
PLACEHOLDER = 'placeholder',
VALUE = 'value',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
};
// checkbox
checkbox?: {
@@ -123,6 +133,12 @@ interface IElement {
code?: string;
disabled?: boolean;
};
+ // radio
+ radio?: {
+ value: boolean | null;
+ code?: string;
+ disabled?: boolean;
+ };
// LaTeX
laTexSVG?: string;
// date
diff --git a/docs/guide/option.md b/docs/guide/option.md
index c81a1fe..3b055fc 100644
--- a/docs/guide/option.md
+++ b/docs/guide/option.md
@@ -61,7 +61,8 @@ interface IEditorOption {
wordBreak?: WordBreak // 单词与标点断行:BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认:BREAK_WORD
watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;}
control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;}
- checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; fontStyle?: string;}
+ checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
+ radio?: IRadioOption // 单选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;}
cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;}
title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;}
placeholder?: IPlaceholder // 编辑器空白占位文本
diff --git a/docs/guide/schema.md b/docs/guide/schema.md
index 097c120..a2de5e5 100644
--- a/docs/guide/schema.md
+++ b/docs/guide/schema.md
@@ -15,6 +15,7 @@ interface IElement {
PAGE_BREAK = 'pageBreak',
CONTROL = 'control',
CHECKBOX = 'checkbox',
+ RADIO = 'radio',
LATEX = 'latex',
TAB = 'tab',
DATE = 'date',
@@ -76,7 +77,8 @@ interface IElement {
type: {
TEXT = 'text',
SELECT = 'select',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
};
value: IElement[] | null;
placeholder?: string;
@@ -98,6 +100,13 @@ interface IElement {
code: string;
}[];
checkbox?: {
+ value: boolean | null;
+ code?: string;
+ min?: number;
+ max?: number;
+ disabled?: boolean;
+ };
+ radio?: {
value: boolean | null;
code?: string;
disabled?: boolean;
@@ -115,7 +124,8 @@ interface IElement {
POSTFIX = 'postfix',
PLACEHOLDER = 'placeholder',
VALUE = 'value',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
};
// 复选框
checkbox?: {
@@ -123,6 +133,12 @@ interface IElement {
code?: string;
disabled?: boolean;
};
+ // 单选框
+ radio?: {
+ value: boolean | null;
+ code?: string;
+ disabled?: boolean;
+ };
// LaTeX
laTexSVG?: string;
// 日期
diff --git a/index.html b/index.html
index a33a93c..2cbbdcc 100644
--- a/index.html
+++ b/index.html
@@ -280,12 +280,16 @@
文本
列举
复选框
+ 单选框
+
diff --git a/src/assets/images/radio.svg b/src/assets/images/radio.svg
new file mode 100644
index 0000000..fff1439
--- /dev/null
+++ b/src/assets/images/radio.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts
index 9f5589d..e1c2f52 100644
--- a/src/editor/core/draw/Draw.ts
+++ b/src/editor/core/draw/Draw.ts
@@ -65,6 +65,7 @@ import {
import { Control } from './control/Control'
import { getSlimCloneElementList, zipElementList } from '../../utils/element'
import { CheckboxParticle } from './particle/CheckboxParticle'
+import { RadioParticle } from './particle/RadioParticle'
import { DeepRequired, IPadding } from '../../interface/Common'
import {
ControlComponent,
@@ -142,6 +143,7 @@ export class Draw {
private superscriptParticle: SuperscriptParticle
private subscriptParticle: SubscriptParticle
private checkboxParticle: CheckboxParticle
+ private radioParticle: RadioParticle
private blockParticle: BlockParticle
private listParticle: ListParticle
private lineBreakParticle: LineBreakParticle
@@ -216,6 +218,7 @@ export class Draw {
this.superscriptParticle = new SuperscriptParticle()
this.subscriptParticle = new SubscriptParticle()
this.checkboxParticle = new CheckboxParticle(this)
+ this.radioParticle = new RadioParticle(this)
this.blockParticle = new BlockParticle(this)
this.listParticle = new ListParticle(this)
this.lineBreakParticle = new LineBreakParticle(this)
@@ -751,6 +754,10 @@ export class Draw {
return this.checkboxParticle
}
+ public getRadioParticle(): RadioParticle {
+ return this.radioParticle
+ }
+
public getControl(): Control {
return this.control
}
@@ -1414,6 +1421,15 @@ export class Draw {
element.width = availableWidth / scale
metrics.width = availableWidth
metrics.height = defaultSize
+ } else if (
+ element.type === ElementType.RADIO ||
+ element.controlComponent === ControlComponent.RADIO
+ ) {
+ const { width, height, gap } = this.options.radio
+ const elementWidth = width + gap * 2
+ element.width = elementWidth
+ metrics.width = elementWidth * scale
+ metrics.height = height * scale
} else if (
element.type === ElementType.CHECKBOX ||
element.controlComponent === ControlComponent.CHECKBOX
@@ -1805,6 +1821,12 @@ export class Draw {
) {
this.textParticle.complete()
this.checkboxParticle.render(ctx, element, x, y + offsetY)
+ } else if (
+ element.type === ElementType.RADIO ||
+ element.controlComponent === ControlComponent.RADIO
+ ) {
+ this.textParticle.complete()
+ this.radioParticle.render(ctx, element, x, y + offsetY)
} else if (element.type === ElementType.TAB) {
this.textParticle.complete()
} else if (element.rowFlex === RowFlex.ALIGNMENT) {
diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts
index c2818d0..b3f4cfc 100644
--- a/src/editor/core/draw/control/Control.ts
+++ b/src/editor/core/draw/control/Control.ts
@@ -32,6 +32,7 @@ import { Listener } from '../../listener/Listener'
import { RangeManager } from '../../range/RangeManager'
import { Draw } from '../Draw'
import { CheckboxControl } from './checkbox/CheckboxControl'
+import { RadioControl } from './radio/RadioControl'
import { ControlSearch } from './interactive/ControlSearch'
import { ControlBorder } from './richtext/Border'
import { SelectControl } from './select/SelectControl'
@@ -236,6 +237,8 @@ export class Control {
selectControl.awake()
} else if (control.type === ControlType.CHECKBOX) {
this.activeControl = new CheckboxControl(element, this)
+ } else if (control.type === ControlType.RADIO) {
+ this.activeControl = new RadioControl(element, this)
}
// 激活控件回调
nextTick(() => {
@@ -522,7 +525,8 @@ export class Control {
})
} else if (
type === ControlType.SELECT ||
- type === ControlType.CHECKBOX
+ type === ControlType.CHECKBOX ||
+ type === ControlType.RADIO
) {
const innerText = code
?.split(',')
@@ -629,6 +633,10 @@ export class Control {
const checkbox = new CheckboxControl(element, this)
const codes = value?.split(',') || []
checkbox.setSelect(codes, controlContext, controlRule)
+ } else if (type === ControlType.RADIO) {
+ const radio = new RadioControl(element, this)
+ const codes = value ? [value] : []
+ radio.setSelect(codes, controlContext, controlRule)
}
// 修改后控件结束索引
let newEndIndex = i
diff --git a/src/editor/core/draw/control/checkbox/CheckboxControl.ts b/src/editor/core/draw/control/checkbox/CheckboxControl.ts
index a6a245f..23bdcc6 100644
--- a/src/editor/core/draw/control/checkbox/CheckboxControl.ts
+++ b/src/editor/core/draw/control/checkbox/CheckboxControl.ts
@@ -9,8 +9,8 @@ import { IElement } from '../../../../interface/Element'
import { Control } from '../Control'
export class CheckboxControl implements IControlInstance {
- private element: IElement
- private control: Control
+ protected element: IElement
+ protected control: Control
constructor(element: IElement, control: Control) {
this.element = element
diff --git a/src/editor/core/draw/control/radio/RadioControl.ts b/src/editor/core/draw/control/radio/RadioControl.ts
new file mode 100644
index 0000000..9965379
--- /dev/null
+++ b/src/editor/core/draw/control/radio/RadioControl.ts
@@ -0,0 +1,57 @@
+import { ControlComponent } from '../../../../dataset/enum/Control'
+import {
+ IControlContext,
+ IControlRuleOption
+} from '../../../../interface/Control'
+import { CheckboxControl } from '../checkbox/CheckboxControl'
+
+export class RadioControl extends CheckboxControl {
+ public setSelect(
+ codes: string[],
+ context: IControlContext = {},
+ options: IControlRuleOption = {}
+ ) {
+ // 校验是否可以设置
+ if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) {
+ return
+ }
+ const { control } = this.element
+ const elementList = context.elementList || this.control.getElementList()
+ const { startIndex } = context.range || this.control.getRange()
+ const startElement = elementList[startIndex]
+ // 向左查找
+ let preIndex = startIndex
+ while (preIndex > 0) {
+ const preElement = elementList[preIndex]
+ if (
+ preElement.controlId !== startElement.controlId ||
+ preElement.controlComponent === ControlComponent.PREFIX
+ ) {
+ break
+ }
+ if (preElement.controlComponent === ControlComponent.RADIO) {
+ const radio = preElement.radio!
+ radio.value = codes.includes(radio.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.RADIO) {
+ const radio = nextElement.radio!
+ radio.value = codes.includes(radio.code!)
+ }
+ nextIndex++
+ }
+ control!.code = codes.join(',')
+ this.control.repaintControl()
+ }
+}
diff --git a/src/editor/core/draw/particle/CheckboxParticle.ts b/src/editor/core/draw/particle/CheckboxParticle.ts
index b16e9a3..1a5817b 100644
--- a/src/editor/core/draw/particle/CheckboxParticle.ts
+++ b/src/editor/core/draw/particle/CheckboxParticle.ts
@@ -35,7 +35,7 @@ export class CheckboxParticle {
y: number
) {
const {
- checkbox: { gap, lineWidth, fillStyle, fontStyle },
+ checkbox: { gap, lineWidth, fillStyle, strokeStyle },
scale
} = this.options
const { metrics, checkbox } = element
@@ -60,7 +60,7 @@ export class CheckboxParticle {
ctx.fillRect(left, top, width, height)
// 勾选对号
ctx.beginPath()
- ctx.strokeStyle = fontStyle
+ ctx.strokeStyle = strokeStyle
ctx.lineWidth = lineWidth * 2 * scale
ctx.moveTo(left + 2 * scale, top + height / 2)
ctx.lineTo(left + width / 2, top + height - 3 * scale)
diff --git a/src/editor/core/draw/particle/RadioParticle.ts b/src/editor/core/draw/particle/RadioParticle.ts
new file mode 100644
index 0000000..a6a2e25
--- /dev/null
+++ b/src/editor/core/draw/particle/RadioParticle.ts
@@ -0,0 +1,65 @@
+import { DeepRequired } from '../../../interface/Common'
+import { IEditorOption } from '../../../interface/Editor'
+import { IElement } from '../../../interface/Element'
+import { IRowElement } from '../../../interface/Row'
+import { Draw } from '../Draw'
+
+export class RadioParticle {
+ private draw: Draw
+ private options: DeepRequired
+
+ constructor(draw: Draw) {
+ this.draw = draw
+ this.options = draw.getOptions()
+ }
+
+ public setSelect(element: IElement) {
+ const { radio } = element
+ if (radio) {
+ radio.value = !radio.value
+ } else {
+ element.radio = {
+ value: true
+ }
+ }
+ this.draw.render({
+ isCompute: false,
+ isSetCursor: false
+ })
+ }
+
+ public render(
+ ctx: CanvasRenderingContext2D,
+ element: IRowElement,
+ x: number,
+ y: number
+ ) {
+ const {
+ radio: { gap, lineWidth, fillStyle, strokeStyle },
+ scale
+ } = this.options
+ const { metrics, radio } = element
+ // left top 四舍五入避免1像素问题
+ const left = Math.round(x + gap * scale)
+ const top = Math.round(y - metrics.height + lineWidth)
+ const width = metrics.width - gap * 2 * scale
+ const height = metrics.height
+ ctx.save()
+ ctx.beginPath()
+ ctx.translate(0.5, 0.5)
+ // 边框
+ ctx.strokeStyle = radio?.value ? fillStyle : strokeStyle
+ ctx.lineWidth = lineWidth
+ ctx.arc(left + width / 2, top + height / 2, width / 2, 0, Math.PI * 2)
+ ctx.stroke()
+ // 填充选中色
+ if (radio?.value) {
+ ctx.beginPath()
+ ctx.fillStyle = fillStyle
+ ctx.arc(left + width / 2, top + height / 2, width / 3, 0, Math.PI * 2)
+ ctx.fill()
+ }
+ ctx.closePath()
+ ctx.restore()
+ }
+}
diff --git a/src/editor/core/event/handlers/click.ts b/src/editor/core/event/handlers/click.ts
index d2c3cc7..5c3fc05 100644
--- a/src/editor/core/event/handlers/click.ts
+++ b/src/editor/core/event/handlers/click.ts
@@ -124,8 +124,13 @@ function dblclick(host: CanvasEvent, evt: MouseEvent) {
return
}
}
- // 复选框双击时是切换选择状态,禁用扩选
- if (positionContext.isCheckbox && positionContext.isDirectHit) return
+ // 复选/单选框双击时是切换选择状态,禁用扩选
+ if (
+ (positionContext.isCheckbox || positionContext.isRadio) &&
+ positionContext.isDirectHit
+ ) {
+ return
+ }
// 自动扩选文字-分词处理,优先使用分词器否则降级使用光标所在位置
const rangeManager = draw.getRange()
const segmenterRange =
diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts
index 8ba08c4..3dfc1fa 100644
--- a/src/editor/core/event/handlers/mousedown.ts
+++ b/src/editor/core/event/handlers/mousedown.ts
@@ -4,6 +4,7 @@ import { MouseEventButton } from '../../../dataset/enum/Event'
import { deepClone } from '../../../utils'
import { isMod } from '../../../utils/hotkey'
import { CheckboxControl } from '../../draw/control/checkbox/CheckboxControl'
+import { RadioControl } from '../../draw/control/radio/RadioControl'
import { CanvasEvent } from '../CanvasEvent'
export function setRangeCache(host: CanvasEvent) {
@@ -54,6 +55,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
index,
isDirectHit,
isCheckbox,
+ isRadio,
isImage,
isTable,
tdValueIndex,
@@ -73,11 +75,14 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
// 绘制
const isDirectHitImage = !!(isDirectHit && isImage)
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
+ const isDirectHitRadio = !!(isDirectHit && isRadio)
if (~index) {
rangeManager.setRange(curIndex, curIndex)
position.setCursorPosition(positionList[curIndex])
// 复选框
const isSetCheckbox = isDirectHitCheckbox && !isReadonly
+ // 单选框
+ const isSetRadio = isDirectHitRadio && !isReadonly
if (isSetCheckbox) {
const { checkbox, control } = curElement
// 复选框不在控件内独立控制
@@ -98,12 +103,25 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
activeControl.setSelect(codes)
}
}
+ } else if (isSetRadio) {
+ const { control, radio } = curElement
+ // 单选框不在控件内独立控制
+ if (!control) {
+ draw.getRadioParticle().setSelect(curElement)
+ } else {
+ const codes = radio?.code ? [radio.code] : []
+ const activeControl = draw.getControl().getActiveControl()
+ if (activeControl instanceof RadioControl) {
+ activeControl.setSelect(codes)
+ }
+ }
} else {
draw.render({
curIndex,
isCompute: false,
isSubmitHistory: false,
- isSetCursor: !isDirectHitImage && !isDirectHitCheckbox
+ isSetCursor:
+ !isDirectHitImage && !isDirectHitCheckbox && !isDirectHitRadio
})
}
// 首字需定位到行首,非上一行最后一个字后
diff --git a/src/editor/core/position/Position.ts b/src/editor/core/position/Position.ts
index c193732..3faa0fe 100644
--- a/src/editor/core/position/Position.ts
+++ b/src/editor/core/position/Position.ts
@@ -394,6 +394,9 @@ export class Position {
tdValueElement.type === ElementType.CHECKBOX ||
tdValueElement.controlComponent ===
ControlComponent.CHECKBOX,
+ isRadio:
+ tdValueElement.type === ElementType.RADIO ||
+ tdValueElement.controlComponent === ControlComponent.RADIO,
isControl: !!tdValueElement.controlId,
isImage: tablePosition.isImage,
isDirectHit: tablePosition.isDirectHit,
@@ -431,6 +434,16 @@ export class Position {
isCheckbox: true
}
}
+ if (
+ element.type === ElementType.RADIO ||
+ element.controlComponent === ControlComponent.RADIO
+ ) {
+ return {
+ index: curPositionIndex,
+ isDirectHit: true,
+ isRadio: true
+ }
+ }
let hitLineStartIndex: number | undefined
// 判断是否在文字中间前后
if (elementList[index].value !== ZERO) {
@@ -651,6 +664,7 @@ export class Position {
const {
index,
isCheckbox,
+ isRadio,
isControl,
isTable,
trIndex,
@@ -663,6 +677,7 @@ export class Position {
this.setPositionContext({
isTable: isTable || false,
isCheckbox: isCheckbox || false,
+ isRadio: isRadio || false,
isControl: isControl || false,
index,
trIndex,
diff --git a/src/editor/core/worker/works/catalog.ts b/src/editor/core/worker/works/catalog.ts
index dc6a2b9..c31b5f3 100644
--- a/src/editor/core/worker/works/catalog.ts
+++ b/src/editor/core/worker/works/catalog.ts
@@ -12,6 +12,7 @@ enum ElementType {
PAGE_BREAK = 'pageBreak',
CONTROL = 'control',
CHECKBOX = 'checkbox',
+ RADIO = 'radio',
LATEX = 'latex',
TAB = 'tab',
DATE = 'date',
diff --git a/src/editor/dataset/constant/Checkbox.ts b/src/editor/dataset/constant/Checkbox.ts
index 7df38b0..3153027 100644
--- a/src/editor/dataset/constant/Checkbox.ts
+++ b/src/editor/dataset/constant/Checkbox.ts
@@ -6,5 +6,5 @@ export const defaultCheckboxOption: Readonly> = {
gap: 5,
lineWidth: 1,
fillStyle: '#5175f4',
- fontStyle: '#ffffff'
+ strokeStyle: '#ffffff'
}
diff --git a/src/editor/dataset/constant/Element.ts b/src/editor/dataset/constant/Element.ts
index 3476224..2d65579 100644
--- a/src/editor/dataset/constant/Element.ts
+++ b/src/editor/dataset/constant/Element.ts
@@ -60,6 +60,7 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array = [
'valueList',
'control',
'checkbox',
+ 'radio',
'dateFormat',
'block',
'level',
diff --git a/src/editor/dataset/constant/Radio.ts b/src/editor/dataset/constant/Radio.ts
new file mode 100644
index 0000000..118d783
--- /dev/null
+++ b/src/editor/dataset/constant/Radio.ts
@@ -0,0 +1,10 @@
+import { IRadioOption } from '../../interface/Radio'
+
+export const defaultRadioOption: Readonly> = {
+ width: 14,
+ height: 14,
+ gap: 5,
+ lineWidth: 1,
+ fillStyle: '#5175f4',
+ strokeStyle: '#000000'
+}
diff --git a/src/editor/dataset/enum/Control.ts b/src/editor/dataset/enum/Control.ts
index 80308ee..61a52d8 100644
--- a/src/editor/dataset/enum/Control.ts
+++ b/src/editor/dataset/enum/Control.ts
@@ -1,7 +1,8 @@
export enum ControlType {
TEXT = 'text',
SELECT = 'select',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
}
export enum ControlComponent {
@@ -9,7 +10,8 @@ export enum ControlComponent {
POSTFIX = 'postfix',
PLACEHOLDER = 'placeholder',
VALUE = 'value',
- CHECKBOX = 'checkbox'
+ CHECKBOX = 'checkbox',
+ RADIO = 'radio'
}
// 控件内容缩进方式
diff --git a/src/editor/dataset/enum/Element.ts b/src/editor/dataset/enum/Element.ts
index bffe15c..23a5dc6 100644
--- a/src/editor/dataset/enum/Element.ts
+++ b/src/editor/dataset/enum/Element.ts
@@ -9,6 +9,7 @@ export enum ElementType {
PAGE_BREAK = 'pageBreak',
CONTROL = 'control',
CHECKBOX = 'checkbox',
+ RADIO = 'radio',
LATEX = 'latex',
TAB = 'tab',
DATE = 'date',
diff --git a/src/editor/index.ts b/src/editor/index.ts
index a3bf4e5..d0a2380 100644
--- a/src/editor/index.ts
+++ b/src/editor/index.ts
@@ -32,7 +32,9 @@ import { ControlIndentation, ControlType } from './dataset/enum/Control'
import { defaultControlOption } from './dataset/constant/Control'
import { IControlOption } from './interface/Control'
import { ICheckboxOption } from './interface/Checkbox'
+import { IRadioOption } from './interface/Radio'
import { defaultCheckboxOption } from './dataset/constant/Checkbox'
+import { defaultRadioOption } from './dataset/constant/Radio'
import { DeepRequired } from './interface/Common'
import { INavigateInfo } from './core/draw/interactive/Search'
import { Shortcut } from './core/shortcut/Shortcut'
@@ -117,6 +119,10 @@ export default class Editor {
...defaultCheckboxOption,
...options.checkbox
}
+ const radioOptions: Required = {
+ ...defaultRadioOption,
+ ...options.radio
+ }
const cursorOptions: Required = {
...defaultCursorOption,
...options.cursor
@@ -200,6 +206,7 @@ export default class Editor {
watermark: waterMarkOptions,
control: controlOptions,
checkbox: checkboxOptions,
+ radio: radioOptions,
cursor: cursorOptions,
title: titleOptions,
placeholder: placeholderOptions,
diff --git a/src/editor/interface/Checkbox.ts b/src/editor/interface/Checkbox.ts
index 71fb359..13ad547 100644
--- a/src/editor/interface/Checkbox.ts
+++ b/src/editor/interface/Checkbox.ts
@@ -10,5 +10,5 @@ export interface ICheckboxOption {
gap?: number
lineWidth?: number
fillStyle?: string
- fontStyle?: string
+ strokeStyle?: string
}
diff --git a/src/editor/interface/Control.ts b/src/editor/interface/Control.ts
index ed80f5c..430475a 100644
--- a/src/editor/interface/Control.ts
+++ b/src/editor/interface/Control.ts
@@ -2,6 +2,7 @@ import { ControlType, ControlIndentation } from '../dataset/enum/Control'
import { EditorZone } from '../dataset/enum/Editor'
import { ICheckbox } from './Checkbox'
import { IElement } from './Element'
+import { IRadio } from './Radio'
import { IRange } from './Range'
export interface IValueSet {
@@ -22,6 +23,12 @@ export interface IControlCheckbox {
checkbox?: ICheckbox
}
+export interface IControlRadio {
+ code: string | null
+ valueSets: IValueSet[]
+ radio?: IRadio
+}
+
export interface IControlHighlightRule {
keyword: string
alpha?: number
@@ -65,6 +72,7 @@ export type IControl = IControlBasic &
IControlRule &
Partial &
Partial &
+ Partial &
Partial
export interface IControlOption {
diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts
index 3f2f440..ab491ef 100644
--- a/src/editor/interface/Editor.ts
+++ b/src/editor/interface/Editor.ts
@@ -7,6 +7,7 @@ import {
} from '../dataset/enum/Editor'
import { IBackgroundOption } from './Background'
import { ICheckboxOption } from './Checkbox'
+import { IRadioOption } from './Radio'
import { IPadding } from './Common'
import { IControlOption } from './Control'
import { ICursorOption } from './Cursor'
@@ -77,6 +78,7 @@ export interface IEditorOption {
watermark?: IWatermark
control?: IControlOption
checkbox?: ICheckboxOption
+ radio?: IRadioOption
cursor?: ICursorOption
title?: ITitleOption
placeholder?: IPlaceholder
diff --git a/src/editor/interface/Element.ts b/src/editor/interface/Element.ts
index 47cbf5b..97a7f92 100644
--- a/src/editor/interface/Element.ts
+++ b/src/editor/interface/Element.ts
@@ -8,6 +8,7 @@ import { TableBorder } from '../dataset/enum/table/Table'
import { IBlock } from './Block'
import { ICheckbox } from './Checkbox'
import { IControl } from './Control'
+import { IRadio } from './Radio'
import { ITextDecoration } from './Text'
import { IColgroup } from './table/Colgroup'
import { ITr } from './table/Tr'
@@ -94,6 +95,10 @@ export interface ICheckboxElement {
checkbox?: ICheckbox
}
+export interface IRadioElement {
+ radio?: IRadio
+}
+
export interface ILaTexElement {
laTexSVG?: string
}
@@ -124,6 +129,7 @@ export type IElement = IElementBasic &
ISeparator &
IControlElement &
ICheckboxElement &
+ IRadioElement &
ILaTexElement &
IDateElement &
IImageElement &
diff --git a/src/editor/interface/Position.ts b/src/editor/interface/Position.ts
index 7033b7f..b5c030b 100644
--- a/src/editor/interface/Position.ts
+++ b/src/editor/interface/Position.ts
@@ -9,6 +9,7 @@ export interface ICurrentPosition {
x?: number
y?: number
isCheckbox?: boolean
+ isRadio?: boolean
isControl?: boolean
isImage?: boolean
isTable?: boolean
@@ -41,6 +42,7 @@ export type IGetFloatPositionByXYPayload = IGetPositionByXYPayload & {
export interface IPositionContext {
isTable: boolean
isCheckbox?: boolean
+ isRadio?: boolean
isControl?: boolean
index?: number
trIndex?: number
diff --git a/src/editor/interface/Radio.ts b/src/editor/interface/Radio.ts
new file mode 100644
index 0000000..1f4fc61
--- /dev/null
+++ b/src/editor/interface/Radio.ts
@@ -0,0 +1,14 @@
+export interface IRadio {
+ value: boolean | null
+ code?: string
+ disabled?: boolean
+}
+
+export interface IRadioOption {
+ width?: number
+ height?: number
+ gap?: number
+ lineWidth?: number
+ fillStyle?: string
+ strokeStyle?: string
+}
diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts
index 62f2294..4bf0ca3 100644
--- a/src/editor/utils/element.ts
+++ b/src/editor/utils/element.ts
@@ -222,7 +222,11 @@ export function formatElementList(
const { prefix, postfix, value, placeholder, code, type, valueSets } =
el.control
const {
- editorOptions: { control: controlOption, checkbox: checkboxOption }
+ editorOptions: {
+ control: controlOption,
+ checkbox: checkboxOption,
+ radio: radioOption
+ }
} = options
const controlId = getUUID()
// 移除父节点
@@ -258,6 +262,7 @@ export function formatElementList(
if (
(value && value.length) ||
type === ControlType.CHECKBOX ||
+ type === ControlType.RADIO ||
(type === ControlType.SELECT && code && (!value || !value.length))
) {
let valueList: IElement[] = value || []
@@ -309,6 +314,53 @@ export function formatElementList(
}
}
}
+ } else if (type === ControlType.RADIO) {
+ if (Array.isArray(valueSets) && valueSets.length) {
+ // 拆分valueList优先使用其属性
+ const valueStyleList = valueList.reduce(
+ (pre, cur) =>
+ pre.concat(
+ cur.value.split('').map(v => ({ ...cur, value: v }))
+ ),
+ [] as IElement[]
+ )
+ let valueStyleIndex = 0
+ for (let v = 0; v < valueSets.length; v++) {
+ const valueSet = valueSets[v]
+ // radio组件
+ elementList.splice(i, 0, {
+ ...controlContext,
+ controlId,
+ value: '',
+ type: el.type,
+ control: el.control,
+ controlComponent: ControlComponent.RADIO,
+ radio: {
+ code: valueSet.code,
+ value: code === valueSet.code
+ }
+ })
+ i++
+ // 文本
+ const valueStrList = splitText(valueSet.value)
+ for (let e = 0; e < valueStrList.length; e++) {
+ const value = valueStrList[e]
+ const isLastLetter = e === valueStrList.length - 1
+ elementList.splice(i, 0, {
+ ...controlContext,
+ ...controlDefaultStyle,
+ ...valueStyleList[valueStyleIndex],
+ controlId,
+ value: value === '\n' ? ZERO : value,
+ letterSpacing: isLastLetter ? radioOption.gap : 0,
+ control: el.control,
+ controlComponent: ControlComponent.VALUE
+ })
+ valueStyleIndex++
+ i++
+ }
+ }
+ }
} else {
if (!value || !value.length) {
if (Array.isArray(valueSets) && valueSets.length) {
@@ -955,6 +1007,13 @@ export function createDomFromElementList(
checkbox.setAttribute('checked', 'true')
}
clipboardDom.append(checkbox)
+ } else if (element.type === ElementType.RADIO) {
+ const radio = document.createElement('input')
+ radio.type = 'radio'
+ if (element.radio?.value) {
+ radio.setAttribute('checked', 'true')
+ }
+ clipboardDom.append(radio)
} else if (element.type === ElementType.TAB) {
const tab = document.createElement('span')
tab.innerHTML = `${NON_BREAKING_SPACE}${NON_BREAKING_SPACE}`
@@ -1200,6 +1259,17 @@ export function getElementListByHTML(
value: (node).checked
}
})
+ } else if (
+ node.nodeName === 'INPUT' &&
+ (node).type === ControlComponent.RADIO
+ ) {
+ elementList.push({
+ type: ElementType.RADIO,
+ value: '',
+ radio: {
+ value: (node).checked
+ }
+ })
} else {
findTextNode(node)
if (node.nodeType === 1 && n !== childNodes.length - 1) {
@@ -1275,6 +1345,8 @@ export function getTextFromElementList(elementList: IElement[]) {
})
} else if (element.type === ElementType.CHECKBOX) {
text += element.checkbox?.value ? `☑` : `□`
+ } else if (element.type === ElementType.RADIO) {
+ text += element.radio?.value ? `☉` : `○`
} else if (
!element.type ||
element.type === ElementType.LATEX ||
diff --git a/src/main.ts b/src/main.ts
index 1ae3981..a3846f1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -768,6 +768,44 @@ window.onload = function () {
}
})
break
+ case ControlType.RADIO:
+ new Dialog({
+ title: '单选框控件',
+ data: [
+ {
+ type: 'text',
+ label: '默认值',
+ name: 'code',
+ placeholder: '请输入默认值'
+ },
+ {
+ type: 'textarea',
+ label: '值集',
+ name: 'valueSets',
+ required: true,
+ 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
}
@@ -789,6 +827,20 @@ window.onload = function () {
])
}
+ const radioDom = document.querySelector('.menu-item__radio')!
+ radioDom.onclick = function () {
+ console.log('radio')
+ instance.command.executeInsertElementList([
+ {
+ type: ElementType.RADIO,
+ checkbox: {
+ value: false
+ },
+ value: ''
+ }
+ ])
+ }
+
const latexDom = document.querySelector('.menu-item__latex')!
latexDom.onclick = function () {
console.log('LaTeX')
diff --git a/src/style.css b/src/style.css
index 5f9ef23..959a6b5 100644
--- a/src/style.css
+++ b/src/style.css
@@ -540,6 +540,10 @@ ul {
background-image: url('./assets/images/checkbox.svg');
}
+.menu-item__radio i {
+ background-image: url('./assets/images/radio.svg');
+}
+
.menu-item__latex i {
background-image: url('./assets/images/latex.svg');
}