feat: add hyperlink

pr675
黄云飞 4 years ago
parent a0e12fe509
commit 34d778dcaa

@ -111,6 +111,9 @@
<i></i>
<input type="file" id="image" accept=".png, .jpg, .jpeg">
</div>
<div class="menu-item__hyperlink">
<i></i>
</div>
</div>
<div class="menu-divider"></div>
<div class="menu-item">

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.9 8.192l4.242 4.243-.707.707L8.192 8.9 3.95 13.142l-.707-.707 4.242-4.243L3.243 3.95l.707-.707 4.242 4.242 4.243-4.242.707.707L8.9 8.192z" fill="#6A6A6A" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g transform="rotate(45 5.5 13.328)" fill="#3D4757" fill-rule="evenodd"><path d="M7 6H6V3.515a2.5 2.5 0 10-5 0V6H0V3.515a3.5 3.5 0 117 0V6zm0 4v2.5a3.5 3.5 0 01-7 0V10h1v2.5a2.5 2.5 0 105 0V10h1z" fill-rule="nonzero"/><rect x="3.062" y="5.209" width="1" height="5.5" rx=".5"/></g></svg>

After

Width:  |  Height:  |  Size: 349 B

@ -0,0 +1,136 @@
import { EditorComponent, EDITOR_COMPONENT } from '../../editor'
import './dialog.css'
export interface IDialogData {
type: string;
label: string;
name: string;
value?: string;
placeholder?: string;
}
export interface IDialogConfirm {
name: string;
value: string;
}
export interface IDialogOptions {
onClose?: () => void;
onCancel?: () => void;
onConfirm?: (payload: IDialogConfirm[]) => void;
title: string;
data: IDialogData[];
}
export class Dialog {
private options: IDialogOptions
private mask: HTMLDivElement | null
private container: HTMLDivElement | null
private inputList: HTMLInputElement[]
constructor(options: IDialogOptions) {
this.options = options
this.mask = null
this.container = null
this.inputList = []
this._render()
}
private _render() {
const { title, data, onClose, onCancel, onConfirm } = this.options
// 渲染遮罩层
const mask = document.createElement('div')
mask.classList.add('dialog-mask')
mask.setAttribute(EDITOR_COMPONENT, EditorComponent.COMPONENT)
document.body.append(mask)
// 渲染容器
const container = document.createElement('div')
container.classList.add('dialog-container')
container.setAttribute(EDITOR_COMPONENT, EditorComponent.COMPONENT)
// 弹窗
const dialogContainer = document.createElement('div')
dialogContainer.classList.add('dialog')
container.append(dialogContainer)
// 标题容器
const titleContainer = document.createElement('div')
titleContainer.classList.add('dialog-title')
// 标题&关闭按钮
const titleSpan = document.createElement('span')
titleSpan.append(document.createTextNode(title))
const titleClose = document.createElement('i')
titleClose.onclick = () => {
if (onClose) {
onClose()
}
this._dispose()
}
titleContainer.append(titleSpan)
titleContainer.append(titleClose)
dialogContainer.append(titleContainer)
// 选项容器
const optionContainer = document.createElement('div')
optionContainer.classList.add('dialog-option')
// 选项
for (let i = 0; i < data.length; i++) {
const option = data[i]
const optionItemContainer = document.createElement('div')
optionItemContainer.classList.add('dialog-option__item')
// 选项名称
const optionName = document.createElement('span')
optionName.append(document.createTextNode(option.label))
optionItemContainer.append(optionName)
// 选项输入框
const optionInput = document.createElement('input')
optionInput.type = option.type
optionInput.name = option.name
optionInput.value = option.value || ''
optionInput.placeholder = option.placeholder || ''
optionItemContainer.append(optionInput)
optionContainer.append(optionItemContainer)
this.inputList.push(optionInput)
}
dialogContainer.append(optionContainer)
// 按钮容器
const menuContainer = document.createElement('div')
menuContainer.classList.add('dialog-menu')
// 取消按钮
const cancelBtn = document.createElement('button')
cancelBtn.classList.add('dialog-menu__cancel')
cancelBtn.append(document.createTextNode('取消'))
cancelBtn.type = 'default'
cancelBtn.onclick = () => {
if (onCancel) {
onCancel()
}
this._dispose()
}
menuContainer.append(cancelBtn)
// 确认按钮
const confirmBtn = document.createElement('button')
confirmBtn.append(document.createTextNode('确定'))
confirmBtn.type = 'primary'
confirmBtn.onclick = () => {
if (onConfirm) {
const payload = this.inputList.map<IDialogConfirm>(input => ({
name: input.name,
value: input.value
}))
onConfirm(payload)
}
this._dispose()
}
menuContainer.append(confirmBtn)
dialogContainer.append(menuContainer)
// 渲染
document.body.append(container)
this.container = container
this.mask = mask
}
private _dispose() {
this.mask?.remove()
this.container?.remove()
}
}

