commit
5fc7758a22
|
After Width: | Height: | Size: 370 B |
|
After Width: | Height: | Size: 242 B |
@ -0,0 +1,186 @@
|
|||||||
|
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.ctx.lineCap = 'round'
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loading…
Reference in new issue