parent
a0e12fe509
commit
34d778dcaa
|
After Width: | Height: | Size: 276 B |
|
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;
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
export enum ElementType {
|
export enum ElementType {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
IMAGE = 'image',
|
IMAGE = 'image',
|
||||||
TABLE = 'table'
|
TABLE = 'table',
|
||||||
|
HYPERLINK = 'hyperlink'
|
||||||
}
|
}
|
||||||
Loading…
Reference in new issue