@ -0,0 +1,119 @@
.dialog-mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: .5;
background: #000000;
z-index: 99;
}
.dialog-container {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
z-index: 999;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
.dialog {
position: absolute;
padding: 0 30px 30px;
background: #ffffff;
box-shadow: 0 2px 12px 0 rgb(56 56 56 / 20%);
border: 1px solid #e2e6ed;
border-radius: 2px;
}
.dialog-title {
position: relative;
border-bottom: 1px solid #e2e6ed;
margin-bottom: 30px;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
}
.dialog-title i {
width: 16px;
height: 16px;
cursor: pointer;
display: inline-block;
background: url(../../assets/images/close.svg);
}
.dialog-option__item {
margin-bottom: 18px;
height: 30px;
display: flex;
align-items: center;
}
.dialog-option__item span {
margin-right: 12px;
font-size: 14px;
color: #3d4757;
}
.dialog-option__item input {
width: 276px;
height: 30px;
border-radius: 2px;
border: 1px solid #d3d3d3;
min-height: 30px;
line-height: 17px;
padding: 5px;
box-sizing: border-box;
outline: none;
appearance: none;
user-select: none;
}
.dialog-option__item input:focus {
border-color: #4991f2;
}
.dialog-menu {
display: flex;
align-items: center;
justify-content: flex-end;
}
.dialog-menu button {
position: relative;
display: inline-block;
border: 1px solid #e2e6ed;
border-radius: 2px;
background: #ffffff;
line-height: 22px;
padding: 0 16px;
white-space: nowrap;
cursor: pointer;
}
.dialog-menu button:hover {
background: rgba(25, 55, 88, .04);
}
.dialog-menu__cancel {
margin-right: 16px;
}
.dialog-menu button[type='primary'] {
color: #ffffff;
background: #4991f2;
border-color: #4991f2;
}
.dialog-menu button[type='primary']:hover {
background: #5b9cf3;
border-color: #5b9cf3;
}

@ -306,4 +306,28 @@
.contextmenu-delete-table {
background-image: url(../../assets/images/delete-table.svg);
}
.hyperlink-popup {
min-width: 100px;
font-size: 12px;
line-height: 20px;
background: #fff;
box-shadow: 0 2px 12px 0 rgb(98 107 132 / 20%);
border-radius: 2px;
color: #3d4757;
padding: 16px;
white-space: nowrap;
position: absolute;
text-align: center;
display: none;
}
.hyperlink-popup a {
cursor: pointer;
text-decoration: none;
border-bottom-width: 1px;
border-bottom-style: solid;
padding-bottom: 2px;
color: #0000ff;
}

@ -1,3 +1,4 @@
import { IElement } from "../.."
import { RowFlex } from "../../dataset/enum/Row"
import { IDrawImagePayload } from "../../interface/Draw"
import { CommandAdapt } from "./CommandAdapt"
@ -34,6 +35,7 @@ export class Command {
private static deleteTableCol: Function
private static deleteTable: Function
private static image: Function
private static hyperlink: Function
private static search: Function
private static print: Function
private static pageScaleRecovery: Function
@ -71,6 +73,7 @@ export class Command {
Command.deleteTableCol = adapt.deleteTableCol.bind(adapt)
Command.deleteTable = adapt.deleteTable.bind(adapt)
Command.image = adapt.image.bind(adapt)
Command.hyperlink = adapt.hyperlink.bind(adapt)
Command.search = adapt.search.bind(adapt)
Command.print = adapt.print.bind(adapt)
Command.pageScaleRecovery = adapt.pageScaleRecovery.bind(adapt)
@ -165,7 +168,7 @@ export class Command {
return Command.rowMargin(payload)
}
// 表格、图片上传、搜索、打印
// 表格、图片上传、超链接、搜索、打印
public executeInsertTable(row: number, col: number) {
return Command.insertTable(row, col)
}
@ -198,6 +201,10 @@ export class Command {
return Command.deleteTable()
}
public executeHyperlink(payload: IElement) {
return Command.hyperlink(payload)
}
public executeImage(payload: IDrawImagePayload) {
return Command.image(payload)
}

@ -654,6 +654,30 @@ export class CommandAdapt {
}
}
public hyperlink(payload: IElement) {
const { startIndex, endIndex } = this.range.getRange()
if (startIndex === 0 && endIndex === 0) return
const elementList = this.draw.getElementList()
const { valueList, url } = payload
const hyperlinkId = getUUID()
const newElementList = valueList?.map<IElement>(v => ({
url,
hyperlinkId,
value: v.value,
type: ElementType.HYPERLINK
}))
if (!newElementList) return
const start = startIndex + 1
if (startIndex === endIndex) {
elementList.splice(start, 0, ...newElementList)
} else {
elementList.splice(start, endIndex - startIndex, ...newElementList)
}
const curIndex = start + newElementList.length - 1
this.range.setRange(curIndex, curIndex)
this.draw.render({ curIndex })
}
public image(payload: IDrawImagePayload) {
const { startIndex, endIndex } = this.range.getRange()
if (startIndex === 0 && endIndex === 0) return
@ -706,7 +730,8 @@ export class CommandAdapt {
// 搜索文本
function searchClosure(payload: string | null, type: EditorContext, elementList: IElement[], restArgs?: ISearchResultRestArgs) {
if (!payload) return
const text = elementList.map(e => !e.type || e.type === ElementType.TEXT ? e.value : ZERO)
const { TEXT, HYPERLINK } = ElementType
const text = elementList.map(e => !e.type || e.type === TEXT || e.type === HYPERLINK ? e.value : ZERO)
.filter(Boolean)
.join('')
const matchStartIndexList = []

@ -26,6 +26,7 @@ import { GlobalObserver } from "../observer/GlobalObserver"
import { TableParticle } from "./particle/table/TableParticle"
import { ISearchResult } from "../../interface/Search"
import { TableTool } from "./particle/table/TableTool"
import { HyperlinkParticle } from "./particle/HyperlinkParticle"
export class Draw {
@ -54,6 +55,7 @@ export class Draw {
private tableParticle: TableParticle
private tableTool: TableTool
private pageNumber: PageNumber
private hyperlinkParticle: HyperlinkParticle
private rowList: IRow[]
private painterStyle: IElementStyle | null
@ -92,6 +94,7 @@ export class Draw {
this.tableParticle = new TableParticle(this)
this.tableTool = new TableTool(this)
this.pageNumber = new PageNumber(this)
this.hyperlinkParticle = new HyperlinkParticle(this)
new GlobalObserver(this)
this.canvasEvent = new CanvasEvent(this)
@ -251,6 +254,10 @@ export class Draw {
return this.tableTool
}
public getHyperlinkParticle(): HyperlinkParticle {
return this.hyperlinkParticle
}
public getRowCount(): number {
return this.rowList.length
}
@ -431,11 +438,10 @@ export class Draw {
const ascent = metrics.boundingBoxAscent + rowMargin
const descent = metrics.boundingBoxDescent + rowMargin
const height = ascent + descent
const rowElement: IRowElement = {
...element,
const rowElement: IRowElement = Object.assign(element, {
metrics,
style: this._getFont(element, scale)
}
})
// 超过限定宽度
const preElement = elementList[i - 1]
if (
@ -509,9 +515,21 @@ export class Draw {
}
}
positionList.push(positionItem)
// 元素绘制
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 if (element.type === ElementType.HYPERLINK) {
this.textParticle.complete()
this.hyperlinkParticle.render(ctx, element, x, y + offsetY)
} else {
this.textParticle.record(ctx, element, x, y + offsetY)
}
// 下划线绘制
if (element.underline) {
this.underline.render(ctx, x, y + curRow.height, metrics.width)
this.underline.render(ctx, element.color!, x, y + curRow.height, metrics.width)
}
// 删除线绘制
if (element.strikeout) {
@ -521,15 +539,6 @@ export class Draw {
if (element.highlight) {
this.highlight.render(ctx, element.highlight, x, y, metrics.width, curRow.height)
}
// 元素绘制
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) {

@ -0,0 +1,67 @@
import { IElement } from "../../.."
import { IEditorOption } from "../../../interface/Editor"
import { IElementPosition } from "../../../interface/Element"
import { IRowElement } from "../../../interface/Row"
import { Draw } from "../Draw"
export class HyperlinkParticle {
private draw: Draw
private options: Required<IEditorOption>
private container: HTMLDivElement
private hyperlinkPopupContainer: HTMLDivElement
private hyperlinkDom: HTMLAnchorElement
constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions()
this.container = draw.getContainer()
const { hyperlinkPopupContainer, hyperlinkDom } = this._createHyperlinkPopupDom()
this.hyperlinkDom = hyperlinkDom
this.hyperlinkPopupContainer = hyperlinkPopupContainer
}
private _createHyperlinkPopupDom() {
const hyperlinkPopupContainer = document.createElement('div')
hyperlinkPopupContainer.classList.add('hyperlink-popup')
const hyperlinkDom = document.createElement('a')
hyperlinkDom.target = '_blank'
hyperlinkPopupContainer.append(hyperlinkDom)
this.container.append(hyperlinkPopupContainer)
return { hyperlinkPopupContainer, hyperlinkDom }
}
public drawHyperlinkPopup(element: IElement, position: IElementPosition) {
const { coordinate: { leftTop: [left, top] }, lineHeight } = position
const height = this.draw.getHeight()
const pageGap = this.draw.getPageGap()
const preY = this.draw.getPageNo() * (height + pageGap)
// 位置
this.hyperlinkPopupContainer.style.display = 'block'
this.hyperlinkPopupContainer.style.left = `${left}px`
this.hyperlinkPopupContainer.style.top = `${top + preY + lineHeight}px`
// 标签
const url = element.url || '#'
this.hyperlinkDom.href = url
this.hyperlinkDom.innerText = url
}
public clearHyperlinkPopup() {
this.hyperlinkPopupContainer.style.display = 'none'
}
public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) {
ctx.save()
ctx.font = element.style
if (!element.color) {
element.color = this.options.defaultHyperlinkColor
}
ctx.fillStyle = element.color
if (element.underline === undefined) {
element.underline = true
}
ctx.fillText(element.value, x, y)
ctx.restore()
}
}

@ -9,10 +9,10 @@ export class Underline {
this.options = draw.getOptions()
}
public render(ctx: CanvasRenderingContext2D, x: number, y: number, width: number) {
public render(ctx: CanvasRenderingContext2D, color: string, x: number, y: number, width: number) {
const { underlineColor } = this.options
ctx.save()
ctx.strokeStyle = underlineColor
ctx.strokeStyle = color || underlineColor
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + width, y)

@ -1,3 +1,4 @@
import { ElementType } from "../.."
import { ZERO } from "../../dataset/constant/Common"
import { ElementStyleKey } from "../../dataset/enum/ElementStyle"
import { MouseEventButton } from "../../dataset/enum/Event"
@ -6,6 +7,7 @@ import { IElement } from "../../interface/Element"
import { writeTextByElementList } from "../../utils/clipboard"
import { Cursor } from "../cursor/Cursor"
import { Draw } from "../draw/Draw"
import { HyperlinkParticle } from "../draw/particle/HyperlinkParticle"
import { ImageParticle } from "../draw/particle/ImageParticle"
import { TableTool } from "../draw/particle/table/TableTool"
import { HistoryManager } from "../history/HistoryManager"
@ -27,6 +29,7 @@ export class CanvasEvent {
private historyManager: HistoryManager
private imageParticle: ImageParticle
private tableTool: TableTool
private hyperlinkParticle: HyperlinkParticle
constructor(draw: Draw) {
this.isAllowDrag = false
@ -42,6 +45,7 @@ export class CanvasEvent {
this.historyManager = this.draw.getHistoryManager()
this.imageParticle = this.draw.getImageParticle()
this.tableTool = this.draw.getTableTool()
this.hyperlinkParticle = this.draw.getHyperlinkParticle()
}
public register() {
@ -160,20 +164,26 @@ export class CanvasEvent {
isComputeRowList: false
})
}
const elementList = this.draw.getElementList()
const positionList = this.position.getPositionList()
const curIndex = isTable ? tdValueIndex! : index
const curElement = elementList[curIndex]
// 图片尺寸拖拽组件
this.imageParticle.clearResizer()
if (isDirectHitImage) {
const elementList = this.draw.getElementList()
const positionList = this.position.getPositionList()
const curIndex = isTable ? tdValueIndex! : index
this.imageParticle.drawResizer(elementList[curIndex], positionList[curIndex])
this.imageParticle.drawResizer(curElement, positionList[curIndex])
}
// 表格工具组件
this.tableTool.dispose()
if (isTable) {
const elementList = this.draw.getOriginalElementList()
const positionList = this.position.getOriginalPositionList()
this.tableTool.render(elementList[index], positionList[index])
const originalElementList = this.draw.getOriginalElementList()
const originalPositionList = this.position.getOriginalPositionList()
this.tableTool.render(originalElementList[index], originalPositionList[index])
}
// 超链接
this.hyperlinkParticle.clearHyperlinkPopup()
if (curElement.type === ElementType.HYPERLINK) {
this.hyperlinkParticle.drawHyperlinkPopup(curElement, positionList[curIndex])
}
}
@ -315,6 +325,7 @@ export class CanvasEvent {
if (!this.cursor) return
const cursorPosition = this.position.getCursorPosition()
if (!data || !cursorPosition || this.isCompositing) return
const { TEXT, HYPERLINK } = ElementType
const text = data.replaceAll(`\n`, ZERO)
const elementList = this.draw.getElementList()
const agentDom = this.cursor.getAgentDom()
@ -330,18 +341,32 @@ export class CanvasEvent {
restArg = { tdId, trId, tableId }
}
const element = elementList[endIndex]
const inputData: IElement[] = text.split('').map(value => ({
value,
font: element.font,
size: element.size,
bold: element.bold,
color: element.color,
highlight: element.highlight,
italic: element.italic,
underline: element.underline,
strikeout: element.strikeout,
...restArg
}))
const inputData: IElement[] = text.split('').map(value => {
const newElement: IElement = {
value,
...restArg
}
if (
!element.type
|| element.type === TEXT
|| (element.type === HYPERLINK && elementList[endIndex + 1]?.type === HYPERLINK)
) {
if (element.type) {
newElement.type = element.type
}
newElement.font = element.font
newElement.size = element.size
newElement.bold = element.bold
newElement.color = element.color
newElement.highlight = element.highlight
newElement.italic = element.italic
newElement.underline = element.underline
newElement.strikeout = element.strikeout
newElement.url = element.url
newElement.hyperlinkId = element.hyperlinkId
}
return newElement
})
let start = 0
if (isCollspace) {
start = index + 1

@ -3,6 +3,7 @@ import { IEditorOption } from "../../interface/Editor"
import { findParent } from "../../utils"
import { Cursor } from "../cursor/Cursor"
import { Draw } from "../draw/Draw"
import { HyperlinkParticle } from "../draw/particle/HyperlinkParticle"
import { ImageParticle } from "../draw/particle/ImageParticle"
import { TableTool } from "../draw/particle/table/TableTool"
import { RangeManager } from "../range/RangeManager"
@ -18,6 +19,7 @@ export class GlobalEvent {
private range: RangeManager
private imageParticle: ImageParticle
private tableTool: TableTool
private hyperlinkParticle: HyperlinkParticle
constructor(draw: Draw, canvasEvent: CanvasEvent) {
this.draw = draw
@ -28,6 +30,7 @@ export class GlobalEvent {
this.range = draw.getRange()
this.imageParticle = draw.getImageParticle()
this.tableTool = draw.getTableTool()
this.hyperlinkParticle = draw.getHyperlinkParticle()
}
public register() {
@ -70,6 +73,7 @@ export class GlobalEvent {
this.range.setRange(0, 0)
this.imageParticle.clearResizer()
this.tableTool.dispose()
this.hyperlinkParticle.clearHyperlinkPopup()
}
public setDragState() {

@ -1,4 +1,5 @@
export enum EditorComponent {
COMPONENT = 'component',
MENU = 'menu',
MAIN = 'main',
FOOTER = 'footer',

@ -1,5 +1,6 @@
export enum ElementType {
TEXT = 'text',
IMAGE = 'image',
TABLE = 'table'
TABLE = 'table',
HYPERLINK = 'hyperlink'
}

@ -13,6 +13,8 @@ import { globalMenus } from './core/contextmenu/menus/GlobalMenus'
import { ContextMenu } from './core/contextmenu/ContextMenu'
import { tableMenus } from './core/contextmenu/menus/tableMenus'
import { IRegisterContextMenu } from './interface/contextmenu/ContextMenu'
import { EditorComponent } from './dataset/enum/Editor'
import { EDITOR_COMPONENT } from './dataset/constant/Editor'
export default class Editor {
@ -49,6 +51,7 @@ export default class Editor {
margins: [100, 120, 100, 120],
tdPadding: 5,
defaultTdHeight: 40,
defaultHyperlinkColor: '#0000FF',
...options
}
formatElementList(elementList)
@ -74,7 +77,9 @@ export default class Editor {
export {
Editor,
RowFlex,
ElementType
ElementType,
EditorComponent,
EDITOR_COMPONENT
}
// 对外类型

@ -26,4 +26,5 @@ export interface IEditorOption {
margins?: [top: number, right: number, bootom: number, left: number],
tdPadding?: number;
defaultTdHeight?: number;
defaultHyperlinkColor?: string;
}

@ -35,9 +35,15 @@ export interface ITableElement {
tableId?: string;
}
export interface IHyperlinkElement {
valueList?: IElement[];
url?: string;
hyperlinkId?: string;
}
export type ITable = ITableAttr & ITableElement
export type IElement = IElementBasic & IElementStyle & ITable
export type IElement = IElementBasic & IElementStyle & ITable & IHyperlinkElement
export interface IElementMetrics {
width: number;

@ -7,8 +7,9 @@ export function writeText(text: string) {
}
export function writeTextByElementList(elementList: IElement[]) {
const { TEXT, HYPERLINK } = ElementType
const text = elementList
.map(p => !p.type || p.type === ElementType.TEXT ? p.value : '')
.map(p => !p.type || p.type === TEXT || p.type === HYPERLINK ? p.value : '')
.join('')
writeText(text)
}

@ -8,7 +8,8 @@ export function formatElementList(elementList: IElement[]) {
value: ZERO
})
}
for (let i = 0; i < elementList.length; i++) {
let i = 0
while (i < elementList.length) {
const el = elementList[i]
if (el.type === ElementType.TABLE) {
const tableId = getUUID()
@ -32,6 +33,23 @@ export function formatElementList(elementList: IElement[]) {
}
}
}
} else if (el.type === ElementType.HYPERLINK) {
const valueList = el.valueList || []
// 移除父节点
elementList.splice(i, 1)
// 追加字节点
if (valueList.length) {
const hyperlinkId = getUUID()
for (let v = 0; v < valueList.length; v++) {
const value = valueList[v]
value.type = el.type
value.url = el.url
value.hyperlinkId = hyperlinkId
elementList.splice(i, 0, value)
i++
}
}
i--
}
if (el.value === '\n') {
el.value = ZERO
@ -39,5 +57,6 @@ export function formatElementList(elementList: IElement[]) {
if (el.type === ElementType.IMAGE) {
el.id = getUUID()
}
i++
}
}

@ -1,9 +1,10 @@
import './style.css'
import Editor, { ElementType, IElement, RowFlex } from './editor'
import { Dialog } from './components/dialog/Dialog'
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体格检查\nT36.5℃P80bpmR20次/分BP120/80mmHg\n辅助检查\n2020年6月10日普放血细胞比容36.50%偏低4050单核细胞绝对值0.75*10^9/L偏高参考值0.10.6\n门诊诊断\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名【】\n其他记录`
const text = `人民医院门诊病历\n主诉\n发热三天咳嗽五天。\n现病史\n患者于三天前无明显诱因感冒后发现面部水肿无皮疹尿量减少出现乏力在外治疗无好转现来我院就诊。\n既往史\n有糖尿病10年有高血压2年有传染性疾病1年。没有报告其他既往疾病。\n流行病史\n否认14天内接触过确诊患者、疑似患者、无症状感染者及其密切接触者否认14天内去过以下场所水产、肉类批发市场农贸市场集市大型超市夜市否认14天内与以下场所工作人员密切接触水产、肉类批发市场农贸市场集市大型超市否认14天内周围如家庭、办公室有2例以上聚集性发病否认14天内接触过有发热或呼吸道症状的人员否认14天内自身有发热或呼吸道症状否认14天内接触过纳入隔离观察的人员及其他可能与新冠肺炎关联的情形陪同家属{有无选择代码}有以上情况。\n体格检查\nT36.5℃P80bpmR20次/分BP120/80mmHg\n辅助检查\n2020年6月10日普放血细胞比容36.50%偏低4050单核细胞绝对值0.75*10^9/L偏高参考值0.10.6\n门诊诊断\n1.高血压\n2.糖尿病\n3.病毒性感冒\n4.过敏性鼻炎\n5.过敏性鼻息肉\n处置治疗\n1.超声引导下甲状腺细针穿刺术;\n2.乙型肝炎表面抗体测定;\n3.膜式病变细胞采集术、后颈皮下肤层;\n电子签名【】\n其他记录`
// 模拟行居中
const centerText = ['人民医院门诊病历']
const centerIndex: number[] = centerText.map(c => {
@ -62,7 +63,25 @@ window.onload = function () {
size: 16
}
})
data.splice(595, 0, {
data.splice(137, 0, {
type: ElementType.HYPERLINK,
value: '',
valueList: [{
value: '新',
size: 16
}, {
value: '冠',
size: 16
}, {
value: '肺',
size: 16
}, {
value: '炎',
size: 16
}],
url: 'https://hufe.club/canvas-editor'
})
data.splice(592, 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,
@ -386,6 +405,39 @@ window.onload = function () {
}
}
}
const hyperlinkDom = document.querySelector<HTMLDivElement>('.menu-item__hyperlink')!
hyperlinkDom.onclick = function () {
console.log('hyperlink')
new Dialog({
title: '超链接',
data: [{
type: 'text',
label: '文本',
name: 'name',
placeholder: '请输入文本'
}, {
type: 'text',
label: '链接',
name: 'url',
placeholder: '请输入链接'
}],
onConfirm: (payload) => {
const name = payload.find(p => p.name === 'name')?.value
if (!name) return
const url = payload.find(p => p.name === 'url')?.value
if (!url) return
instance.command.executeHyperlink({
type: ElementType.HYPERLINK,
value: '',
url,
valueList: name.split('').map(n => ({
value: n,
size: 16
}))
})
}
})
}
const collspanDom = document.querySelector<HTMLDivElement>('.menu-item__search__collapse')
const searchInputDom = document.querySelector<HTMLInputElement>('.menu-item__search__collapse__search input')
document.querySelector<HTMLDivElement>('.menu-item__search')!.onclick = function () {

@ -357,6 +357,10 @@ ul {
margin-right: 0;
}
.menu-item__hyperlink i {
background-image: url('./assets/images/hyperlink.svg');
}
.menu-item__search {
position: relative;
}

Loading…
Cancel
Save