feat:add signature component

pr675
黄云飞 4 years ago
parent 75225e7180
commit a9d6601207

@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#3D4757" d="M2 13h12v1H2z"/><path fill="#4B5463" fill-rule="nonzero" d="M5.26 8.458l2.23 1.28L6 12H3z"/><path d="M5.666 6.149L4.31 8.168l4.045 2.48 1.503-2.159-4.192-2.34zm1.106-4.156l-1.986 3.44 6.3 3.648 2.037-2.943-6.351-4.145z" stroke="#4B5463"/></g></svg>

After

Width:  |  Height:  |  Size: 370 B

@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="#3d4757" fill-rule="evenodd"><path d="M17 3v4h4v2h-2v13H6V9H4V7h4V3h9zm0 6H8v11h9V9zm-2-4h-5v2h5V5z" fill-rule="nonzero"/><path d="M10 10h2v7h-2zm3 0h2v7h-2z"/></g></svg>

After

Width:  |  Height:  |  Size: 242 B

@ -0,0 +1,187 @@
import { EditorComponent, EDITOR_COMPONENT } from '../../editor'
import './signature.css'
export interface ISignatureConfirm {
value: string;
width: number;
height: number;
}
export interface ISignatureOptions {
width?: number;
height?: number;
onClose?: () => void;
onCancel?: () => void;
onConfirm?: (payload: ISignatureConfirm) => void;
}
export class Signature {
private x: number
private y: number
private isDrawing: boolean
private canvasWidth: number
private canvasHeight: number
private options: ISignatureOptions
private mask: HTMLDivElement
private container: HTMLDivElement
private trashContainer: HTMLDivElement
private canvas: HTMLCanvasElement
private ctx: CanvasRenderingContext2D
constructor(options: ISignatureOptions) {
this.x = 0
this.y = 0
this.isDrawing = false
this.options = options
this.canvasWidth = options.width || 390
this.canvasHeight = options.height || 180
const { mask, container, trashContainer, canvas } = this._render()
this.mask = mask
this.container = container
this.trashContainer = trashContainer
this.canvas = canvas
this.ctx = <CanvasRenderingContext2D>canvas.getContext('2d')
this._bindEvent()
}
private _render() {
const { onClose, onCancel, onConfirm } = this.options
// 渲染遮罩层
const mask = document.createElement('div')
mask.classList.add('signature-mask')
mask.setAttribute(EDITOR_COMPONENT, EditorComponent.COMPONENT)
document.body.append(mask)
// 渲染容器
const container = document.createElement('div')
container.classList.add('signature-container')
container.setAttribute(EDITOR_COMPONENT, EditorComponent.COMPONENT)
// 弹窗
const signatureContainer = document.createElement('div')
signatureContainer.classList.add('signature')
container.append(signatureContainer)
// 标题容器
const titleContainer = document.createElement('div')
titleContainer.classList.add('signature-title')
// 标题&关闭按钮
const titleSpan = document.createElement('span')
titleSpan.append(document.createTextNode('插入签名'))
const titleClose = document.createElement('i')
titleClose.onclick = () => {
if (onClose) {
onClose()
}
this._dispose()
}
titleContainer.append(titleSpan)
titleContainer.append(titleClose)
signatureContainer.append(titleContainer)
// 操作区
const operationContainer = document.createElement('div')
operationContainer.classList.add('signature-operation')
const trashContainer = document.createElement('div')
trashContainer.classList.add('signature-operation__trash')
const trashIcon = document.createElement('i')
const trashLabel = document.createElement('span')
trashLabel.innerText = '清空画布'
trashContainer.append(trashIcon)
trashContainer.append(trashLabel)
operationContainer.append(trashContainer)
signatureContainer.append(operationContainer)
// 绘图区
const canvasContainer = document.createElement('div')
canvasContainer.classList.add('signature-canvas')
const canvas = document.createElement('canvas')
canvas.width = this.canvasWidth
canvas.height = this.canvasHeight
canvas.style.width = `${this.canvasWidth}px`
canvas.style.height = `${this.canvasHeight}px`
canvasContainer.append(canvas)
signatureContainer.append(canvasContainer)
// 按钮容器
const menuContainer = document.createElement('div')
menuContainer.classList.add('signature-menu')
// 取消按钮
const cancelBtn = document.createElement('button')
cancelBtn.classList.add('signature-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) {
onConfirm({
width: this.canvasWidth,
height: this.canvasHeight,
value: this._toDataURL()
})
}
this._dispose()
}
menuContainer.append(confirmBtn)
signatureContainer.append(menuContainer)
// 渲染
document.body.append(container)
this.container = container
this.mask = mask
return {
mask,
canvas,
container,
trashContainer
}
}
private _bindEvent() {
this.trashContainer.onclick = this._clearCanvas.bind(this)
this.canvas.onmousedown = this._startDraw.bind(this)
this.canvas.onmousemove = this._draw.bind(this)
this.container.onmouseup = this._stopDraw.bind(this)
}
private _clearCanvas() {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
}
private _startDraw(evt: MouseEvent) {
this.isDrawing = true
this.x = evt.offsetX
this.y = evt.offsetY
this.ctx.lineWidth = 5
this.ctx.lineCap = 'butt'
this.ctx.lineJoin = 'round'
}
private _draw(evt: MouseEvent) {
if (!this.isDrawing) return
const { offsetX, offsetY } = evt
this.ctx.beginPath()
this.ctx.moveTo(this.x, this.y)
this.ctx.lineTo(offsetX, offsetY)
this.ctx.stroke()
this.x = offsetX
this.y = offsetY
}
private _stopDraw() {
this.isDrawing = false
}
private _toDataURL() {
return this.canvas.toDataURL()
}
private _dispose() {
this.mask.remove()
this.container.remove()
}
}

