+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"video": false,
|
||||
"viewportWidth": 1080,
|
||||
"viewportWidth": 1366,
|
||||
"viewportHeight": 720
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Editor, { ElementType } from '../../../src/editor'
|
||||
|
||||
describe('菜单-复选框', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:3000/canvas-editor/')
|
||||
|
||||
cy.get('canvas').first().as('canvas').should('have.length', 1)
|
||||
})
|
||||
|
||||
const type: ElementType = <ElementType>'checkbox'
|
||||
|
||||
it('代码块', () => {
|
||||
cy.getEditor().then((editor: Editor) => {
|
||||
editor.listener.saved = function (payload) {
|
||||
const data = payload.data[0]
|
||||
|
||||
expect(data.checkbox?.value).to.eq(true)
|
||||
}
|
||||
|
||||
editor.command.executeSelectAll()
|
||||
|
||||
editor.command.executeBackspace()
|
||||
|
||||
editor.command.executeInsertElementList([{
|
||||
type,
|
||||
value: '',
|
||||
checkbox: {
|
||||
value: true
|
||||
}
|
||||
}])
|
||||
|
||||
cy.get('@canvas').type('{ctrl}s')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -169,6 +169,9 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item__checkbox">
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-divider"></div>
|
||||
<div class="menu-item">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 16 16" xml:space="preserve"><style>.st0{fill:#3d4757}</style><g id="_x30_1-文字_x2F_01开始_x2F_任务列表-16px"><path id="合并形状" class="st0" d="M10.1 2H2v11h11V8.7l1-1V13c0 .6-.4 1-1 1H2c-.6 0-1-.4-1-1V2c0-.6.4-1 1-1h9.1l-1 1z"/><path id="路径" class="st0" d="M7.7 8.5l5.7-5.8.9.8-6.1 5.9-.5.5-3.9-3.4.8-.7z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
@@ -37,6 +37,8 @@ import { Watermark } from './frame/Watermark'
|
||||
import { EditorMode } from '../../dataset/enum/Editor'
|
||||
import { Control } from './control/Control'
|
||||
import { zipElementList } from '../../utils/element'
|
||||
import { CheckboxParticle } from './particle/CheckboxParticle'
|
||||
import { DeepRequired } from '../../interface/Common'
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -46,7 +48,7 @@ export class Draw {
|
||||
private ctxList: CanvasRenderingContext2D[]
|
||||
private pageNo: number
|
||||
private mode: EditorMode
|
||||
private options: Required<IEditorOption>
|
||||
private options: DeepRequired<IEditorOption>
|
||||
private position: Position
|
||||
private elementList: IElement[]
|
||||
private listener: Listener
|
||||
@@ -73,6 +75,7 @@ export class Draw {
|
||||
private pageBreakParticle: PageBreakParticle
|
||||
private superscriptParticle: SuperscriptParticle
|
||||
private subscriptParticle: SubscriptParticle
|
||||
private checkboxParticle: CheckboxParticle
|
||||
private control: Control
|
||||
|
||||
private rowList: IRow[]
|
||||
@@ -83,7 +86,7 @@ export class Draw {
|
||||
|
||||
constructor(
|
||||
container: HTMLDivElement,
|
||||
options: Required<IEditorOption>,
|
||||
options: DeepRequired<IEditorOption>,
|
||||
elementList: IElement[],
|
||||
listener: Listener
|
||||
) {
|
||||
@@ -120,6 +123,7 @@ export class Draw {
|
||||
this.pageBreakParticle = new PageBreakParticle(this)
|
||||
this.superscriptParticle = new SuperscriptParticle()
|
||||
this.subscriptParticle = new SubscriptParticle()
|
||||
this.checkboxParticle = new CheckboxParticle(this)
|
||||
this.control = new Control(this)
|
||||
|
||||
new ScrollObserver(this)
|
||||
@@ -249,7 +253,7 @@ export class Draw {
|
||||
return this.ctxList[this.pageNo]
|
||||
}
|
||||
|
||||
public getOptions(): Required<IEditorOption> {
|
||||
public getOptions(): DeepRequired<IEditorOption> {
|
||||
return this.options
|
||||
}
|
||||
|
||||
@@ -563,6 +567,12 @@ export class Draw {
|
||||
element.width = innerWidth
|
||||
metrics.width = innerWidth
|
||||
metrics.height = this.options.defaultSize
|
||||
} else if (element.type === ElementType.CHECKBOX) {
|
||||
const { width, height, gap } = this.options.checkbox
|
||||
const elementWidth = (width + gap * 2) * scale
|
||||
element.width = elementWidth
|
||||
metrics.width = elementWidth
|
||||
metrics.height = height * scale
|
||||
} else {
|
||||
// 设置上下标真实字体尺寸
|
||||
const size = element.size || this.options.defaultSize
|
||||
@@ -697,6 +707,9 @@ export class Draw {
|
||||
if (this.mode !== EditorMode.CLEAN) {
|
||||
this.pageBreakParticle.render(ctx, element, x, y)
|
||||
}
|
||||
} else if (element.type === ElementType.CHECKBOX) {
|
||||
this.textParticle.complete()
|
||||
this.checkboxParticle.render(ctx, element, x, y + offsetY)
|
||||
} else {
|
||||
this.textParticle.record(ctx, element, x, y + offsetY)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { DeepRequired } from '../../../interface/Common'
|
||||
import { IEditorOption } from '../../../interface/Editor'
|
||||
import { IRowElement } from '../../../interface/Row'
|
||||
import { Draw } from '../Draw'
|
||||
|
||||
export class CheckboxParticle {
|
||||
|
||||
private options: DeepRequired<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) {
|
||||
const { checkbox: { gap, lineWidth, fillStyle, fontStyle }, scale } = this.options
|
||||
const { metrics, checkbox } = element
|
||||
// left top 四舍五入避免1像素问题
|
||||
const left = Math.round(x + gap)
|
||||
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)
|
||||
// 绘制勾选状态
|
||||
if (checkbox?.value) {
|
||||
// 边框
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.strokeStyle = fillStyle
|
||||
ctx.rect(left, top, width, height)
|
||||
ctx.stroke()
|
||||
// 背景色
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = fillStyle
|
||||
ctx.fillRect(left, top, width, height)
|
||||
// 勾选对号
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = fontStyle
|
||||
ctx.lineWidth = lineWidth * 2
|
||||
ctx.moveTo(left + 2 * scale, top + 7 * scale)
|
||||
ctx.lineTo(left + 7 * scale, top + 11 * scale)
|
||||
ctx.moveTo(left + 6.5 * scale, top + 11 * scale)
|
||||
ctx.lineTo(left + 12 * scale, top + 3 * scale)
|
||||
ctx.stroke()
|
||||
} else {
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.rect(left, top, width, height)
|
||||
ctx.stroke()
|
||||
}
|
||||
ctx.closePath()
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -188,6 +188,7 @@ export class CanvasEvent {
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
isCheckbox,
|
||||
isControl,
|
||||
isImage,
|
||||
isTable,
|
||||
@@ -214,27 +215,34 @@ export class CanvasEvent {
|
||||
...positionResult,
|
||||
index: isTable ? tdValueIndex! : index
|
||||
}
|
||||
// 绘制
|
||||
const isDirectHitImage = isDirectHit && isImage
|
||||
if (~index) {
|
||||
let curIndex = index
|
||||
if (isTable) {
|
||||
this.range.setRange(tdValueIndex!, tdValueIndex!)
|
||||
curIndex = tdValueIndex!
|
||||
} else {
|
||||
this.range.setRange(index, index)
|
||||
}
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: !isDirectHitImage,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
const elementList = this.draw.getElementList()
|
||||
const positionList = this.position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
const curElement = elementList[curIndex]
|
||||
// 绘制
|
||||
const isDirectHitImage = !!(isDirectHit && isImage)
|
||||
const isDirectHitCheckbox = !!(isDirectHit && isCheckbox)
|
||||
if (~index) {
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
// 复选框
|
||||
const isSetCheckbox = isDirectHitCheckbox && !isReadonly
|
||||
if (isSetCheckbox) {
|
||||
const { checkbox } = curElement
|
||||
if (checkbox) {
|
||||
checkbox.value = !checkbox.value
|
||||
} else {
|
||||
curElement.checkbox = {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
}
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: isSetCheckbox,
|
||||
isSetCursor: !isDirectHitImage && !isDirectHitCheckbox,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
// 图片尺寸拖拽组件
|
||||
this.imageParticle.clearResizer()
|
||||
if (isDirectHitImage && !isReadonly) {
|
||||
|
||||
@@ -94,9 +94,11 @@ export class Position {
|
||||
})
|
||||
if (~tablePosition.index) {
|
||||
const { index: tdValueIndex } = tablePosition
|
||||
const tdValueElement = td.value[tdValueIndex]
|
||||
return {
|
||||
index,
|
||||
isControl: td.value[tdValueIndex].type === ElementType.CONTROL,
|
||||
isCheckbox: tdValueElement.type === ElementType.CHECKBOX,
|
||||
isControl: tdValueElement.type === ElementType.CONTROL,
|
||||
isImage: tablePosition.isImage,
|
||||
isDirectHit: tablePosition.isDirectHit,
|
||||
isTable: true,
|
||||
@@ -119,6 +121,13 @@ export class Position {
|
||||
isImage: true
|
||||
}
|
||||
}
|
||||
if (element.type === ElementType.CHECKBOX) {
|
||||
return {
|
||||
index: curPositionIndex,
|
||||
isDirectHit: true,
|
||||
isCheckbox: true
|
||||
}
|
||||
}
|
||||
// 判断是否在文字中间前后
|
||||
if (elementList[index].value !== ZERO) {
|
||||
const valueWidth = rightTop[0] - leftTop[0]
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ICheckboxOption } from '../../interface/Checkbox'
|
||||
|
||||
export const defaultCheckboxOption: Readonly<Required<ICheckboxOption>> = {
|
||||
width: 14,
|
||||
height: 14,
|
||||
gap: 5,
|
||||
lineWidth: 1,
|
||||
fillStyle: '#5175f4',
|
||||
fontStyle: '#ffffff'
|
||||
}
|
||||
@@ -46,7 +46,8 @@ export const EDITOR_ELEMENT_ZIP_ATTR: Array<keyof IElement> = [
|
||||
'url',
|
||||
'colgroup',
|
||||
'valueList',
|
||||
'control'
|
||||
'control',
|
||||
'checkbox'
|
||||
]
|
||||
|
||||
export const TEXTLIKE_ELEMENT_TYPE: ElementType[] = [
|
||||
|
||||
@@ -7,5 +7,6 @@ export enum ElementType {
|
||||
SUBSCRIPT = 'subscript',
|
||||
SEPARATOR = 'separator',
|
||||
PAGE_BREAK = 'pageBreak',
|
||||
CONTROL = 'control'
|
||||
CONTROL = 'control',
|
||||
CHECKBOX = 'checkbox'
|
||||
}
|
||||
+11
-2
@@ -22,6 +22,9 @@ import { defaultWatermarkOption } from './dataset/constant/Watermark'
|
||||
import { ControlType } from './dataset/enum/Control'
|
||||
import { defaultControlOption } from './dataset/constant/Control'
|
||||
import { IControlOption } from './interface/Control'
|
||||
import { ICheckboxOption } from './interface/Checkbox'
|
||||
import { defaultCheckboxOption } from './dataset/constant/Checkbox'
|
||||
import { DeepRequired } from './interface/Common'
|
||||
|
||||
export default class Editor {
|
||||
|
||||
@@ -42,7 +45,12 @@ export default class Editor {
|
||||
...defaultControlOption,
|
||||
...options.control
|
||||
}
|
||||
const editorOptions: Required<IEditorOption> = {
|
||||
const checkboxOptions: Required<ICheckboxOption> = {
|
||||
...defaultCheckboxOption,
|
||||
...options.checkbox
|
||||
}
|
||||
|
||||
const editorOptions: DeepRequired<IEditorOption> = {
|
||||
defaultMode: EditorMode.EDIT,
|
||||
defaultType: 'TEXT',
|
||||
defaultFont: 'Yahei',
|
||||
@@ -76,7 +84,8 @@ export default class Editor {
|
||||
...options,
|
||||
header: headerOptions,
|
||||
watermark: waterMarkOptions,
|
||||
control: controlOptions
|
||||
control: controlOptions,
|
||||
checkbox: checkboxOptions
|
||||
}
|
||||
formatElementList(elementList, {
|
||||
editorOptions
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface ICheckbox {
|
||||
disabled?: boolean;
|
||||
value: boolean | null;
|
||||
}
|
||||
|
||||
export interface ICheckboxOption {
|
||||
width?: number;
|
||||
height?: number;
|
||||
gap?: number;
|
||||
lineWidth?: number;
|
||||
fillStyle?: string;
|
||||
fontStyle?: string;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IElement } from '..'
|
||||
import { EditorMode } from '../dataset/enum/Editor'
|
||||
import { ICheckboxOption } from './Checkbox'
|
||||
import { IControlOption } from './Control'
|
||||
import { IHeader } from './Header'
|
||||
import { IWatermark } from './Watermark'
|
||||
@@ -38,6 +39,7 @@ export interface IEditorOption {
|
||||
header?: IHeader;
|
||||
watermark?: IWatermark;
|
||||
control?: IControlOption;
|
||||
checkbox?: ICheckboxOption;
|
||||
}
|
||||
|
||||
export interface IEditorResult {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ControlComponent } from '../dataset/enum/Control'
|
||||
import { ElementType } from '../dataset/enum/Element'
|
||||
import { RowFlex } from '../dataset/enum/Row'
|
||||
import { ICheckbox } from './Checkbox'
|
||||
import { IControl } from './Control'
|
||||
import { IColgroup } from './table/Colgroup'
|
||||
import { ITr } from './table/Tr'
|
||||
@@ -59,6 +60,10 @@ export interface IControlElement {
|
||||
controlComponent?: ControlComponent;
|
||||
}
|
||||
|
||||
export interface ICheckboxElement {
|
||||
checkbox?: ICheckbox;
|
||||
}
|
||||
|
||||
export type IElement = IElementBasic
|
||||
& IElementStyle
|
||||
& ITable
|
||||
@@ -66,6 +71,7 @@ export type IElement = IElementBasic
|
||||
& ISuperscriptSubscript
|
||||
& ISeparator
|
||||
& IControlElement
|
||||
& ICheckboxElement
|
||||
|
||||
export interface IElementMetrics {
|
||||
width: number;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ITd } from './table/Td'
|
||||
|
||||
export interface ICurrentPosition {
|
||||
index: number;
|
||||
isCheckbox?: boolean;
|
||||
isControl?: boolean;
|
||||
isImage?: boolean;
|
||||
isTable?: boolean;
|
||||
|
||||
+11
-1
@@ -492,6 +492,15 @@ window.onload = function () {
|
||||
}
|
||||
}
|
||||
|
||||
const checkboxDom = document.querySelector<HTMLDivElement>('.menu-item__checkbox')!
|
||||
checkboxDom.onclick = function () {
|
||||
console.log('checkbox')
|
||||
instance.command.executeInsertElementList([{
|
||||
type: ElementType.CHECKBOX,
|
||||
value: ''
|
||||
}])
|
||||
}
|
||||
|
||||
// 5. | 搜索&替换 | 打印 |
|
||||
const searchCollapseDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')!
|
||||
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')!
|
||||
@@ -679,7 +688,8 @@ window.onload = function () {
|
||||
'separator',
|
||||
'codeblock',
|
||||
'page-break',
|
||||
'control'
|
||||
'control',
|
||||
'checkbox'
|
||||
]
|
||||
// 菜单操作权限
|
||||
disableMenusInControlContext.forEach(menu => {
|
||||
|
||||
+20
@@ -246,6 +246,26 @@ elementList.push({
|
||||
}]
|
||||
})
|
||||
|
||||
// 模拟checkbox
|
||||
elementList.push(...<IElement[]>[{
|
||||
value: '是否同意以上内容:'
|
||||
}, {
|
||||
type: ElementType.CHECKBOX,
|
||||
checkbox: {
|
||||
value: true
|
||||
},
|
||||
value: ''
|
||||
}, {
|
||||
value: '同意'
|
||||
}, {
|
||||
type: ElementType.CHECKBOX,
|
||||
value: ''
|
||||
}, {
|
||||
value: '否定'
|
||||
}, {
|
||||
value: '\n'
|
||||
}])
|
||||
|
||||
// 模拟结尾文本
|
||||
elementList.push(...[{
|
||||
value: 'E',
|
||||
|
||||
@@ -439,6 +439,10 @@ ul {
|
||||
background-image: url('./assets/images/control.svg');
|
||||
}
|
||||
|
||||
.menu-item__checkbox i {
|
||||
background-image: url('./assets/images/checkbox.svg');
|
||||
}
|
||||
|
||||
.menu-item .menu-item__control .options {
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user