qa-regulatory-gwj-app/uni_modules/sp-sign-board/components/sp-sign-board/sp-sign-board.vue

460 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="sign-page">
<view class="sign-body">
<canvas
id="signCanvas"
canvas-id="signCanvas"
class="sign-canvas"
disable-scroll
@touchstart="signCanvasStart"
@touchmove="signCanvasMove"
@touchend="signCanvasEnd"
></canvas>
<!-- #ifndef APP -->
<!--用于临时储存横屏图片的canvas容器H5和小程序需要-->
<canvas
v-if="horizontal"
id="hsignCanvas"
canvas-id="hsignCanvas"
style="position: absolute; left: -1000px; z-index: -1"
:style="{ width: canvasHeight + 'px', height: canvasWidth + 'px' }"
></canvas>
<!-- #endif -->
</view>
<!-- <view class="sign-footer" :class="[horizontal ? 'horizontal-btns' : 'vertical-btns']">-->
<!-- <view class="btn" @click="cancel">取消</view>-->
<!-- <view class="btn" @click="reset">重写</view>-->
<!-- <view class="btn" @click="confirm">确认</view>-->
<!-- </view>-->
</view>
</template>
<script>
import { pathToBase64, base64ToPath } from './index.js'
export default {
name: 'sign',
props: {
// 签字板id用于多签名场景下作为区分
sid: {
type: String,
default: 'sign-board'
},
// 背景水印图,优先级大于 bgColor
bgImg: {
type: String,
default: ''
},
// 背景纯色底色,为空则透明
bgColor: {
type: String,
default: ''
},
// 是否显示水印
showMark: {
type: Boolean,
default: true
},
// 水印内容,可多行
markText: {
type: Array,
default: () => {
return [] // ['水印1', '水印2']
}
},
// 水印样式
markStyle: {
type: Object,
default: () => {
return {
fontSize: 12, // 水印字体大小
fontFamily: 'microsoft yahei', // 水印字体
color: '#cccccc', // 水印字体颜色
rotate: 60, // 水印旋转角度
step: 2.2 // 步长部分场景下可通过调节该参数来调整水印间距建议为1.4-2.6左右
}
}
},
// 是否横屏
horizontal: {
type: Boolean,
default: false
},
// 画笔样式
penStyle: {
type: Object,
default: () => {
return {
lineWidth: 3, // 画笔线宽 建议1~5
color: '#000000' // 画笔颜色
}
}
},
// 导出图片配置
expFile: {
type: Object,
default: () => {
return {
fileType: 'png', // png/jpg (png不可压缩质量支持透明jpg可压缩质量不支持透明)
quality: 1 // 范围 0 - 1 (仅jpg支持)
}
}
},
// 取消时是否需要返回
needBack: {
type: Boolean,
default: true
}
},
data() {
return {
canvasCtx: null, // canvascanvasWidth: 0, // canvas宽度
canvasWidth: 0, // canvas宽度
canvasHeight: 0, // canvas高度
x0: 0, // 初始横坐标或上一段touchmove事件中触摸点的横坐标
y0: 0, // 初始纵坐标或上一段touchmove事件中触摸点的纵坐标
signFlag: false // 签名旗帜
}
},
computed: {},
created() {},
mounted() {
this.$nextTick(() => {
this.createCanvas()
})
},
methods: {
// 创建canvas实例
createCanvas() {
this.canvasCtx = uni.createCanvasContext('signCanvas', this)
this.canvasCtx.setLineCap('round') // 向线条的每个末端添加圆形线帽
// 获取canvas宽高
const query = uni.createSelectorQuery().in(this)
query
.select('.sign-body')
.boundingClientRect((data) => {
this.canvasWidth = data.width
this.canvasHeight = data.height
})
.exec(async () => {
await this.drawBg()
this.drawMark(this.markText)
})
},
async drawBg() {
if (this.bgImg) {
const img = await uni.getImageInfo({ src: this.bgImg })
this.canvasCtx.drawImage(img.path, 0, 0, this.canvasWidth, this.canvasHeight)
} else if (this.bgColor) {
// 绘制底色填充,否则为透明
this.canvasCtx.setFillStyle(this.bgColor)
this.canvasCtx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
}
},
// 绘制动态水印
drawMark(textArray) {
if (!this.showMark) {
this.canvasCtx.draw()
return
}
// 绘制背景
this.drawBg()
// 水印参数
const markStyle = Object.assign(
{
fontSize: 12, // 水印字体大小
fontFamily: 'microsoft yahei', // 水印字体
color: '#cccccc', // 水印字体颜色
rotate: 60, // 水印旋转角度
step: 2 // 步长部分场景下可通过调节该参数来调整水印间距建议为1.4-2.6左右
},
this.markStyle
)
this.canvasCtx.font = `${markStyle.fontSize}px ${markStyle.fontFamily}`
this.canvasCtx.fillStyle = markStyle.color
// 文字坐标
const maxPx = Math.max(this.canvasWidth / 2, this.canvasHeight / 2)
const stepPx = Math.floor(maxPx / markStyle.step)
let arrayX = [0] // 初始水印位置 canvas坐标 0 0 点
while (arrayX[arrayX.length - 1] < maxPx / 2) {
arrayX.push(arrayX[arrayX.length - 1] + stepPx)
}
arrayX.push(
...arrayX.slice(1, arrayX.length).map((item) => {
return -item
})
)
for (let i = 0; i < arrayX.length; i++) {
for (let j = 0; j < arrayX.length; j++) {
this.canvasCtx.save()
this.canvasCtx.translate(this.canvasWidth / 2, this.canvasHeight / 2) // 画布旋转原点 移到 图片中心
this.canvasCtx.rotate(Math.PI * (markStyle.rotate / 180))
textArray.forEach((item, index) => {
let offsetY = markStyle.fontSize * index
this.canvasCtx.fillText(item, arrayX[i], arrayX[j] + offsetY)
})
this.canvasCtx.restore()
}
}
this.canvasCtx.draw()
},
cancel() {
//取消按钮事件
this.$emit('cancel')
this.reset()
if (this.needBack) uni.navigateBack()
},
async reset() {
this.$emit('reset')
this.signFlag = false
this.canvasCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
await this.drawBg()
this.drawMark(this.markText)
},
async confirm() {
this.$emit('confirm')
// 确认按钮事件
if (!this.signFlag) {
uni.showToast({
title: '请签名后再点击确定',
icon: 'none',
duration: 2000
})
return
}
// uni.showModal({
// title: '确认',
// content: '确认签名无误吗',
// showCancel: true,
// success: async ({ confirm }) => {
// if (confirm) {
let tempFile
if (this.horizontal) {
tempFile = await this.saveHorizontalCanvas()
} else {
tempFile = await this.saveCanvas()
}
const base64 = await pathToBase64(tempFile)
const path = await base64ToPath(base64)
uni.$emit('getSignImg', { base64, path, sid: this.sid })
if (this.needBack) uni.navigateBack()
// }
// }
// })
},
signCanvasEnd(e) {
// 签名抬起事件
// console.log(e, 'signCanvasEnd')
this.x0 = 0
this.y0 = 0
},
signCanvasMove(e) {
// 签名滑动事件
// console.log(e, 'signCanvasMove')
let dx = e.touches[0].x
let dy = e.touches[0].y
this.canvasCtx.moveTo(this.x0, this.y0)
this.canvasCtx.lineTo(dx, dy)
this.canvasCtx.setLineWidth(this.penStyle?.lineWidth || 4)
this.canvasCtx.strokeStyle = this.penStyle?.color || '#000000' // 赋值过去
this.canvasCtx.stroke()
this.canvasCtx.draw(true)
this.x0 = e.touches[0].x
this.y0 = e.touches[0].y
},
signCanvasStart(e) {
// 签名按下事件 app获取的e不一样区分小程序app
// console.log('signCanvasStart', e)
if (!this.signFlag) {
// 第一次开始触碰事件
this.$emit('firstTouchStart')
}
this.signFlag = true
this.x0 = e.touches[0].x
this.y0 = e.touches[0].y
},
// 保存竖屏图片
async saveCanvas() {
return await new Promise((resolve, reject) => {
uni.canvasToTempFilePath(
{
canvasId: 'signCanvas',
fileType: this.expFile.fileType, // 只支持png和jpg
quality: this.expFile.quality, // 范围 0 - 1
success: (res) => {
if (!res.tempFilePath) {
uni.showModal({
title: '提示',
content: '保存签名失败',
showCancel: false
})
return
}
resolve(res.tempFilePath)
},
fail: (r) => {
console.log('图片生成失败:' , r)
resolve(false)
}
},
this
)
})
},
// 保存横屏图片
async saveHorizontalCanvas() {
return await new Promise((resolve, reject) => {
uni.canvasToTempFilePath(
{
canvasId: 'signCanvas',
fileType: this.expFile.fileType, // 只支持png和jpg
success: (res) => {
if (!res.tempFilePath) {
uni.showModal({
title: '提示',
content: '保存签名失败',
showCancel: false
})
return
}
// #ifdef APP
uni.compressImage({
src: res.tempFilePath,
quality: this.expFile.quality * 100, // 范围 0 - 100
rotate: 270,
success: (r) => {
console.log('==== compressImage :', r)
resolve(r.tempFilePath)
}
})
// #endif
// #ifndef APP
uni.getImageInfo({
src: res.tempFilePath,
success: (r) => {
// console.log('==== getImageInfo :', r)
// 将signCanvas的内容复制到hsignCanvas中
const hcanvasCtx = uni.createCanvasContext('hsignCanvas', this)
// 横屏宽高互换
hcanvasCtx.translate(this.canvasHeight / 2, this.canvasWidth / 2)
hcanvasCtx.rotate(Math.PI * (-90 / 180))
hcanvasCtx.drawImage(
r.path,
-this.canvasWidth / 2,
-this.canvasHeight / 2,
this.canvasWidth,
this.canvasHeight
)
hcanvasCtx.draw(false, async () => {
const hpathRes = await uni.canvasToTempFilePath(
{
canvasId: 'hsignCanvas',
fileType: this.expFile.fileType, // 只支持png和jpg
quality: this.expFile.quality // 范围 0 - 1
},
this
)
let tempFile = ''
if (Array.isArray(hpathRes)) {
hpathRes.some((item) => {
if (item) {
tempFile = item.tempFilePath
return
}
})
} else {
tempFile = hpathRes.tempFilePath
}
resolve(tempFile)
})
}
})
// #endif
},
fail: (err) => {
console.log('图片生成失败:' , err)
resolve(false)
}
},
this
)
})
}
}
}
</script>
<style scoped lang="scss">
.sign-page {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
.sign-body {
width: 100%;
flex-grow: 1;
.sign-canvas {
width: 100%;
height: 100%;
}
}
.sign-footer {
width: 100%;
height: 88rpx;
display: flex;
justify-content: space-evenly;
align-items: center;
border-top: 1px solid #cccccc;
box-sizing: border-box;
.btn {
line-height: 66rpx;
text-align: center;
border-radius: 12rpx;
&:nth-child(1) {
background-color: #ff0800;
color: #ffffff;
}
&:nth-child(2) {
background-color: #00d000;
color: #ffffff;
}
&:nth-child(3) {
background-color: #0184ff;
color: #ffffff;
}
}
}
.vertical-btns {
.btn {
width: 120rpx;
height: 66rpx;
}
}
.horizontal-btns {
.btn {
width: 66rpx;
height: 120rpx;
writing-mode: vertical-lr;
transform: rotate(90deg);
}
}
}
</style>