@ -0,0 +1,122 @@
.signature-mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: .5;
background: #000000;
z-index: 99;
}
.signature-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;
}
.signature {
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;
}
.signature-title {
position: relative;
border-bottom: 1px solid #e2e6ed;
margin-bottom: 15px;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
}
.signature-title i {
width: 16px;
height: 16px;
cursor: pointer;
display: inline-block;
background: url(../../assets/images/close.svg);
}
.signature-operation__trash {
cursor: pointer;
display: inline-flex;
align-items: center;
color: #3d4757;
user-select: none;
}
.signature-operation__trash:hover {
color: #6e7175;
}
.signature-operation__trash i {
width: 24px;
height: 24px;
display: inline-block;
background: url(../../assets/images/trash.svg) no-repeat;
}
.signature-operation__trash span {
font-size: 12px;
margin-left: 5px;
}
.signature-canvas {
margin: 15px 0;
border: 1px solid #e9e9e9;
user-select: none;
}
.signature-canvas canvas {
background: #fbfbfb;
}
.signature-menu {
display: flex;
align-items: center;
justify-content: flex-end;
}
.signature-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;
}
.signature-menu button:hover {
background: rgba(25, 55, 88, .04);
}
.signature-menu__cancel {
margin-right: 16px;
}
.signature-menu button[type='primary'] {
color: #ffffff;
background: #4991f2;
border-color: #4991f2;
}
.signature-menu button[type='primary']:hover {
background: #5b9cf3;
border-color: #5b9cf3;
}

@ -116,7 +116,8 @@ export {
EditorComponent,
EDITOR_COMPONENT,
PageMode,
ImageDisplay
ImageDisplay,
Command
}
// 对外类型

@ -1,9 +1,10 @@
import { data, options } from './mock'
import './style.css'
import prism from 'prismjs'
import Editor, { ControlType, EditorMode, ElementType, IElement, PageMode } from './editor'
import Editor, { Command, ControlType, EditorMode, ElementType, IElement, PageMode } from './editor'
import { Dialog } from './components/dialog/Dialog'
import { formatPrismToken } from './utils/prism'
import { Signature } from './components/signature/Signature'
window.onload = function () {
@ -865,4 +866,29 @@ window.onload = function () {
console.log('elementList: ', payload)
}
// 9. 右键菜单注册
instance.register.contextMenuList([
{
name: '签名',
icon: 'signature',
when: (payload) => {
return !payload.isReadonly && payload.editorTextFocus
},
callback: (command: Command) => {
new Signature({
onConfirm(payload) {
const { value, width, height } = payload
if (!value || !width || !height) return
command.executeInsertElementList([{
value,
width,
height,
type: ElementType.IMAGE
}])
}
})
}
}
])
}

@ -699,4 +699,8 @@ ul {
.footer .paper-size .options {
right: 0;
left: unset;
}
.contextmenu-signature {
background-image: url('./assets/images/signature.svg');
}
Loading…
Cancel
Save