feat:add table component
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect x=".5" y=".5" width="12" height="12" rx="1" transform="translate(1 1)" stroke="#3D4757"/><path fill="#3D4757" fill-rule="nonzero" d="M2 9h12v1H2zm0-4h12v1H2z"/><path fill="#3D4757" fill-rule="nonzero" d="M5 1h1v13H5zm4 0h1v13H9z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -21,6 +21,7 @@ export class Command {
|
||||
private static center: Function
|
||||
private static right: Function
|
||||
private static rowMargin: Function
|
||||
private static insertTable: Function
|
||||
private static image: Function
|
||||
private static search: Function
|
||||
private static print: Function
|
||||
@@ -46,6 +47,7 @@ export class Command {
|
||||
Command.center = adapt.rowFlex.bind(adapt)
|
||||
Command.right = adapt.rowFlex.bind(adapt)
|
||||
Command.rowMargin = adapt.rowMargin.bind(adapt)
|
||||
Command.insertTable = adapt.insertTable.bind(adapt)
|
||||
Command.image = adapt.image.bind(adapt)
|
||||
Command.search = adapt.search.bind(adapt)
|
||||
Command.print = adapt.print.bind(adapt)
|
||||
@@ -124,7 +126,11 @@ export class Command {
|
||||
return Command.rowMargin(payload)
|
||||
}
|
||||
|
||||
// 图片上传、搜索、打印
|
||||
// 表格、图片上传、搜索、打印
|
||||
public executeInsertTable(row: number, col: number) {
|
||||
return Command.insertTable(row, col)
|
||||
}
|
||||
|
||||
public executeImage(payload: IDrawImagePayload) {
|
||||
return Command.image(payload)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@ import { RowFlex } from "../../dataset/enum/Row"
|
||||
import { IDrawImagePayload } from "../../interface/Draw"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementStyle } from "../../interface/Element"
|
||||
import { IColgroup } from "../../interface/table/Colgroup"
|
||||
import { ITd } from "../../interface/table/Td"
|
||||
import { ITr } from "../../interface/table/Tr"
|
||||
import { getUUID } from "../../utils"
|
||||
import { formatElementList } from "../../utils/element"
|
||||
import { printImageBase64 } from "../../utils/print"
|
||||
import { Draw } from "../draw/Draw"
|
||||
import { HistoryManager } from "../history/HistoryManager"
|
||||
@@ -214,6 +218,55 @@ export class CommandAdapt {
|
||||
this.draw.render({ curIndex, isSetCursor })
|
||||
}
|
||||
|
||||
public insertTable(row: number, col: number) {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex === 0 && endIndex === 0) return
|
||||
const elementList = this.draw.getElementList()
|
||||
const { width, margins } = this.options
|
||||
const innerWidth = width - margins[1] - margins[3]
|
||||
// colgroup
|
||||
const colgroup: IColgroup[] = []
|
||||
const colWidth = innerWidth / col
|
||||
for (let c = 0; c < col; c++) {
|
||||
colgroup.push({
|
||||
width: colWidth
|
||||
})
|
||||
}
|
||||
// trlist
|
||||
const trList: ITr[] = []
|
||||
for (let r = 0; r < row; r++) {
|
||||
const tdList: ITd[] = []
|
||||
const tr: ITr = {
|
||||
height: 40,
|
||||
tdList
|
||||
}
|
||||
for (let c = 0; c < col; c++) {
|
||||
tdList.push({
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [{ value: ZERO, size: 16 }]
|
||||
})
|
||||
}
|
||||
trList.push(tr)
|
||||
}
|
||||
const element: IElement = {
|
||||
type: ElementType.TABLE,
|
||||
value: ZERO,
|
||||
colgroup,
|
||||
trList
|
||||
}
|
||||
// 格式化element
|
||||
formatElementList([element])
|
||||
const curIndex = startIndex + 1
|
||||
if (startIndex === endIndex) {
|
||||
elementList.splice(curIndex, 0, element)
|
||||
} else {
|
||||
elementList.splice(curIndex, endIndex - startIndex, element)
|
||||
}
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex, isSetCursor: false })
|
||||
}
|
||||
|
||||
public image(payload: IDrawImagePayload) {
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex === 0 && endIndex === 0) return
|
||||
|
||||
+160
-37
@@ -1,6 +1,6 @@
|
||||
import { ZERO } from "../../dataset/constant/Common"
|
||||
import { RowFlex } from "../../dataset/enum/Row"
|
||||
import { IDrawOption } from "../../interface/Draw"
|
||||
import { IDrawOption, IDrawRowPayload, IDrawRowResult } from "../../interface/Draw"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementMetrics, IElementPosition, IElementStyle } from "../../interface/Element"
|
||||
import { IRow, IRowElement } from "../../interface/Row"
|
||||
@@ -23,6 +23,7 @@ import { ImageParticle } from "./particle/ImageParticle"
|
||||
import { TextParticle } from "./particle/TextParticle"
|
||||
import { PageNumber } from "./frame/PageNumber"
|
||||
import { GlobalObserver } from "../observer/GlobalObserver"
|
||||
import { TableParticle } from "./particle/table/TableParticle"
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -47,6 +48,7 @@ export class Draw {
|
||||
private historyManager: HistoryManager
|
||||
private imageParticle: ImageParticle
|
||||
private textParticle: TextParticle
|
||||
private tableParticle: TableParticle
|
||||
private pageNumber: PageNumber
|
||||
|
||||
private rowList: IRow[]
|
||||
@@ -83,6 +85,7 @@ export class Draw {
|
||||
this.highlight = new Highlight(this)
|
||||
this.imageParticle = new ImageParticle(this)
|
||||
this.textParticle = new TextParticle(this)
|
||||
this.tableParticle = new TableParticle(this)
|
||||
this.pageNumber = new PageNumber(this)
|
||||
new GlobalObserver(this)
|
||||
|
||||
@@ -135,6 +138,10 @@ export class Draw {
|
||||
return this.options.defaultBasicRowMarginHeight * this.options.scale
|
||||
}
|
||||
|
||||
public getTdPadding(): number {
|
||||
return this.options.tdPadding * this.options.scale
|
||||
}
|
||||
|
||||
public getContainer(): HTMLDivElement {
|
||||
return this.container
|
||||
}
|
||||
@@ -202,6 +209,15 @@ export class Draw {
|
||||
}
|
||||
|
||||
public getElementList(): IElement[] {
|
||||
const positionContext = this.position.getPositionContext()
|
||||
if (positionContext.isTable) {
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
return this.elementList[index!].trList![trIndex!].tdList[tdIndex!].value
|
||||
}
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
public getOriginalElementList() {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
@@ -306,25 +322,25 @@ export class Draw {
|
||||
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${(el.size || defaultSize) * scale}px ${el.font || defaultFont}`
|
||||
}
|
||||
|
||||
private _computeRowList() {
|
||||
const { defaultSize, defaultRowMargin, scale } = this.options
|
||||
const innerWidth = this.getInnerWidth()
|
||||
private _computeRowList(innerWidth: number, elementList: IElement[]) {
|
||||
const { defaultSize, defaultRowMargin, scale, tdPadding } = this.options
|
||||
const defaultBasicRowMarginHeight = this.getDefaultBasicRowMarginHeight()
|
||||
const tdGap = tdPadding * 2
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||
const rowList: IRow[] = []
|
||||
if (this.elementList.length) {
|
||||
if (elementList.length) {
|
||||
rowList.push({
|
||||
width: 0,
|
||||
height: 0,
|
||||
ascent: 0,
|
||||
elementList: [],
|
||||
rowFlex: this.elementList?.[1]?.rowFlex
|
||||
rowFlex: elementList?.[1]?.rowFlex
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < this.elementList.length; i++) {
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const curRow: IRow = rowList[rowList.length - 1]
|
||||
const element = this.elementList[i]
|
||||
const element = elementList[i]
|
||||
const rowMargin = defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin)
|
||||
let metrics: IElementMetrics = {
|
||||
width: 0,
|
||||
@@ -350,6 +366,40 @@ export class Draw {
|
||||
metrics.boundingBoxDescent = elementHeight
|
||||
}
|
||||
metrics.boundingBoxAscent = 0
|
||||
} else if (element.type === ElementType.TABLE) {
|
||||
// 计算表格行列
|
||||
this.tableParticle.computeRowColInfo(element)
|
||||
// 计算表格内元素信息
|
||||
const trList = element.trList!
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
let maxTrHeight = 0
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const rowList = this._computeRowList((td.width! - tdGap) * scale, td.value)
|
||||
const rowHeight = rowList.reduce((pre, cur) => pre + cur.height, 0)
|
||||
td.rowList = rowList
|
||||
// 移除缩放导致的行高变化-渲染时会进行缩放调整
|
||||
const curTrHeight = (rowHeight + tdGap) / scale
|
||||
if (maxTrHeight < curTrHeight) {
|
||||
maxTrHeight = curTrHeight
|
||||
}
|
||||
}
|
||||
tr.height = maxTrHeight
|
||||
}
|
||||
// 需要重新计算表格内值
|
||||
this.tableParticle.computeRowColInfo(element)
|
||||
// 计算出表格高度
|
||||
const tableHeight = trList.reduce((pre, cur) => pre + cur.height, 0)
|
||||
const tableWidth = element.colgroup!.reduce((pre, cur) => pre + cur.width, 0)
|
||||
element.width = tableWidth
|
||||
element.height = tableHeight
|
||||
const elementWidth = tableWidth * scale
|
||||
const elementHeight = tableHeight * scale
|
||||
metrics.width = elementWidth
|
||||
metrics.height = elementHeight
|
||||
metrics.boundingBoxDescent = elementHeight
|
||||
metrics.boundingBoxAscent = 0
|
||||
} else {
|
||||
metrics.height = (element.size || this.options.defaultSize) * scale
|
||||
ctx.font = this._getFont(element)
|
||||
@@ -367,7 +417,12 @@ export class Draw {
|
||||
style: this._getFont(element, scale)
|
||||
}
|
||||
// 超过限定宽度
|
||||
if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) {
|
||||
const preElement = elementList[i - 1]
|
||||
if (
|
||||
(preElement && preElement.type === ElementType.TABLE)
|
||||
|| curRow.width + metrics.width > innerWidth
|
||||
|| (i !== 0 && element.value === ZERO)
|
||||
) {
|
||||
rowList.push({
|
||||
width: metrics.width,
|
||||
height,
|
||||
@@ -388,35 +443,29 @@ export class Draw {
|
||||
curRow.elementList.push(rowElement)
|
||||
}
|
||||
}
|
||||
this.rowList = rowList
|
||||
return rowList
|
||||
}
|
||||
|
||||
private _drawElement(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||
const width = this.getWidth()
|
||||
const height = this.getHeight()
|
||||
const margins = this.getMargins()
|
||||
const ctx = this.ctxList[pageNo]
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
// 绘制背景
|
||||
this.background.render(ctx)
|
||||
// 绘制页边距
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
this.margin.render(ctx)
|
||||
// 渲染元素
|
||||
let x = leftTopPoint[0]
|
||||
let y = leftTopPoint[1]
|
||||
let index = positionList.length
|
||||
private _drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload): IDrawRowResult {
|
||||
const { positionList, rowList, pageNo, startX, startY, startIndex, innerWidth } = payload
|
||||
const { scale, tdPadding } = this.options
|
||||
const tdGap = tdPadding * 2
|
||||
let x = startX
|
||||
let y = startY
|
||||
let index = startIndex
|
||||
for (let i = 0; i < rowList.length; i++) {
|
||||
const curRow = rowList[i]
|
||||
// 计算行偏移量(行居左、居中、居右)
|
||||
if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) {
|
||||
const canvasInnerWidth = width - margins[1] - margins[3]
|
||||
if (curRow.rowFlex === RowFlex.CENTER) {
|
||||
x += (canvasInnerWidth - curRow.width) / 2
|
||||
x += (innerWidth - curRow.width) / 2
|
||||
} else {
|
||||
x += canvasInnerWidth - curRow.width
|
||||
x += innerWidth - curRow.width
|
||||
}
|
||||
}
|
||||
// 当前td所在位置
|
||||
let tablePreX = x
|
||||
let tablePreY = y
|
||||
for (let j = 0; j < curRow.elementList.length; j++) {
|
||||
const element = curRow.elementList[j]
|
||||
const metrics = element.metrics
|
||||
@@ -456,25 +505,89 @@ export class Draw {
|
||||
if (element.type === ElementType.IMAGE) {
|
||||
this.textParticle.complete()
|
||||
this.imageParticle.render(ctx, element, x, y + offsetY)
|
||||
} else if (element.type === ElementType.TABLE) {
|
||||
this.tableParticle.render(ctx, element, x, y)
|
||||
} else {
|
||||
this.textParticle.record(ctx, element, x, y + offsetY)
|
||||
}
|
||||
// 选区绘制
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex !== endIndex && startIndex < index && index <= endIndex) {
|
||||
let rangeWidth = metrics.width
|
||||
if (rangeWidth === 0 && curRow.elementList.length === 1) {
|
||||
rangeWidth = this.options.rangeMinWidth
|
||||
const positionContext = this.position.getPositionContext()
|
||||
// 表格需限定上下文
|
||||
if (
|
||||
(!positionContext.isTable && !element.tdId)
|
||||
|| positionContext.tdId === element.tdId
|
||||
) {
|
||||
let rangeWidth = metrics.width
|
||||
if (rangeWidth === 0 && curRow.elementList.length === 1) {
|
||||
rangeWidth = this.options.rangeMinWidth
|
||||
}
|
||||
this.range.render(ctx, x, y, rangeWidth, curRow.height)
|
||||
}
|
||||
this.range.render(ctx, x, y, rangeWidth, curRow.height)
|
||||
}
|
||||
index++
|
||||
x += metrics.width
|
||||
// 绘制表格内元素
|
||||
if (element.type === ElementType.TABLE) {
|
||||
for (let t = 0; t < element.trList!.length; t++) {
|
||||
const tr = element.trList![t]
|
||||
for (let d = 0; d < tr.tdList!.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
td.positionList = []
|
||||
const drawRowResult = this._drawRow(ctx, {
|
||||
positionList: td.positionList,
|
||||
rowList: td.rowList!,
|
||||
pageNo,
|
||||
startIndex: 0,
|
||||
startX: (td.x! + tdPadding) * scale + tablePreX,
|
||||
startY: td.y! * scale + tablePreY,
|
||||
innerWidth: (td.width! - tdGap) * scale
|
||||
})
|
||||
x = drawRowResult.x
|
||||
y = drawRowResult.y
|
||||
}
|
||||
}
|
||||
// 恢复初始x、y
|
||||
x = tablePreX
|
||||
y = tablePreY
|
||||
}
|
||||
}
|
||||
this.textParticle.complete()
|
||||
x = leftTopPoint[0]
|
||||
x = startX
|
||||
y += curRow.height
|
||||
}
|
||||
return { x, y, index }
|
||||
}
|
||||
|
||||
private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||
const width = this.getWidth()
|
||||
const height = this.getHeight()
|
||||
const margins = this.getMargins()
|
||||
const innerWidth = this.getInnerWidth()
|
||||
const ctx = this.ctxList[pageNo]
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
// 绘制背景
|
||||
this.background.render(ctx)
|
||||
// 绘制页边距
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
this.margin.render(ctx)
|
||||
// 渲染元素
|
||||
let x = leftTopPoint[0]
|
||||
let y = leftTopPoint[1]
|
||||
let index = positionList.length
|
||||
const drawRowResult = this._drawRow(ctx, {
|
||||
positionList,
|
||||
rowList,
|
||||
pageNo,
|
||||
startIndex: index,
|
||||
startX: x,
|
||||
startY: y,
|
||||
innerWidth
|
||||
})
|
||||
x = drawRowResult.x
|
||||
y = drawRowResult.y
|
||||
index = drawRowResult.index
|
||||
// 绘制页码
|
||||
this.pageNumber.render(ctx, pageNo)
|
||||
// 搜索匹配绘制
|
||||
@@ -491,14 +604,15 @@ export class Draw {
|
||||
isComputeRowList = true
|
||||
} = payload || {}
|
||||
const height = this.getHeight()
|
||||
const innerWidth = this.getInnerWidth()
|
||||
// 计算行信息
|
||||
if (isComputeRowList) {
|
||||
this._computeRowList()
|
||||
this.rowList = this._computeRowList(innerWidth, this.elementList)
|
||||
}
|
||||
// 清除光标等副作用
|
||||
this.cursor.recoveryCursor()
|
||||
this.position.setPositionList([])
|
||||
const positionList = this.position.getPositionList()
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
// 按页渲染
|
||||
const margins = this.getMargins()
|
||||
const marginHeight = margins[0] + margins[2]
|
||||
@@ -522,7 +636,7 @@ export class Draw {
|
||||
this._createPage(i)
|
||||
}
|
||||
const rowList = pageRowList[i]
|
||||
this._drawElement(positionList, rowList, i)
|
||||
this._drawPage(positionList, rowList, i)
|
||||
}
|
||||
// 移除多余页
|
||||
setTimeout(() => {
|
||||
@@ -540,7 +654,14 @@ export class Draw {
|
||||
if (curIndex === undefined) {
|
||||
curIndex = positionList.length - 1
|
||||
}
|
||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||
const positionContext = this.position.getPositionContext()
|
||||
if (positionContext.isTable) {
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
const tablePosition = this.elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList?.[curIndex!]
|
||||
this.position.setCursorPosition(tablePosition || null)
|
||||
} else {
|
||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||
}
|
||||
this.cursor.drawCursor()
|
||||
}
|
||||
// 历史记录用于undo、redo
|
||||
@@ -549,8 +670,10 @@ export class Draw {
|
||||
const oldElementList = deepClone(this.elementList)
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const pageNo = this.pageNo
|
||||
const oldPositionContext = deepClone(this.position.getPositionContext())
|
||||
this.historyManager.execute(function () {
|
||||
self.setPageNo(pageNo)
|
||||
self.position.setPositionContext(oldPositionContext)
|
||||
self.range.setRange(startIndex, endIndex)
|
||||
self.elementList = deepClone(oldElementList)
|
||||
self.render({ curIndex, isSubmitHistory: false })
|
||||
|
||||
@@ -18,7 +18,7 @@ export class Search {
|
||||
const searchMatch = this.draw.getSearchMatch()
|
||||
if (!searchMatch || !searchMatch.length) return
|
||||
const searchMatchList = searchMatch.flat()
|
||||
const positionList = this.position.getPositionList()
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
ctx.save()
|
||||
ctx.globalAlpha = this.options.searchMatchAlpha
|
||||
ctx.fillStyle = this.options.searchMatchColor
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { IElement } from "../../../.."
|
||||
import { IEditorOption } from "../../../../interface/Editor"
|
||||
import { Draw } from "../../Draw"
|
||||
|
||||
export class TableParticle {
|
||||
|
||||
private options: Required<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
private _drawBorder(ctx: CanvasRenderingContext2D, startX: number, startY: number, width: number, height: number) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(startX, startY)
|
||||
ctx.lineTo(startX + width, startY)
|
||||
ctx.lineTo(startX + width, startY + height)
|
||||
ctx.lineTo(startX, startY + height)
|
||||
ctx.closePath()
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _drawRange(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {
|
||||
const { rangeAlpha, rangeColor } = this.options
|
||||
ctx.save()
|
||||
ctx.globalAlpha = rangeAlpha
|
||||
ctx.fillStyle = rangeColor
|
||||
ctx.fillRect(x, y, width, height)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
public computeRowColInfo(element: IElement) {
|
||||
const { colgroup, trList } = element
|
||||
if (!colgroup || !trList) return
|
||||
let x = 0
|
||||
let y = 0
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
// 表格最后一行
|
||||
const isLastTr = trList.length - 1 === t
|
||||
// 当前行最小高度
|
||||
let rowMinHeight = 0
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
// 计算之前行x轴偏移量
|
||||
let offsetXIndex = 0
|
||||
if (trList.length > 1 && t !== 0) {
|
||||
for (let pT = 0; pT < t; pT++) {
|
||||
const pTr = trList[pT]
|
||||
// 相同x轴是否存在跨行
|
||||
for (let pD = 0; pD < pTr.tdList.length; pD++) {
|
||||
const pTd = pTr.tdList[pD]
|
||||
const pTdX = pTd.x!
|
||||
const pTdY = pTd.y!
|
||||
const pTdWidth = pTd.width!
|
||||
const pTdHeight = pTd.height!
|
||||
// 小于
|
||||
if (pTdX < x) continue
|
||||
if (pTdX > x) break
|
||||
if (pTd.x === x && pTdY + pTdHeight > y) {
|
||||
x += pTdWidth
|
||||
offsetXIndex += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 计算格列数
|
||||
let colIndex = 0
|
||||
const preTd = tr.tdList[d - 1]
|
||||
if (preTd) {
|
||||
colIndex = preTd.colIndex! + (offsetXIndex || 1)
|
||||
if (preTd.colspan > 1) {
|
||||
colIndex += preTd.colspan - 1
|
||||
}
|
||||
} else {
|
||||
colIndex += offsetXIndex
|
||||
}
|
||||
// 计算格宽高
|
||||
let width = 0
|
||||
for (let col = 0; col < td.colspan; col++) {
|
||||
width += colgroup[col + colIndex].width
|
||||
}
|
||||
let height = 0
|
||||
for (let row = 0; row < td.rowspan; row++) {
|
||||
height += trList[row + t].height
|
||||
}
|
||||
// y偏移量
|
||||
if (rowMinHeight === 0 || rowMinHeight > height) {
|
||||
rowMinHeight = height
|
||||
}
|
||||
// 当前行最后一个td
|
||||
const isLastRowTd = tr.tdList.length - 1 === d
|
||||
// 当前列最后一个td
|
||||
let isLastColTd = isLastTr
|
||||
if (!isLastColTd) {
|
||||
if (td.rowspan > 1) {
|
||||
const nextTrLength = trList.length - 1 - t
|
||||
isLastColTd = td.rowspan - 1 === nextTrLength
|
||||
}
|
||||
}
|
||||
// 当前表格最后一个td
|
||||
const isLastTd = isLastTr && isLastRowTd
|
||||
td.isLastRowTd = isLastRowTd
|
||||
td.isLastColTd = isLastColTd
|
||||
td.isLastTd = isLastTd
|
||||
// 修改当前格clientBox
|
||||
td.x = x
|
||||
td.y = y
|
||||
td.width = width
|
||||
td.height = height
|
||||
td.rowIndex = t
|
||||
td.colIndex = colIndex
|
||||
// 当前列x轴累加
|
||||
x += width
|
||||
// 一行中的最后td
|
||||
if (isLastRowTd && !isLastTd) {
|
||||
x = 0
|
||||
y += rowMinHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, element: IElement, startX: number, startY: number) {
|
||||
const { colgroup, trList } = element
|
||||
if (!colgroup || !trList) return
|
||||
const { scale } = this.options
|
||||
const tableWidth = element.width! * scale
|
||||
const tableHeight = element.height! * scale
|
||||
ctx.save()
|
||||
// 渲染边框
|
||||
this._drawBorder(ctx, startX, startY, tableWidth, tableHeight)
|
||||
// 渲染表格
|
||||
for (let t = 0; t < trList.length; t++) {
|
||||
const tr = trList[t]
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const { isLastRowTd, isLastColTd, isLastTd } = td
|
||||
const width = td.width! * scale
|
||||
const height = td.height! * scale
|
||||
const x = td.x! * scale + startX + width
|
||||
const y = td.y! * scale + startY
|
||||
// 绘制线条
|
||||
ctx.beginPath()
|
||||
if (isLastRowTd && !isLastTd) {
|
||||
// 是否跨行到底
|
||||
if (y + height < startY + tableHeight) {
|
||||
ctx.moveTo(x, y + height)
|
||||
ctx.lineTo(x - width, y + height)
|
||||
}
|
||||
} else if (isLastColTd && !isLastTd) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
} else if (!isLastRowTd && !isLastColTd && !isLastTd) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
ctx.lineTo(x - width, y + height)
|
||||
} else if (isLastTd) {
|
||||
// 是否跨列到最右
|
||||
if (x + width === startX + tableWidth) {
|
||||
ctx.moveTo(x, y)
|
||||
ctx.lineTo(x, y + height)
|
||||
}
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,7 +80,12 @@ export class CanvasEvent {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
// 结束位置
|
||||
const { index: endIndex } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||
const positionResult = this.position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const { index, isTable, tdValueIndex } = positionResult
|
||||
let endIndex = isTable ? tdValueIndex! : index
|
||||
let end = ~endIndex ? endIndex : 0
|
||||
// 开始位置
|
||||
let start = this.mouseDownStartIndex
|
||||
@@ -105,15 +110,46 @@ export class CanvasEvent {
|
||||
this.draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
this.isAllowDrag = true
|
||||
const { index, isDirectHit, isImage } = this.position.getPositionByXY(evt.offsetX, evt.offsetY)
|
||||
const positionResult = this.position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
isImage,
|
||||
isTable,
|
||||
trIndex,
|
||||
tdIndex,
|
||||
tdValueIndex,
|
||||
tdId,
|
||||
trId,
|
||||
tableId
|
||||
} = positionResult
|
||||
// 设置位置上下文
|
||||
this.position.setPositionContext({
|
||||
isTable: isTable || false,
|
||||
index,
|
||||
trIndex,
|
||||
tdIndex,
|
||||
tdId,
|
||||
trId,
|
||||
tableId
|
||||
})
|
||||
// 记录选区开始位置
|
||||
this.mouseDownStartIndex = index
|
||||
this.mouseDownStartIndex = isTable ? tdValueIndex! : index
|
||||
// 绘制
|
||||
const isDirectHitImage = isDirectHit && isImage
|
||||
if (~index) {
|
||||
this.range.setRange(index, index)
|
||||
let curIndex = index
|
||||
if (isTable) {
|
||||
this.range.setRange(tdValueIndex!, tdValueIndex!)
|
||||
curIndex = tdValueIndex!
|
||||
} else {
|
||||
this.range.setRange(index, index)
|
||||
}
|
||||
this.draw.render({
|
||||
curIndex: index,
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: !isDirectHitImage,
|
||||
isComputeRowList: false
|
||||
@@ -124,7 +160,8 @@ export class CanvasEvent {
|
||||
if (isDirectHitImage) {
|
||||
const elementList = this.draw.getElementList()
|
||||
const positionList = this.position.getPositionList()
|
||||
this.imageParticle.drawResizer(elementList[index], positionList[index])
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
this.imageParticle.drawResizer(elementList[curIndex], positionList[curIndex])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +204,16 @@ export class CanvasEvent {
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex })
|
||||
} else if (evt.key === KeyMap.Enter) {
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const enterText: IElement = {
|
||||
value: ZERO
|
||||
value: ZERO,
|
||||
...restArg
|
||||
}
|
||||
if (isCollspace) {
|
||||
elementList.splice(index + 1, 0, enterText)
|
||||
@@ -277,8 +322,16 @@ export class CanvasEvent {
|
||||
const { index } = cursorPosition
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const isCollspace = startIndex === endIndex
|
||||
// 表格需要上下文信息
|
||||
const positionContext = this.position.getPositionContext()
|
||||
let restArg = {}
|
||||
if (positionContext.isTable) {
|
||||
const { tdId, trId, tableId } = positionContext
|
||||
restArg = { tdId, trId, tableId }
|
||||
}
|
||||
const inputData: IElement[] = data.split('').map(value => ({
|
||||
value
|
||||
value,
|
||||
...restArg
|
||||
}))
|
||||
let start = 0
|
||||
if (isCollspace) {
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
import { ElementType } from "../.."
|
||||
import { ZERO } from "../../dataset/constant/Common"
|
||||
import { IEditorOption } from "../../interface/Editor"
|
||||
import { IElement, IElementPosition } from "../../interface/Element"
|
||||
import { ICurrentPosition } from "../../interface/Position"
|
||||
import { IElementPosition } from "../../interface/Element"
|
||||
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from "../../interface/Position"
|
||||
import { Draw } from "../draw/Draw"
|
||||
|
||||
export class Position {
|
||||
|
||||
private cursorPosition: IElementPosition | null
|
||||
private positionContext: IPositionContext
|
||||
private positionList: IElementPosition[]
|
||||
private elementList: IElement[]
|
||||
|
||||
private draw: Draw
|
||||
private options: Required<IEditorOption>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.positionList = []
|
||||
this.elementList = []
|
||||
this.cursorPosition = null
|
||||
this.positionContext = {
|
||||
isTable: false
|
||||
}
|
||||
|
||||
this.draw = draw
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
public getOriginalPositionList(): IElementPosition[] {
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
public getPositionList(): IElementPosition[] {
|
||||
const { isTable } = this.positionContext
|
||||
if (isTable) {
|
||||
const { index, trIndex, tdIndex } = this.positionContext
|
||||
const elementList = this.draw.getOriginalElementList()
|
||||
return elementList[index!].trList![trIndex!].tdList[tdIndex!].positionList || []
|
||||
}
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
@@ -31,7 +43,7 @@ export class Position {
|
||||
this.positionList = payload
|
||||
}
|
||||
|
||||
public setCursorPosition(position: IElementPosition) {
|
||||
public setCursorPosition(position: IElementPosition | null) {
|
||||
this.cursorPosition = position
|
||||
}
|
||||
|
||||
@@ -39,22 +51,69 @@ export class Position {
|
||||
return this.cursorPosition
|
||||
}
|
||||
|
||||
public getPositionByXY(x: number, y: number): ICurrentPosition {
|
||||
this.elementList = this.draw.getElementList()
|
||||
public getPositionContext(): IPositionContext {
|
||||
return this.positionContext
|
||||
}
|
||||
|
||||
public setPositionContext(payload: IPositionContext) {
|
||||
this.positionContext = payload
|
||||
}
|
||||
|
||||
public getPositionByXY(payload: IGetPositionByXYPayload): ICurrentPosition {
|
||||
const { x, y, isTable } = payload
|
||||
let { elementList, positionList } = payload
|
||||
if (!elementList) {
|
||||
elementList = this.draw.getOriginalElementList()
|
||||
}
|
||||
if (!positionList) {
|
||||
positionList = this.positionList
|
||||
}
|
||||
const curPageNo = this.draw.getPageNo()
|
||||
for (let j = 0; j < this.positionList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = this.positionList[j]
|
||||
for (let j = 0; j < positionList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
// 命中元素
|
||||
if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) {
|
||||
let curPostionIndex = j
|
||||
const element = this.elementList[j]
|
||||
const element = elementList[j]
|
||||
// 表格被命中
|
||||
if (element.type === ElementType.TABLE) {
|
||||
for (let t = 0; t < element.trList!.length; t++) {
|
||||
const tr = element.trList![t]
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const tablePosition = this.getPositionByXY({
|
||||
x,
|
||||
y,
|
||||
td,
|
||||
tablePosition: positionList[j],
|
||||
isTable: true,
|
||||
elementList: td.value,
|
||||
positionList: td.positionList
|
||||
})
|
||||
if (~tablePosition.index) {
|
||||
return {
|
||||
index,
|
||||
isImage: tablePosition.isImage,
|
||||
isDirectHit: tablePosition.isDirectHit,
|
||||
isTable: true,
|
||||
tdIndex: d,
|
||||
trIndex: t,
|
||||
tdValueIndex: tablePosition.index,
|
||||
tdId: td.id,
|
||||
trId: tr.id,
|
||||
tableId: element.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片区域均为命中
|
||||
if (element.type === ElementType.IMAGE) {
|
||||
return { index: curPostionIndex, isDirectHit: true, isImage: true }
|
||||
}
|
||||
// 判断是否在文字中间前后
|
||||
if (this.elementList[index].value !== ZERO) {
|
||||
if (elementList[index].value !== ZERO) {
|
||||
const valueWidth = rightTop[0] - leftTop[0]
|
||||
if (x < leftTop[0] + valueWidth / 2) {
|
||||
curPostionIndex = j - 1
|
||||
@@ -66,8 +125,22 @@ export class Position {
|
||||
// 非命中区域
|
||||
let isLastArea = false
|
||||
let curPostionIndex = -1
|
||||
// 判断是否在表格内
|
||||
if (isTable) {
|
||||
const { td, tablePosition } = payload
|
||||
if (td && tablePosition) {
|
||||
const { leftTop } = tablePosition.coordinate
|
||||
const tdX = td.x! + leftTop[0]
|
||||
const tdY = td.y! + leftTop[1]
|
||||
const tdWidth = td.width!
|
||||
const tdHeight = td.height!
|
||||
if (!(tdX < x && x < tdX + tdWidth && tdY < y && y < tdY + tdHeight)) {
|
||||
return { index: curPostionIndex }
|
||||
}
|
||||
}
|
||||
}
|
||||
// 判断所属行是否存在元素
|
||||
const firstLetterList = this.positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||
const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||
for (let j = 0; j < firstLetterList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
@@ -75,7 +148,7 @@ export class Position {
|
||||
const isHead = x < this.options.margins[3]
|
||||
// 是否在头部
|
||||
if (isHead) {
|
||||
const headIndex = this.positionList.findIndex(p => p.rowNo === firstLetterList[j].rowNo)
|
||||
const headIndex = positionList.findIndex(p => p.rowNo === firstLetterList[j].rowNo)
|
||||
curPostionIndex = ~headIndex ? headIndex : index
|
||||
} else {
|
||||
curPostionIndex = index
|
||||
@@ -86,7 +159,7 @@ export class Position {
|
||||
}
|
||||
if (!isLastArea) {
|
||||
// 当前页最后一行
|
||||
return { index: firstLetterList[firstLetterList.length - 1]?.index || this.positionList.length - 1 }
|
||||
return { index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1 }
|
||||
}
|
||||
return { index: curPostionIndex }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum ElementType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image'
|
||||
IMAGE = 'image',
|
||||
TABLE = 'table'
|
||||
}
|
||||
+4
-16
@@ -1,5 +1,4 @@
|
||||
import './assets/css/index.css'
|
||||
import { ZERO } from './dataset/constant/Common'
|
||||
import { IEditorOption } from './interface/Editor'
|
||||
import { IElement } from './interface/Element'
|
||||
import { Draw } from './core/draw/Draw'
|
||||
@@ -7,8 +6,8 @@ import { Command } from './core/command/Command'
|
||||
import { CommandAdapt } from './core/command/CommandAdapt'
|
||||
import { Listener } from './core/listener/Listener'
|
||||
import { RowFlex } from './dataset/enum/Row'
|
||||
import { getUUID } from './utils'
|
||||
import { ElementType } from './dataset/enum/Element'
|
||||
import { formatElementList } from './utils/element'
|
||||
|
||||
export default class Editor {
|
||||
|
||||
@@ -42,22 +41,11 @@ export default class Editor {
|
||||
marginIndicatorSize: 35,
|
||||
marginIndicatorColor: '#BABABA',
|
||||
margins: [100, 120, 100, 120],
|
||||
tdPadding: 5,
|
||||
defaultTdHeight: 40,
|
||||
...options
|
||||
}
|
||||
if (elementList[0]?.value !== ZERO) {
|
||||
elementList.unshift({
|
||||
value: ZERO
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const el = elementList[i]
|
||||
if (el.value === '\n') {
|
||||
el.value = ZERO
|
||||
}
|
||||
if (el.type === ElementType.IMAGE) {
|
||||
el.id = getUUID()
|
||||
}
|
||||
}
|
||||
formatElementList(elementList)
|
||||
// 监听
|
||||
this.listener = new Listener()
|
||||
// 启动
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { IElementPosition } from "./Element"
|
||||
import { IRow } from "./Row"
|
||||
|
||||
export interface IDrawOption {
|
||||
curIndex?: number;
|
||||
isSetCursor?: boolean;
|
||||
@@ -16,4 +19,20 @@ export interface IImageParticleCreateResult {
|
||||
resizerHandleList: HTMLDivElement[];
|
||||
resizerImageContainer: HTMLDivElement;
|
||||
resizerImage: HTMLImageElement;
|
||||
}
|
||||
|
||||
export interface IDrawRowPayload {
|
||||
positionList: IElementPosition[];
|
||||
rowList: IRow[];
|
||||
pageNo: number;
|
||||
startIndex: number;
|
||||
startX: number;
|
||||
startY: number;
|
||||
innerWidth: number;
|
||||
}
|
||||
|
||||
export interface IDrawRowResult {
|
||||
x: number;
|
||||
y: number;
|
||||
index: number;
|
||||
}
|
||||
@@ -23,5 +23,7 @@ export interface IEditorOption {
|
||||
resizerSize?: number;
|
||||
marginIndicatorSize?: number;
|
||||
marginIndicatorColor?: string,
|
||||
margins?: [top: number, right: number, bootom: number, left: number]
|
||||
margins?: [top: number, right: number, bootom: number, left: number],
|
||||
tdPadding?: number;
|
||||
defaultTdHeight?: number;
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ElementType } from "../dataset/enum/Element"
|
||||
import { RowFlex } from "../dataset/enum/Row"
|
||||
import { IColgroup } from "./table/Colgroup"
|
||||
import { ITr } from "./table/Tr"
|
||||
|
||||
export interface IElementMetrics {
|
||||
width: number;
|
||||
height: number;
|
||||
boundingBoxAscent: number;
|
||||
boundingBoxDescent: number;
|
||||
export interface IElementBasic {
|
||||
id?: string;
|
||||
type?: ElementType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IElementStyle {
|
||||
@@ -23,13 +24,27 @@ export interface IElementStyle {
|
||||
rowMargin?: number;
|
||||
}
|
||||
|
||||
export interface IElementBasic {
|
||||
id?: string;
|
||||
type?: ElementType;
|
||||
value: string;
|
||||
export interface ITableAttr {
|
||||
colgroup?: IColgroup[];
|
||||
trList?: ITr[];
|
||||
}
|
||||
|
||||
export type IElement = IElementBasic & IElementStyle
|
||||
export interface ITableElement {
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export type ITable = ITableAttr & ITableElement
|
||||
|
||||
export type IElement = IElementBasic & IElementStyle & ITable
|
||||
|
||||
export interface IElementMetrics {
|
||||
width: number;
|
||||
height: number;
|
||||
boundingBoxAscent: number;
|
||||
boundingBoxDescent: number;
|
||||
}
|
||||
|
||||
export interface IElementPosition {
|
||||
pageNo: number;
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
import { IElement } from ".."
|
||||
import { IElementPosition } from "./Element"
|
||||
import { ITd } from "./table/Td"
|
||||
|
||||
export interface ICurrentPosition {
|
||||
index: number;
|
||||
isImage?: boolean;
|
||||
isTable?: boolean;
|
||||
isDirectHit?: boolean;
|
||||
trIndex?: number;
|
||||
tdIndex?: number;
|
||||
tdValueIndex?: number;
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export interface IGetPositionByXYPayload {
|
||||
x: number;
|
||||
y: number;
|
||||
isTable?: boolean;
|
||||
td?: ITd;
|
||||
tablePosition?: IElementPosition;
|
||||
elementList?: IElement[];
|
||||
positionList?: IElementPosition[];
|
||||
}
|
||||
|
||||
export interface IPositionContext {
|
||||
isTable: boolean;
|
||||
index?: number;
|
||||
trIndex?: number;
|
||||
tdIndex?: number;
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IColgroup {
|
||||
id?: string;
|
||||
width: number;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IElement, IElementPosition } from "../Element"
|
||||
import { IRow } from "../Row"
|
||||
|
||||
export interface ITd {
|
||||
id?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
colspan: number;
|
||||
rowspan: number;
|
||||
value: IElement[];
|
||||
isLastRowTd?: boolean;
|
||||
isLastColTd?: boolean;
|
||||
isLastTd?: boolean;
|
||||
rowIndex?: number;
|
||||
colIndex?: number;
|
||||
rowList?: IRow[];
|
||||
positionList?: IElementPosition[];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ITd } from "./Td"
|
||||
|
||||
export interface ITr {
|
||||
id?: string;
|
||||
height: number;
|
||||
tdList: ITd[];
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { getUUID } from "."
|
||||
import { ElementType, IElement } from ".."
|
||||
import { ZERO } from "../dataset/constant/Common"
|
||||
|
||||
export function formatElementList(elementList: IElement[]) {
|
||||
if (elementList[0]?.value !== ZERO) {
|
||||
elementList.unshift({
|
||||
value: ZERO
|
||||
})
|
||||
}
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const el = elementList[i]
|
||||
if (el.type === ElementType.TABLE) {
|
||||
const tableId = getUUID()
|
||||
el.id = tableId
|
||||
if (el.trList) {
|
||||
for (let t = 0; t < el.trList.length; t++) {
|
||||
const tr = el.trList[t]
|
||||
const trId = getUUID()
|
||||
tr.id = trId
|
||||
for (let d = 0; d < tr.tdList.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
const tdId = getUUID()
|
||||
td.id = tdId
|
||||
formatElementList(td.value)
|
||||
for (let v = 0; v < td.value.length; v++) {
|
||||
const value = td.value[v]
|
||||
value.tdId = tdId
|
||||
value.trId = trId
|
||||
value.tableId = tableId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (el.value === '\n') {
|
||||
el.value = ZERO
|
||||
}
|
||||
if (el.type === ElementType.IMAGE) {
|
||||
el.id = getUUID()
|
||||
}
|
||||
}
|
||||
}
|
||||
+182
-5
@@ -3,7 +3,7 @@ import Editor, { ElementType, IElement, RowFlex } from './editor'
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
const text = `人民医院门诊病历\n主诉:\n发热三天,咳嗽五天。\n现病史:\n患者于三天前无明显诱因,感冒后发现面部水肿,无皮疹,尿量减少,出现乏力,在外治疗无好转,现来我院就诊。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。没有报告其他既往疾病。\n流行病史:\n否认14天内接触过新冠肺炎确诊患者、疑似患者、无症状感染者及其密切接触者;否认14天内去过以下场所:水产、肉类批发市场,农贸市场,集市,大型超市,夜市;否认14天内与以下场所工作人员密切接触:水产、肉类批发市场,农贸市场,集市,大型超市;否认14天内周围(如家庭、办公室)有2例以上聚集性发病;否认14天内接触过有发热或呼吸道症状的人员;否认14天内自身有发热或呼吸道症状;否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形;陪同家属{有无选择代码}有以上情况。\n体格检查:\nT:36.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10^9/L(偏高)参考值:0.1~0.6;\n门诊诊断:\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗:\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名:【】`
|
||||
const text = `人民医院门诊病历\n主诉:\n发热三天,咳嗽五天。\n现病史:\n患者于三天前无明显诱因,感冒后发现面部水肿,无皮疹,尿量减少,出现乏力,在外治疗无好转,现来我院就诊。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。没有报告其他既往疾病。\n流行病史:\n否认14天内接触过新冠肺炎确诊患者、疑似患者、无症状感染者及其密切接触者;否认14天内去过以下场所:水产、肉类批发市场,农贸市场,集市,大型超市,夜市;否认14天内与以下场所工作人员密切接触:水产、肉类批发市场,农贸市场,集市,大型超市;否认14天内周围(如家庭、办公室)有2例以上聚集性发病;否认14天内接触过有发热或呼吸道症状的人员;否认14天内自身有发热或呼吸道症状;否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形;陪同家属{有无选择代码}有以上情况。\n体格检查:\nT:36.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10^9/L(偏高)参考值:0.1~0.6;\n门诊诊断:\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗:\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名:【】\n其他记录:`
|
||||
// 模拟行居中
|
||||
const centerText = ['人民医院门诊病历']
|
||||
const centerIndex: number[] = centerText.map(c => {
|
||||
@@ -11,7 +11,7 @@ window.onload = function () {
|
||||
return ~i ? Array(c.length).fill(i).map((_, j) => i + j) : []
|
||||
}).flat()
|
||||
// 模拟加粗字
|
||||
const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:']
|
||||
const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:', '其他记录:']
|
||||
const boldIndex: number[] = boldText.map(b => {
|
||||
const i = text.indexOf(b)
|
||||
return ~i ? Array(b.length).fill(i).map((_, j) => i + j) : []
|
||||
@@ -69,12 +69,116 @@ window.onload = function () {
|
||||
id: 'signature',
|
||||
type: ElementType.IMAGE
|
||||
})
|
||||
data.push({
|
||||
type: ElementType.TABLE,
|
||||
value: `\n`,
|
||||
colgroup: [{
|
||||
width: 180
|
||||
}, {
|
||||
width: 80
|
||||
}, {
|
||||
width: 130
|
||||
}, {
|
||||
width: 130
|
||||
}],
|
||||
trList: [{
|
||||
height: 40,
|
||||
tdList: [{
|
||||
colspan: 1,
|
||||
rowspan: 2,
|
||||
value: [
|
||||
{ value: `1`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `2`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 2,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `3`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}]
|
||||
}, {
|
||||
height: 40,
|
||||
tdList: [{
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `4`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `5`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `6`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}]
|
||||
}, {
|
||||
height: 40,
|
||||
tdList: [{
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `7`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `8`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `9`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}, {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
value: [
|
||||
{ value: `1`, size: 16 },
|
||||
{ value: `0`, size: 16 },
|
||||
{ value: '.', size: 16 }
|
||||
]
|
||||
}]
|
||||
}]
|
||||
})
|
||||
data.push(...[{
|
||||
value: 'E',
|
||||
size: 16
|
||||
}, {
|
||||
value: 'O',
|
||||
size: 16
|
||||
}, {
|
||||
value: 'F',
|
||||
size: 16
|
||||
}])
|
||||
// 初始化编辑器
|
||||
const container = document.querySelector<HTMLDivElement>('.editor')!
|
||||
const instance = new Editor(container, data, {
|
||||
margins: [100, 120, 100, 120]
|
||||
})
|
||||
console.log('编辑器实例: ', instance)
|
||||
console.log('实例: ', instance)
|
||||
|
||||
// 撤销、重做、格式刷、清除格式
|
||||
const undoDom = document.querySelector<HTMLDivElement>('.menu-item__undo')!
|
||||
@@ -183,7 +287,81 @@ window.onload = function () {
|
||||
const li = evt.target as HTMLLIElement
|
||||
instance.command.executeRowMargin(Number(li.dataset.rowmargin!))
|
||||
}
|
||||
// 图片上传、搜索、打印
|
||||
// 表格插入、图片上传、搜索、打印
|
||||
const tableDom = document.querySelector<HTMLDivElement>('.menu-item__table')!
|
||||
const tablePanelContainer = document.querySelector<HTMLDivElement>('.menu-item__table__collapse')!
|
||||
const tableClose = document.querySelector<HTMLDivElement>('.table-close')!
|
||||
const tableTitle = document.querySelector<HTMLDivElement>('.table-select')!
|
||||
const tablePanel = document.querySelector<HTMLDivElement>('.table-panel')!
|
||||
// 绘制行列
|
||||
const tableCellList: HTMLDivElement[][] = []
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const tr = document.createElement('tr')
|
||||
tr.classList.add('table-row')
|
||||
const trCellList: HTMLDivElement[] = []
|
||||
for (let j = 0; j < 10; j++) {
|
||||
const td = document.createElement('td')
|
||||
td.classList.add('table-cel')
|
||||
tr.append(td)
|
||||
trCellList.push(td)
|
||||
}
|
||||
tablePanel.append(tr)
|
||||
tableCellList.push(trCellList)
|
||||
}
|
||||
let colIndex = 0
|
||||
let rowIndex = 0
|
||||
// 移除所有格选择
|
||||
function removeAllTableCellSelect() {
|
||||
tableCellList.forEach(tr => {
|
||||
tr.forEach(td => td.classList.remove('active'))
|
||||
})
|
||||
}
|
||||
// 设置标题内容
|
||||
function setTableTitle(payload: string) {
|
||||
tableTitle.innerText = payload
|
||||
}
|
||||
// 恢复初始状态
|
||||
function recoveryTable() {
|
||||
// 还原选择样式、标题、选择行列
|
||||
removeAllTableCellSelect()
|
||||
setTableTitle('插入')
|
||||
colIndex = 0
|
||||
rowIndex = 0
|
||||
// 隐藏panel
|
||||
tablePanelContainer.style.display = 'none'
|
||||
}
|
||||
tableDom.onclick = function () {
|
||||
console.log('table')
|
||||
tablePanelContainer!.style.display = 'block'
|
||||
}
|
||||
tablePanel.onmousemove = function (evt) {
|
||||
const celSize = 16
|
||||
const rowMarginTop = 10
|
||||
const celMarginRight = 6
|
||||
const { offsetX, offsetY } = evt
|
||||
// 移除所有选择
|
||||
removeAllTableCellSelect()
|
||||
colIndex = Math.ceil(offsetX / (celSize + celMarginRight)) || 1
|
||||
rowIndex = Math.ceil(offsetY / (celSize + rowMarginTop)) || 1
|
||||
// 改变选择样式
|
||||
tableCellList.forEach((tr, trIndex) => {
|
||||
tr.forEach((td, tdIndex) => {
|
||||
if (tdIndex < colIndex && trIndex < rowIndex) {
|
||||
td.classList.add('active')
|
||||
}
|
||||
})
|
||||
})
|
||||
// 改变表格标题
|
||||
setTableTitle(`${rowIndex}×${colIndex}`)
|
||||
}
|
||||
tableClose.onclick = function () {
|
||||
recoveryTable()
|
||||
}
|
||||
tablePanel.onclick = function () {
|
||||
// 应用选择
|
||||
instance.command.executeInsertTable(rowIndex, colIndex)
|
||||
recoveryTable()
|
||||
}
|
||||
const imageDom = document.querySelector<HTMLDivElement>('.menu-item__image')!
|
||||
const imageFileDom = document.querySelector<HTMLInputElement>('#image')!
|
||||
imageDom.onclick = function () {
|
||||
@@ -308,7 +486,6 @@ window.onload = function () {
|
||||
}
|
||||
|
||||
instance.listener.pageScaleChange = function (payload) {
|
||||
console.log('payload: ', payload);
|
||||
document.querySelector<HTMLSpanElement>('.page-scale-percentage')!.innerText = `${Math.floor(payload * 10 * 10)}%`
|
||||
}
|
||||
|
||||
|
||||
+87
-2
@@ -78,7 +78,7 @@ ul {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 6px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.menu-item>div:hover {
|
||||
@@ -97,7 +97,7 @@ ul {
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.menu-item>div span {
|
||||
.menu-item>div>span {
|
||||
width: 16px;
|
||||
height: 3px;
|
||||
display: inline-block;
|
||||
@@ -272,6 +272,91 @@ ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-item__table {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-item__table i {
|
||||
background-image: url('./assets/images/table.svg');
|
||||
}
|
||||
|
||||
.menu-item .menu-item__table__collapse {
|
||||
width: 270px;
|
||||
height: 310px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgb(56 56 56 / 20%);
|
||||
border: 1px solid #e2e6ed;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
display: none;
|
||||
z-index: 99;
|
||||
top: 25px;
|
||||
left: 0;
|
||||
padding: 14px 27px;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.menu-item .menu-item__table__collapse .table-close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item .menu-item__table__collapse .table-close:hover {
|
||||
color: #7d7e80;
|
||||
}
|
||||
|
||||
.menu-item .menu-item__table__collapse:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.menu-item .menu-item__table__collapse .table-title {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #e2e6ed;
|
||||
}
|
||||
|
||||
.table-title span {
|
||||
font-size: 12px;
|
||||
color: #3d4757;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table-panel {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table-panel .table-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-top: 10px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.table-panel .table-cel {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e2e6ed;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
margin-right: 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.table-panel .table-cel.active {
|
||||
border: 1px solid rgba(73, 145, 242, .2);
|
||||
background: rgba(73, 145, 242, .15);
|
||||
}
|
||||
|
||||
.table-panel .table-row .table-cel:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.menu-item__search {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user