feat:edit page header
This commit is contained in:
@@ -62,8 +62,9 @@ export class Cursor {
|
||||
// 设置光标代理
|
||||
const height = this.draw.getHeight()
|
||||
const pageGap = this.draw.getPageGap()
|
||||
const { metrics, coordinate: { leftTop, rightTop }, ascent, pageNo } = cursorPosition
|
||||
const preY = pageNo * (height + pageGap)
|
||||
const { metrics, coordinate: { leftTop, rightTop }, ascent } = cursorPosition
|
||||
const curPageNo = this.draw.getPageNo()
|
||||
const preY = curPageNo * (height + pageGap)
|
||||
// 增加1/4字体大小
|
||||
const offsetHeight = metrics.height / 4
|
||||
const cursorHeight = metrics.height + offsetHeight * 2
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { version } from '../../../../package.json'
|
||||
import { ZERO } from '../../dataset/constant/Common'
|
||||
import { RowFlex } from '../../dataset/enum/Row'
|
||||
import { IDrawOption, IDrawRowPayload, IPainterOptions } from '../../interface/Draw'
|
||||
import { IEditorDrawData, IEditorOption, IEditorResult } from '../../interface/Editor'
|
||||
import { IElement, IElementMetrics, IElementPosition, IElementFillRect, IElementStyle } from '../../interface/Element'
|
||||
import { IDrawOption, IDrawPagePayload, IDrawRowPayload, IPainterOptions } from '../../interface/Draw'
|
||||
import { IEditorData, IEditorOption, IEditorResult } from '../../interface/Editor'
|
||||
import { IElement, IElementMetrics, IElementFillRect, IElementStyle } from '../../interface/Element'
|
||||
import { IRow, IRowElement } from '../../interface/Row'
|
||||
import { deepClone, getUUID, nextTick } from '../../utils'
|
||||
import { Cursor } from '../cursor/Cursor'
|
||||
@@ -35,7 +35,7 @@ import { SubscriptParticle } from './particle/Subscript'
|
||||
import { SeparatorParticle } from './particle/Separator'
|
||||
import { PageBreakParticle } from './particle/PageBreak'
|
||||
import { Watermark } from './frame/Watermark'
|
||||
import { EditorComponent, EditorMode, PageMode, PaperDirection } from '../../dataset/enum/Editor'
|
||||
import { EditorComponent, EditorMode, EditorZone, PageMode, PaperDirection } from '../../dataset/enum/Editor'
|
||||
import { Control } from './control/Control'
|
||||
import { zipElementList } from '../../utils/element'
|
||||
import { CheckboxParticle } from './particle/CheckboxParticle'
|
||||
@@ -50,6 +50,7 @@ import { BlockParticle } from './particle/block/BlockParticle'
|
||||
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
|
||||
import { I18n } from '../i18n/I18n'
|
||||
import { ImageObserver } from '../observer/ImageObserver'
|
||||
import { Zone } from '../zone/Zone'
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -61,6 +62,7 @@ export class Draw {
|
||||
private mode: EditorMode
|
||||
private options: DeepRequired<IEditorOption>
|
||||
private position: Position
|
||||
private zone: Zone
|
||||
private headerElementList: IElement[]
|
||||
private elementList: IElement[]
|
||||
private footerElementList: IElement[]
|
||||
@@ -112,7 +114,7 @@ export class Draw {
|
||||
constructor(
|
||||
rootContainer: HTMLElement,
|
||||
options: DeepRequired<IEditorOption>,
|
||||
data: IEditorDrawData,
|
||||
data: IEditorData,
|
||||
listener: Listener
|
||||
) {
|
||||
this.container = this._wrapContainer(rootContainer)
|
||||
@@ -133,6 +135,7 @@ export class Draw {
|
||||
this.i18n = new I18n()
|
||||
this.historyManager = new HistoryManager()
|
||||
this.position = new Position(this)
|
||||
this.zone = new Zone()
|
||||
this.range = new RangeManager(this)
|
||||
this.margin = new Margin(this)
|
||||
this.background = new Background(this)
|
||||
@@ -212,6 +215,12 @@ export class Draw {
|
||||
return Math.floor(this.getOriginalHeight() * this.options.scale)
|
||||
}
|
||||
|
||||
public getOriginalMainHeight(): number {
|
||||
const mainHeight = this.getOriginalHeight()
|
||||
const extraHeight = this.header.getExtraHeight()
|
||||
return mainHeight - extraHeight
|
||||
}
|
||||
|
||||
public getCanvasWidth(pageNo = -1): number {
|
||||
const page = this.getPage(pageNo)
|
||||
return page.width
|
||||
@@ -343,6 +352,10 @@ export class Draw {
|
||||
return this.position
|
||||
}
|
||||
|
||||
public getZone(): Zone {
|
||||
return this.zone
|
||||
}
|
||||
|
||||
public getRange(): RangeManager {
|
||||
return this.range
|
||||
}
|
||||
@@ -351,12 +364,35 @@ export class Draw {
|
||||
return this.headerElementList
|
||||
}
|
||||
|
||||
public getTableElementList(sourceElementList: IElement[]): IElement[] {
|
||||
const positionContext = this.position.getPositionContext()
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
return sourceElementList[index!].trList![trIndex!].tdList[tdIndex!].value
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
const elementList = this.getOriginalElementList()
|
||||
return positionContext.isTable
|
||||
? this.getTableElementList(elementList)
|
||||
: elementList
|
||||
}
|
||||
|
||||
public getMainElementList(): IElement[] {
|
||||
const positionContext = this.position.getPositionContext()
|
||||
return positionContext.isTable
|
||||
? this.getTableElementList(this.elementList)
|
||||
: this.elementList
|
||||
}
|
||||
|
||||
public getOriginalElementList() {
|
||||
const zoneManager = this.getZone()
|
||||
return zoneManager.isHeaderActive()
|
||||
? this.header.getElementList()
|
||||
: this.elementList
|
||||
}
|
||||
|
||||
public getOriginalMainElementList(): IElement[] {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
@@ -406,10 +442,6 @@ export class Draw {
|
||||
}
|
||||
}
|
||||
|
||||
public getOriginalElementList() {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
public getCanvasEvent(): CanvasEvent {
|
||||
return this.canvasEvent
|
||||
}
|
||||
@@ -434,6 +466,10 @@ export class Draw {
|
||||
return this.tableTool
|
||||
}
|
||||
|
||||
public getHeader(): Header {
|
||||
return this.header
|
||||
}
|
||||
|
||||
public getHyperlinkParticle(): HyperlinkParticle {
|
||||
return this.hyperlinkParticle
|
||||
}
|
||||
@@ -610,7 +646,7 @@ export class Draw {
|
||||
// 配置
|
||||
const { width, height, margins, watermark } = this.options
|
||||
// 数据
|
||||
const data: IEditorDrawData = {
|
||||
const data: IEditorData = {
|
||||
header: zipElementList(this.headerElementList),
|
||||
main: zipElementList(this.elementList)
|
||||
}
|
||||
@@ -768,7 +804,7 @@ export class Draw {
|
||||
metrics.boundingBoxAscent = 0
|
||||
// 表格分页处理(拆分表格)
|
||||
const margins = this.getMargins()
|
||||
const height = this.getHeight()
|
||||
const height = this.getOriginalMainHeight()
|
||||
const marginHeight = margins[0] + margins[2]
|
||||
let curPagePreHeight = marginHeight
|
||||
for (let r = 0; r < rowList.length; r++) {
|
||||
@@ -826,13 +862,13 @@ export class Draw {
|
||||
} else if (element.type === ElementType.SEPARATOR) {
|
||||
element.width = innerWidth
|
||||
metrics.width = innerWidth
|
||||
metrics.height = this.options.defaultSize
|
||||
metrics.height = defaultSize
|
||||
metrics.boundingBoxAscent = -rowMargin
|
||||
metrics.boundingBoxDescent = -rowMargin
|
||||
} else if (element.type === ElementType.PAGE_BREAK) {
|
||||
element.width = innerWidth
|
||||
metrics.width = innerWidth
|
||||
metrics.height = this.options.defaultSize
|
||||
metrics.height = defaultSize
|
||||
} else if (
|
||||
element.type === ElementType.CHECKBOX ||
|
||||
element.controlComponent === ControlComponent.CHECKBOX
|
||||
@@ -860,7 +896,7 @@ export class Draw {
|
||||
metrics.boundingBoxAscent = 0
|
||||
} else {
|
||||
// 设置上下标真实字体尺寸
|
||||
const size = element.size || this.options.defaultSize
|
||||
const size = element.size || defaultSize
|
||||
if (element.type === ElementType.SUPERSCRIPT || element.type === ElementType.SUBSCRIPT) {
|
||||
element.actualSize = Math.ceil(size * 0.6)
|
||||
}
|
||||
@@ -934,7 +970,7 @@ export class Draw {
|
||||
private _computePageList(): IRow[][] {
|
||||
const pageRowList: IRow[][] = [[]]
|
||||
const { pageMode } = this.options
|
||||
const height = this.getHeight()
|
||||
const height = this.getOriginalMainHeight()
|
||||
const margins = this.getMargins()
|
||||
const marginHeight = margins[0] + margins[2]
|
||||
let pageHeight = marginHeight
|
||||
@@ -979,7 +1015,7 @@ export class Draw {
|
||||
}
|
||||
|
||||
public drawRow(ctx: CanvasRenderingContext2D, payload: IDrawRowPayload) {
|
||||
const { rowList, pageNo, positionList, startIndex } = payload
|
||||
const { rowList, pageNo, elementList, positionList, startIndex, zone } = payload
|
||||
const { scale, tdPadding } = this.options
|
||||
const { isCrossRowCol, tableId } = this.range.getRange()
|
||||
let index = startIndex
|
||||
@@ -1077,11 +1113,11 @@ export class Draw {
|
||||
this.highlight.render(ctx)
|
||||
}
|
||||
// 选区记录
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
if (startIndex !== endIndex && startIndex <= index && index <= endIndex) {
|
||||
const { zone: currentZone, startIndex, endIndex } = this.range.getRange()
|
||||
if (currentZone === zone && startIndex !== endIndex && startIndex <= index && index <= endIndex) {
|
||||
// 从行尾开始-绘制最小宽度
|
||||
if (startIndex === index) {
|
||||
const nextElement = this.elementList[startIndex + 1]
|
||||
const nextElement = elementList[startIndex + 1]
|
||||
if (nextElement && nextElement.value === ZERO) {
|
||||
rangeRecord.x = x + metrics.width
|
||||
rangeRecord.y = y
|
||||
@@ -1119,11 +1155,13 @@ export class Draw {
|
||||
for (let d = 0; d < tr.tdList!.length; d++) {
|
||||
const td = tr.tdList[d]
|
||||
this.drawRow(ctx, {
|
||||
elementList: td.value,
|
||||
positionList: td.positionList!,
|
||||
rowList: td.rowList!,
|
||||
pageNo,
|
||||
startIndex: 0,
|
||||
innerWidth: (td.width! - tdGap) * scale
|
||||
innerWidth: (td.width! - tdGap) * scale,
|
||||
zone
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1150,10 +1188,13 @@ export class Draw {
|
||||
this.blockParticle.clear()
|
||||
}
|
||||
|
||||
private _drawPage(positionList: IElementPosition[], rowList: IRow[], pageNo: number) {
|
||||
const { pageMode } = this.options
|
||||
private _drawPage(payload: IDrawPagePayload) {
|
||||
const { elementList, positionList, rowList, pageNo } = payload
|
||||
const { inactiveAlpha, pageMode } = this.options
|
||||
const innerWidth = this.getInnerWidth()
|
||||
const ctx = this.ctxList[pageNo]
|
||||
// 判断当前激活区域-激活页眉时主题元素透明度降低
|
||||
ctx.globalAlpha = this.zone.isHeaderActive() ? inactiveAlpha : 1
|
||||
this._clearPage(pageNo)
|
||||
// 绘制背景
|
||||
this.background.render(ctx)
|
||||
@@ -1162,14 +1203,16 @@ export class Draw {
|
||||
// 渲染元素
|
||||
const index = rowList[0].startIndex
|
||||
this.drawRow(ctx, {
|
||||
elementList,
|
||||
positionList,
|
||||
rowList,
|
||||
pageNo,
|
||||
startIndex: index,
|
||||
innerWidth
|
||||
innerWidth,
|
||||
zone: EditorZone.MAIN
|
||||
})
|
||||
// 绘制页眉
|
||||
this.header.render(ctx)
|
||||
this.header.render(ctx, pageNo)
|
||||
// 绘制页码
|
||||
this.pageNumber.render(ctx, pageNo)
|
||||
// 搜索匹配绘制
|
||||
@@ -1183,14 +1226,20 @@ export class Draw {
|
||||
}
|
||||
|
||||
private _lazyRender() {
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
const positionList = this.position.getOriginalMainPositionList()
|
||||
const elementList = this.getOriginalMainElementList()
|
||||
this.lazyRenderIntersectionObserver?.disconnect()
|
||||
this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const index = Number((<HTMLCanvasElement>entry.target).dataset.index)
|
||||
this.header.render(this.ctxList[index])
|
||||
this._drawPage(positionList, this.pageRowList[index], index)
|
||||
this.header.render(this.ctxList[index], index)
|
||||
this._drawPage({
|
||||
elementList,
|
||||
positionList,
|
||||
rowList: this.pageRowList[index],
|
||||
pageNo: index
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1200,9 +1249,15 @@ export class Draw {
|
||||
}
|
||||
|
||||
private _immediateRender() {
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
const positionList = this.position.getOriginalMainPositionList()
|
||||
const elementList = this.getOriginalMainElementList()
|
||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
||||
this._drawPage(positionList, this.pageRowList[i], i)
|
||||
this._drawPage({
|
||||
elementList,
|
||||
positionList,
|
||||
rowList: this.pageRowList[i],
|
||||
pageNo: i
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1236,7 +1291,6 @@ export class Draw {
|
||||
this.imageObserver.clearAll()
|
||||
this.cursor.recoveryCursor()
|
||||
// 创建纸张
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
||||
if (!this.pageList[i]) {
|
||||
this._createPage(i)
|
||||
@@ -1260,20 +1314,19 @@ export class Draw {
|
||||
}
|
||||
// 光标重绘
|
||||
if (isSetCursor) {
|
||||
const positionList = this.position.getPositionList()
|
||||
const positionContext = this.position.getPositionContext()
|
||||
if (positionContext.isTable) {
|
||||
const { index, trIndex, tdIndex } = positionContext
|
||||
const tablePositionList = this.elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList
|
||||
const elementList = this.getElementList()
|
||||
const tablePositionList = elementList[index!].trList?.[trIndex!].tdList[tdIndex!].positionList
|
||||
if (curIndex === undefined && tablePositionList) {
|
||||
curIndex = tablePositionList.length - 1
|
||||
}
|
||||
const tablePosition = tablePositionList?.[curIndex!]
|
||||
this.position.setCursorPosition(tablePosition || null)
|
||||
} else {
|
||||
if (curIndex === undefined) {
|
||||
curIndex = positionList.length - 1
|
||||
}
|
||||
this.position.setCursorPosition(positionList[curIndex!] || null)
|
||||
this.position.setCursorPosition(curIndex !== undefined ? positionList[curIndex] : null)
|
||||
}
|
||||
this.cursor.drawCursor()
|
||||
}
|
||||
@@ -1281,12 +1334,16 @@ export class Draw {
|
||||
if (isSubmitHistory) {
|
||||
const self = this
|
||||
const oldElementList = deepClone(this.elementList)
|
||||
const oldHeaderElementList = deepClone(this.header.getElementList())
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
const pageNo = this.pageNo
|
||||
const oldPositionContext = deepClone(this.position.getPositionContext())
|
||||
const zone = this.zone.getZone()
|
||||
this.historyManager.execute(function () {
|
||||
self.zone.setZone(zone)
|
||||
self.setPageNo(pageNo)
|
||||
self.position.setPositionContext(oldPositionContext)
|
||||
self.header.setElementList(oldHeaderElementList)
|
||||
self.elementList = deepClone(oldElementList)
|
||||
self.range.setRange(startIndex, endIndex)
|
||||
self.render({ curIndex, isSubmitHistory: false })
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { maxHeightRadioMapping } from '../../../dataset/constant/Header'
|
||||
import { EditorZone } from '../../../dataset/enum/Editor'
|
||||
import { DeepRequired } from '../../../interface/Common'
|
||||
import { IEditorOption } from '../../../interface/Editor'
|
||||
import { IElement, IElementPosition } from '../../../interface/Element'
|
||||
@@ -25,6 +27,18 @@ export class Header {
|
||||
this.positionList = []
|
||||
}
|
||||
|
||||
public setElementList(elementList: IElement[]) {
|
||||
this.elementList = elementList
|
||||
}
|
||||
|
||||
public getElementList(): IElement[] {
|
||||
return this.elementList
|
||||
}
|
||||
|
||||
public getPositionList(): IElementPosition[] {
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
public compute() {
|
||||
this._recovery()
|
||||
this._computeRowList()
|
||||
@@ -46,7 +60,7 @@ export class Header {
|
||||
const innerWidth = this.draw.getInnerWidth()
|
||||
const margins = this.draw.getMargins()
|
||||
const startX = margins[3]
|
||||
const startY = margins[0] + top
|
||||
const startY = top
|
||||
this.position.computePageRowPosition({
|
||||
positionList: this.positionList,
|
||||
rowList: this.rowList,
|
||||
@@ -58,14 +72,53 @@ export class Header {
|
||||
})
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D) {
|
||||
public getMaxHeight(): number {
|
||||
const { header: { maxHeightRadio }, height } = this.options
|
||||
return height * maxHeightRadioMapping[maxHeightRadio]
|
||||
}
|
||||
|
||||
public getHeight(): number {
|
||||
const maxHeight = this.getMaxHeight()
|
||||
const rowHeight = this.getRowHeight()
|
||||
return rowHeight > maxHeight ? maxHeight : rowHeight
|
||||
}
|
||||
|
||||
public getRowHeight(): number {
|
||||
return this.rowList.reduce((pre, cur) => pre + cur.height, 0)
|
||||
}
|
||||
|
||||
public getExtraHeight(): number {
|
||||
const { header: { top: headerTop } } = this.options
|
||||
// 页眉上边距 + 实际高 - 页面上边距
|
||||
const rowHeight = this.getRowHeight()
|
||||
const margins = this.draw.getOriginalMargins()
|
||||
const extraHeight = headerTop + rowHeight - margins[0]
|
||||
return extraHeight <= 0 ? 0 : extraHeight
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, pageNo: number) {
|
||||
ctx.globalAlpha = 1
|
||||
const innerWidth = this.draw.getInnerWidth()
|
||||
const maxHeight = this.getMaxHeight()
|
||||
// 超出最大高度不渲染
|
||||
const rowList: IRow[] = []
|
||||
let curRowHeight = 0
|
||||
for (let r = 0; r < this.rowList.length; r++) {
|
||||
const row = this.rowList[r]
|
||||
if (curRowHeight + row.height > maxHeight) {
|
||||
break
|
||||
}
|
||||
rowList.push(row)
|
||||
curRowHeight += row.height
|
||||
}
|
||||
this.draw.drawRow(ctx, {
|
||||
elementList: this.elementList,
|
||||
positionList: this.positionList,
|
||||
rowList: this.rowList,
|
||||
pageNo: 0,
|
||||
rowList,
|
||||
pageNo,
|
||||
startIndex: 0,
|
||||
innerWidth
|
||||
innerWidth,
|
||||
zone: EditorZone.HEADER
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -147,8 +147,8 @@ export class CanvasEvent {
|
||||
keydown(evt, this)
|
||||
}
|
||||
|
||||
public dblclick() {
|
||||
click.dblclick(this)
|
||||
public dblclick(evt: MouseEvent) {
|
||||
click.dblclick(this, evt)
|
||||
}
|
||||
|
||||
public threeClick() {
|
||||
|
||||
@@ -2,9 +2,25 @@ import { ZERO } from '../../../dataset/constant/Common'
|
||||
import { LETTER_REG, NUMBER_LIKE_REG } from '../../../dataset/constant/Regular'
|
||||
import { CanvasEvent } from '../CanvasEvent'
|
||||
|
||||
function dblclick(host: CanvasEvent) {
|
||||
function dblclick(host: CanvasEvent, evt: MouseEvent) {
|
||||
// 切换区域
|
||||
const draw = host.getDraw()
|
||||
const position = draw.getPosition()
|
||||
const positionContext = position.getPositionByXY({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
if (!~positionContext.index && positionContext.zone) {
|
||||
const zoneManager = draw.getZone()
|
||||
zoneManager.setZone(positionContext.zone)
|
||||
draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isCompute: false
|
||||
})
|
||||
return
|
||||
}
|
||||
// 自动扩选文字
|
||||
const cursorPosition = position.getCursorPosition()
|
||||
if (!cursorPosition) return
|
||||
const { value, index } = cursorPosition
|
||||
|
||||
@@ -21,10 +21,12 @@ function dragover(evt: DragEvent | MouseEvent, host: CanvasEvent) {
|
||||
draw.setPageNo(Number(pageIndex))
|
||||
}
|
||||
const position = draw.getPosition()
|
||||
const { isTable, tdValueIndex, index } = position.adjustPositionContext({
|
||||
const positionContext = position.adjustPositionContext({
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
if (!positionContext) return
|
||||
const { isTable, tdValueIndex, index } = positionContext
|
||||
// 设置选区及光标位置
|
||||
const positionList = position.getPositionList()
|
||||
const curIndex = isTable ? tdValueIndex! : index
|
||||
|
||||
@@ -36,6 +36,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) {
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
if (!positionResult) return
|
||||
const {
|
||||
index,
|
||||
isDirectHit,
|
||||
|
||||
@@ -32,6 +32,7 @@ export function mousemove(evt: MouseEvent, host: CanvasEvent) {
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY
|
||||
})
|
||||
if (!~positionResult.index) return
|
||||
const {
|
||||
index,
|
||||
isTable,
|
||||
|
||||
@@ -3,9 +3,10 @@ import { ZERO } from '../../dataset/constant/Common'
|
||||
import { ControlComponent, ImageDisplay } from '../../dataset/enum/Control'
|
||||
import { IComputePageRowPositionPayload, IComputePageRowPositionResult } from '../../interface/Position'
|
||||
import { IEditorOption } from '../../interface/Editor'
|
||||
import { IElementPosition } from '../../interface/Element'
|
||||
import { IElement, IElementPosition } from '../../interface/Element'
|
||||
import { ICurrentPosition, IGetPositionByXYPayload, IPositionContext } from '../../interface/Position'
|
||||
import { Draw } from '../draw/Draw'
|
||||
import { EditorZone } from '../../dataset/enum/Editor'
|
||||
|
||||
export class Position {
|
||||
|
||||
@@ -28,17 +29,32 @@ export class Position {
|
||||
this.options = draw.getOptions()
|
||||
}
|
||||
|
||||
public getOriginalPositionList(): IElementPosition[] {
|
||||
return this.positionList
|
||||
public getTablePositionList(sourceElementList: IElement[]): IElementPosition[] {
|
||||
const { index, trIndex, tdIndex } = this.positionContext
|
||||
return sourceElementList[index!].trList![trIndex!].tdList[tdIndex!].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 || []
|
||||
}
|
||||
const elementList = this.draw.getElementList()
|
||||
return this.positionContext.isTable
|
||||
? this.getTablePositionList(elementList)
|
||||
: this.getOriginalPositionList()
|
||||
}
|
||||
|
||||
public getMainPositionList(): IElementPosition[] {
|
||||
const elementList = this.draw.getMainElementList()
|
||||
return this.positionContext.isTable
|
||||
? this.getTablePositionList(elementList)
|
||||
: this.positionList
|
||||
}
|
||||
|
||||
public getOriginalPositionList(): IElementPosition[] {
|
||||
const zoneManager = this.draw.getZone()
|
||||
const header = this.draw.getHeader()
|
||||
return zoneManager.isHeaderActive() ? header.getPositionList() : this.positionList
|
||||
}
|
||||
|
||||
public getOriginalMainPositionList(): IElementPosition[] {
|
||||
return this.positionList
|
||||
}
|
||||
|
||||
@@ -130,7 +146,10 @@ export class Position {
|
||||
const pageRowList = this.draw.getPageRowList()
|
||||
const margins = this.draw.getMargins()
|
||||
const startX = margins[3]
|
||||
const startY = margins[0]
|
||||
// 起始位置受页眉影响
|
||||
const header = this.draw.getHeader()
|
||||
const extraHeight = header.getExtraHeight()
|
||||
const startY = margins[0] + extraHeight
|
||||
for (let i = 0; i < pageRowList.length; i++) {
|
||||
const rowList = pageRowList[i]
|
||||
const startIndex = rowList[0].startIndex
|
||||
@@ -169,12 +188,14 @@ export class Position {
|
||||
elementList = this.draw.getOriginalElementList()
|
||||
}
|
||||
if (!positionList) {
|
||||
positionList = this.positionList
|
||||
positionList = this.getOriginalPositionList()
|
||||
}
|
||||
const zoneManager = this.draw.getZone()
|
||||
const curPageNo = this.draw.getPageNo()
|
||||
const positionNo = zoneManager.isMainActive() ? curPageNo : 0
|
||||
for (let j = 0; j < positionList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, rightTop, leftBottom } } = positionList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
if (positionNo !== pageNo) continue
|
||||
// 命中元素
|
||||
if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) {
|
||||
let curPositionIndex = j
|
||||
@@ -267,15 +288,15 @@ export class Position {
|
||||
}
|
||||
}
|
||||
// 判断所属行是否存在元素
|
||||
const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === curPageNo)
|
||||
const firstLetterList = positionList.filter(p => p.isLastLetter && p.pageNo === positionNo)
|
||||
for (let j = 0; j < firstLetterList.length; j++) {
|
||||
const { index, pageNo, coordinate: { leftTop, leftBottom } } = firstLetterList[j]
|
||||
if (curPageNo !== pageNo) continue
|
||||
if (positionNo !== pageNo) continue
|
||||
if (y > leftTop[1] && y <= leftBottom[1]) {
|
||||
const isHead = x < this.options.margins[3]
|
||||
// 是否在头部
|
||||
if (isHead) {
|
||||
const headIndex = positionList.findIndex(p => p.pageNo === curPageNo && p.rowNo === firstLetterList[j].rowNo)
|
||||
const headIndex = positionList.findIndex(p => p.pageNo === positionNo && p.rowNo === firstLetterList[j].rowNo)
|
||||
curPositionIndex = ~headIndex ? headIndex - 1 : index
|
||||
} else {
|
||||
curPositionIndex = index
|
||||
@@ -285,8 +306,28 @@ export class Position {
|
||||
}
|
||||
}
|
||||
if (!isLastArea) {
|
||||
// 判断所属位置是否属于header区域,当前位置小于第一行的上边距
|
||||
if (zoneManager.isMainActive()) {
|
||||
if (y < firstLetterList[0].coordinate.leftTop[1]) {
|
||||
return {
|
||||
index: -1,
|
||||
zone: EditorZone.HEADER
|
||||
}
|
||||
}
|
||||
}
|
||||
// 判断所属位置是否属于main区域,当前位置大于第一行的上边距
|
||||
if (zoneManager.isHeaderActive()) {
|
||||
if (y > firstLetterList[0].coordinate.leftTop[1]) {
|
||||
return {
|
||||
index: -1,
|
||||
zone: EditorZone.MAIN
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当前页最后一行
|
||||
return { index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1 }
|
||||
return {
|
||||
index: firstLetterList[firstLetterList.length - 1]?.index || positionList.length - 1,
|
||||
}
|
||||
}
|
||||
return {
|
||||
index: curPositionIndex,
|
||||
@@ -294,13 +335,10 @@ export class Position {
|
||||
}
|
||||
}
|
||||
|
||||
public adjustPositionContext(payload: Pick<IGetPositionByXYPayload, 'x' | 'y'>): ICurrentPosition {
|
||||
public adjustPositionContext(payload: IGetPositionByXYPayload): ICurrentPosition | null {
|
||||
const isReadonly = this.draw.isReadonly()
|
||||
const { x, y } = payload
|
||||
const positionResult = this.getPositionByXY({
|
||||
x,
|
||||
y
|
||||
})
|
||||
const positionResult = this.getPositionByXY(payload)
|
||||
if (!~positionResult.index) return null
|
||||
// 移动控件内光标
|
||||
if (positionResult.isControl && !isReadonly) {
|
||||
const {
|
||||
|
||||
@@ -90,6 +90,7 @@ export class RangeManager {
|
||||
this.range.startTrIndex = startTrIndex
|
||||
this.range.endTrIndex = endTrIndex
|
||||
this.range.isCrossRowCol = !!(startTdIndex || endTdIndex || startTrIndex || endTrIndex)
|
||||
this.range.zone = this.draw.getZone().getZone()
|
||||
// 激活控件
|
||||
const control = this.draw.getControl()
|
||||
if (~startIndex && ~endIndex) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { EditorZone } from '../../dataset/enum/Editor'
|
||||
|
||||
export class Zone {
|
||||
|
||||
private currentZone: EditorZone
|
||||
|
||||
constructor() {
|
||||
this.currentZone = EditorZone.MAIN
|
||||
}
|
||||
|
||||
public isHeaderActive(): boolean {
|
||||
return this.getZone() === EditorZone.HEADER
|
||||
}
|
||||
|
||||
public isMainActive(): boolean {
|
||||
return this.getZone() === EditorZone.MAIN
|
||||
}
|
||||
|
||||
public getZone(): EditorZone {
|
||||
return this.currentZone
|
||||
}
|
||||
|
||||
public setZone(payload: EditorZone) {
|
||||
this.currentZone = payload
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,12 @@ import { IHeader } from '../../interface/Header'
|
||||
import { HeaderMaxHeightRatio } from '../enum/Header'
|
||||
|
||||
export const defaultHeaderOption: Readonly<Required<IHeader>> = {
|
||||
top: -50,
|
||||
top: 30,
|
||||
maxHeightRadio: HeaderMaxHeightRatio.HALF
|
||||
}
|
||||
|
||||
export const maxHeightRadioMapping: Record<HeaderMaxHeightRatio, number> = {
|
||||
[HeaderMaxHeightRatio.HALF]: 1 / 2,
|
||||
[HeaderMaxHeightRatio.ONE_THIRD]: 1 / 3,
|
||||
[HeaderMaxHeightRatio.QUARTER]: 1 / 4
|
||||
}
|
||||
+2
-1
@@ -39,7 +39,7 @@ export default class Editor {
|
||||
public register: Register
|
||||
public destroy: Function
|
||||
|
||||
constructor(container: HTMLDivElement, data: IEditorData, options: IEditorOption = {}) {
|
||||
constructor(container: HTMLDivElement, data: IEditorData | IElement[], options: IEditorOption = {}) {
|
||||
const headerOptions: Required<IHeader> = {
|
||||
...defaultHeaderOption,
|
||||
...options.header
|
||||
@@ -96,6 +96,7 @@ export default class Editor {
|
||||
defaultHyperlinkColor: '#0000FF',
|
||||
headerTop: 50,
|
||||
paperDirection: PaperDirection.VERTICAL,
|
||||
inactiveAlpha: 0.6,
|
||||
...options,
|
||||
header: headerOptions,
|
||||
watermark: waterMarkOptions,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IElementPosition } from './Element'
|
||||
import { EditorZone } from '../dataset/enum/Editor'
|
||||
import { IElement, IElementPosition } from './Element'
|
||||
import { IRow } from './Row'
|
||||
|
||||
export interface IDrawOption {
|
||||
@@ -16,11 +17,20 @@ export interface IDrawImagePayload {
|
||||
}
|
||||
|
||||
export interface IDrawRowPayload {
|
||||
elementList: IElement[];
|
||||
positionList: IElementPosition[];
|
||||
rowList: IRow[];
|
||||
pageNo: number;
|
||||
startIndex: number;
|
||||
innerWidth: number;
|
||||
zone: EditorZone;
|
||||
}
|
||||
|
||||
export interface IDrawPagePayload {
|
||||
elementList: IElement[];
|
||||
positionList: IElementPosition[];
|
||||
rowList: IRow[];
|
||||
pageNo: number;
|
||||
}
|
||||
|
||||
export interface IPainterOptions {
|
||||
|
||||
@@ -7,14 +7,12 @@ import { IHeader } from './Header'
|
||||
import { IMargin } from './Margin'
|
||||
import { IWatermark } from './Watermark'
|
||||
|
||||
export interface IEditorDrawData {
|
||||
export interface IEditorData {
|
||||
header?: IElement[];
|
||||
main: IElement[];
|
||||
footer?: IElement[];
|
||||
}
|
||||
|
||||
export type IEditorData = IEditorDrawData | IElement[]
|
||||
|
||||
export interface IEditorOption {
|
||||
mode?: EditorMode;
|
||||
defaultType?: string;
|
||||
@@ -50,6 +48,7 @@ export interface IEditorOption {
|
||||
defaultHyperlinkColor?: string;
|
||||
headerTop?: number;
|
||||
paperDirection?: PaperDirection;
|
||||
inactiveAlpha?: number;
|
||||
header?: IHeader;
|
||||
watermark?: IWatermark;
|
||||
control?: IControlOption;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IElement } from '..'
|
||||
import { EditorZone } from '../dataset/enum/Editor'
|
||||
import { IElementPosition } from './Element'
|
||||
import { IRow } from './Row'
|
||||
import { ITd } from './table/Td'
|
||||
@@ -16,6 +17,7 @@ export interface ICurrentPosition {
|
||||
tdId?: string;
|
||||
trId?: string;
|
||||
tableId?: string;
|
||||
zone?: EditorZone;
|
||||
}
|
||||
|
||||
export interface IGetPositionByXYPayload {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EditorZone } from '../dataset/enum/Editor'
|
||||
|
||||
export interface IRange {
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
@@ -7,6 +9,7 @@ export interface IRange {
|
||||
endTdIndex?: number;
|
||||
startTrIndex?: number;
|
||||
endTrIndex?: number;
|
||||
zone?: EditorZone;
|
||||
}
|
||||
|
||||
export type RangeRowMap = Map<number, Set<number>>
|
||||
|
||||
+9
-3
@@ -15,10 +15,16 @@ window.onload = function () {
|
||||
container,
|
||||
{
|
||||
header: [{
|
||||
value: '人民医院门诊',
|
||||
size: 14,
|
||||
color: '#AAAAAA',
|
||||
value: '第一人民医院',
|
||||
size: 32,
|
||||
rowFlex: RowFlex.CENTER
|
||||
}, {
|
||||
value: '\n门诊病历',
|
||||
size: 18,
|
||||
rowFlex: RowFlex.CENTER
|
||||
}, {
|
||||
value: '\n',
|
||||
type: ElementType.SEPARATOR
|
||||
}],
|
||||
main: <IElement[]>data
|
||||
},
|
||||
|
||||
+8
-28
@@ -1,13 +1,6 @@
|
||||
import { ControlType, ElementType, IEditorOption, IElement, RowFlex } from './editor'
|
||||
import { ControlType, ElementType, IEditorOption, IElement } from './editor'
|
||||
|
||||
const text = `人民医院门诊病历\n主诉:\n发热三天,咳嗽五天。\n现病史:\n患者于三天前无明显诱因,感冒后发现面部水肿,无皮疹,尿量减少,出现乏力,在外治疗无好转,现来我院就诊。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。报告其他既往疾病。\n流行病史:\n否认14天内接触过确诊患者、疑似患者、无症状感染者及其密切接触者;否认14天内去过以下场所:水产、肉类批发市场,农贸市场,集市,大型超市,夜市;否认14天内与以下场所工作人员密切接触:水产、肉类批发市场,农贸市场,集市,大型超市;否认14天内周围(如家庭、办公室)有2例以上聚集性发病;否认14天内接触过有发热或呼吸道症状的人员;否认14天内自身有发热或呼吸道症状;否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形;陪同家属无以上情况。\n体格检查:\nT:39.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10/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 => {
|
||||
const i = text.indexOf(c)
|
||||
return ~i ? Array(c.length).fill(i).map((_, j) => i + j) : []
|
||||
}).flat()
|
||||
const text = `主诉:\n发热三天,咳嗽五天。\n现病史:\n患者于三天前无明显诱因,感冒后发现面部水肿,无皮疹,尿量减少,出现乏力,在外治疗无好转,现来我院就诊。\n既往史:\n有糖尿病10年,有高血压2年,有传染性疾病1年。报告其他既往疾病。\n流行病史:\n否认14天内接触过确诊患者、疑似患者、无症状感染者及其密切接触者;否认14天内去过以下场所:水产、肉类批发市场,农贸市场,集市,大型超市,夜市;否认14天内与以下场所工作人员密切接触:水产、肉类批发市场,农贸市场,集市,大型超市;否认14天内周围(如家庭、办公室)有2例以上聚集性发病;否认14天内接触过有发热或呼吸道症状的人员;否认14天内自身有发热或呼吸道症状;否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形;陪同家属无以上情况。\n体格检查:\nT:39.5℃,P:80bpm,R:20次/分,BP:120/80mmHg;\n辅助检查:\n2020年6月10日,普放:血细胞比容36.50%(偏低)40~50;单核细胞绝对值0.75*10/L(偏高)参考值:0.1~0.6;\n门诊诊断:\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗:\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名:【】\n其他记录:`
|
||||
|
||||
// 模拟加粗字
|
||||
const boldText = ['主诉:', '现病史:', '既往史:', '流行病史:', '体格检查:', '辅助检查:', '门诊诊断:', '处置治疗:', '电子签名:', '其他记录:']
|
||||
@@ -32,13 +25,6 @@ const highlightIndex: number[] = highlightText.map(b => {
|
||||
|
||||
// 组合纯文本数据
|
||||
const elementList: IElement[] = text.split('').map((value, index) => {
|
||||
if (centerIndex.includes(index)) {
|
||||
return {
|
||||
value,
|
||||
size: 32,
|
||||
rowFlex: RowFlex.CENTER
|
||||
}
|
||||
}
|
||||
if (boldIndex.includes(index)) {
|
||||
return {
|
||||
value,
|
||||
@@ -65,14 +51,8 @@ const elementList: IElement[] = text.split('').map((value, index) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 模拟分隔符
|
||||
elementList.splice(8, 0, {
|
||||
value: '\n',
|
||||
type: ElementType.SEPARATOR
|
||||
})
|
||||
|
||||
// 模拟文本控件
|
||||
elementList.splice(24, 0, {
|
||||
elementList.splice(14, 0, {
|
||||
type: ElementType.CONTROL,
|
||||
value: '',
|
||||
control: {
|
||||
@@ -85,7 +65,7 @@ elementList.splice(24, 0, {
|
||||
})
|
||||
|
||||
// 模拟下拉控件
|
||||
elementList.splice(112, 0, {
|
||||
elementList.splice(102, 0, {
|
||||
type: ElementType.CONTROL,
|
||||
value: '',
|
||||
control: {
|
||||
@@ -109,7 +89,7 @@ elementList.splice(112, 0, {
|
||||
})
|
||||
|
||||
// 模拟超链接
|
||||
elementList.splice(138, 0, {
|
||||
elementList.splice(128, 0, {
|
||||
type: ElementType.HYPERLINK,
|
||||
value: '',
|
||||
valueList: [{
|
||||
@@ -129,20 +109,20 @@ elementList.splice(138, 0, {
|
||||
})
|
||||
|
||||
// 模拟下标
|
||||
elementList.splice(371, 0, {
|
||||
elementList.splice(361, 0, {
|
||||
value: '∆',
|
||||
color: '#FF0000',
|
||||
type: ElementType.SUBSCRIPT
|
||||
})
|
||||
|
||||
// 模拟上标
|
||||
elementList.splice(459, 0, {
|
||||
elementList.splice(449, 0, {
|
||||
value: '9',
|
||||
type: ElementType.SUPERSCRIPT
|
||||
})
|
||||
|
||||
// 模拟图片
|
||||
elementList.splice(585, 0, {
|
||||
elementList.splice(575, 0, {
|
||||
value: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAAAgCAYAAAB5JtSmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAQ0SURBVGhD7dhrUSNBFAVgvKACEVjAAhJQgAIUYAABGEAABvgfAdn6UnWou01PppOZhIXNj1P9vo9zH5PK1Waz2V5wWlxIPgMuJJ8Bi0h+fn7eXl9fb29ubrYPDw/dO/8DHh8fu/vB4kym4Orqaofb29vund8OSSbhemewSrugBMnG3vlvw9vb265yn56edmtz/t/f33+5C8MkixQSZSsl9UzLOHUmcwTYAN/Rpl5eXnY+pnIB0Xd3d7s5m3rvDsrkCGszNiQ7r/tr4v39fSc/uipOqRcqufTHBiO78GGdzG5xcLtIFmVde7L9NsvXRo9s84+Pj+79pUAwn5GcD1wIz5r+fYGeJdnjGiF9hwL7iWAcfX19/evtKVHJXrtN8Rf4A3TVczqhrut5i1mSZQgnIriSWtdzP2N+EvIhi3/GWqHWtWXuy2IYbheiKarJZIZknkxyrryc2Utrgal+9S8iScUXIx/3kcxfe/jotcuDezLFlIbARDrzHpytXdKnQr4xyc74Vu9YV5Ih2Q/tT7mDSEYw5ZU4wu3nJx64k/1z9umlUG0hah/JSbC6Jzi5exDJWoTHERoBxu8uf/pT1j3HDkUIJitjbRfRA/iwVzlgy1RCfSF5ili9xj7BUWKs9wJZ3MpditYu+lsc+/PRx53cVF9Pdg/syE9Hb6cS75PkmhUEUFofmTvLGEXKimHueJP9Y3swWQwGLUiA9xEbHKuvgs4pPe1+1myTAKlw81buJ8kigjAXKauXPLQPhEYgJSEYsgdTUR0BmTVgc6C359wcvKGnBrGO8dO5VlD1ZZ519nrBHvrwKVMCas9hgL0YUI2wV98fC4FqCWizzXyqF44A0ZKLHkilgvPs1zbiTuZIdZ414KvqGCKZYx4zple+MSrrJVncAyL02/TOqncJwVMglx5zI4QDZ5WPvBGEcNP+7TlEcqJIAQFGsIdQjmZt7MlYA5yiI3pOQTCQXUm2TuVmXgmewxDJQDgl6deJJoU5y7p9uwZagmu1mCvbNoOOBfkhOf6lRZjzPb8qRjBMMiUhM9GNMZQq5/oRXBP7Mlj/i12A7EMIaJGqDcl8I79+/N1xTvdINQ2TDAQSvI9Md479vdqCHKSFQKAfEmgBqCTDkjaSgOZXQkg2jy1ti0xApnBQJo/0obQRipeQXbN3CmxKGQch5xgki4Efghl/kFqzPD//2DnXIodIRpaoETaXxcmwGNO7N4I2Oyuc6b+xK/tL9IH3kY/E+r1JdST4yM+7VUiuJbuPZHBeHZcNvXtziMMV9mRuvUOX8Vg9IFjRx9dUYM3s2oJyNx9ahFfSWwyRHKHG3nmL2q/mojyFVAWnEdi2Hg7OBXwUCCKr1QEtoe0+/9jI3xqIiuF2QRD0zqcwpfQnge9TVSI4tWrNe79shj98F0xDC0N4bTUVF5LPgAvJJ8dm+wcP2iJuZNdC5QAAAABJRU5ErkJggg==`,
|
||||
width: 89,
|
||||
height: 32,
|
||||
|
||||
Reference in New Issue
Block a user