|
@@ -1,204 +1,281 @@
|
|
|
<template>
|
|
|
- <div class="ml_sign">
|
|
|
- <canvas ref="signature" id="signature"></canvas>
|
|
|
- <div class="btn-wrapper">
|
|
|
- <button @click="clear">取消</button>
|
|
|
- <button @click="save">保存</button>
|
|
|
+ <div class="ml_sign-all">
|
|
|
+ <div class="ml_sign-form">
|
|
|
+ <el-form :model="signinfo" :rules="signrules" ref="signinfo" label-width="80px" class="demo-ruleForm">
|
|
|
+ <el-form-item label="团队:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="人数:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="领队:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="导游:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="行程:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="天数:" prop="tname">
|
|
|
+ <el-input size="small" v-model="signinfo.tname"></el-input>
|
|
|
+ </el-form-item> -->
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ <div class="ml_sign">
|
|
|
+ <canvas ref="signature" id="signature"></canvas>
|
|
|
+ <div class="btn-wrapper">
|
|
|
+ <button @click="clear">取消</button>
|
|
|
+ <button @click="save">保存</button>
|
|
|
+ </div>
|
|
|
+ <img :src="imgurl">
|
|
|
</div>
|
|
|
- <img :src="imgurl">
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
- <script>
|
|
|
- export default {
|
|
|
+<script>
|
|
|
+export default {
|
|
|
name: 'Signatrue',
|
|
|
- data () {
|
|
|
- return {
|
|
|
- canvas: null, // 存储canvas节点
|
|
|
- ctx: null, // 存储canvas的context上下文
|
|
|
- config: {
|
|
|
- width: 400, // 宽度
|
|
|
- height: 200, // 高度
|
|
|
- strokeStyle: 'red', // 线条颜色
|
|
|
- lineWidth: 4, // 线条宽度
|
|
|
- lineCap: 'round', // 设置线条两端圆角
|
|
|
- lineJoin: 'round' // 线条交汇处圆角
|
|
|
- },
|
|
|
- points: [], // 记录坐标 用来判断是否有签名的
|
|
|
- client: {
|
|
|
- offsetX: 0, // 偏移量
|
|
|
- offsetY: 0,
|
|
|
- endX: 0, // 坐标
|
|
|
- endY: 0
|
|
|
- },
|
|
|
- imgurl: ''
|
|
|
- }
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ signinfo:{
|
|
|
+ tname:'',
|
|
|
+ },
|
|
|
+ signrules:{
|
|
|
+ tname: [
|
|
|
+ { required: true, message: '请输入活动名称', trigger: 'blur' },
|
|
|
+ { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ canvas: null, // 存储canvas节点
|
|
|
+ ctx: null, // 存储canvas的context上下文
|
|
|
+ config: {
|
|
|
+ width: 350, // 宽度
|
|
|
+ height: 200, // 高度
|
|
|
+ strokeStyle: 'red', // 线条颜色
|
|
|
+ lineWidth: 4, // 线条宽度
|
|
|
+ lineCap: 'round', // 设置线条两端圆角
|
|
|
+ lineJoin: 'round' // 线条交汇处圆角
|
|
|
+ },
|
|
|
+ points: [], // 记录坐标 用来判断是否有签名的
|
|
|
+ client: {
|
|
|
+ offsetX: 0, // 偏移量
|
|
|
+ offsetY: 0,
|
|
|
+ endX: 0, // 坐标
|
|
|
+ endY: 0
|
|
|
+ },
|
|
|
+ imgurl: ''
|
|
|
+ }
|
|
|
},
|
|
|
computed: {
|
|
|
- // 判断是否为移动端
|
|
|
- mobileStatus () {
|
|
|
- return (/Mobile|Android|iPhone/i.test(navigator.userAgent))
|
|
|
- }
|
|
|
+ // 判断是否为移动端
|
|
|
+ mobileStatus() {
|
|
|
+ return (/Mobile|Android|iPhone/i.test(navigator.userAgent))
|
|
|
+ }
|
|
|
},
|
|
|
- mounted () {
|
|
|
- this.init()
|
|
|
+ mounted() {
|
|
|
+ this.init()
|
|
|
},
|
|
|
methods: {
|
|
|
- // 初始化
|
|
|
- init () {
|
|
|
- const canvas = this.$refs.signature
|
|
|
- canvas.width = this.config.width // 设置canvas的宽
|
|
|
- canvas.height = this.config.height // 设置canvas的高
|
|
|
- // 设置一个边框
|
|
|
- canvas.style.border = '1px solid #000'
|
|
|
-
|
|
|
- // 存储canvas节点
|
|
|
- this.canvas = canvas
|
|
|
- // 创建context对象
|
|
|
- this.ctx = canvas.getContext('2d')
|
|
|
-
|
|
|
- // 设置相应配置
|
|
|
- this.ctx.fillStyle = 'transparent'
|
|
|
- this.ctx.lineWidth = this.config.lineWidth
|
|
|
- this.ctx.strokeStyle = this.config.strokeStyle
|
|
|
- this.ctx.lineCap = this.config.lineCap
|
|
|
- this.ctx.lineJoin = this.config.lineJoin
|
|
|
-
|
|
|
- // 绘制填充矩形
|
|
|
- this.ctx.fillRect(
|
|
|
- 0, // x 轴起始绘制位置
|
|
|
- 0, // y 轴起始绘制位置
|
|
|
- this.config.width, // 宽度
|
|
|
- this.config.height // 高度
|
|
|
- )
|
|
|
-
|
|
|
- // 创建鼠标/手势按下监听器
|
|
|
- canvas.addEventListener(this.mobileStatus ? 'touchstart' : 'mousedown', this.startDraw)
|
|
|
- // 创建鼠标/手势 弹起/离开 监听器
|
|
|
- canvas.addEventListener(this.mobileStatus ? 'touchend' : 'mouseup', this.cloaseDraw)
|
|
|
- },
|
|
|
- // 开始绘制
|
|
|
- startDraw (event) {
|
|
|
- // 获取偏移量及坐标
|
|
|
- const { offsetX, offsetY, pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
|
|
|
-
|
|
|
- // 修改上次的偏移量及坐标
|
|
|
- this.client.offsetX = offsetX
|
|
|
- this.client.offsetY = offsetY
|
|
|
- this.client.endX = pageX
|
|
|
- this.client.endY = pageY
|
|
|
-
|
|
|
- // 清除以上一次 beginPath 之后的所有路径,进行绘制
|
|
|
- this.ctx.beginPath()
|
|
|
- // 设置画线起始点位
|
|
|
- this.ctx.moveTo(this.client.endX, this.client.endY)
|
|
|
- // 监听 鼠标移动或手势移动
|
|
|
- this.canvas.addEventListener(this.mobileStatus ? 'touchmove' : 'mousemove', this.draw)
|
|
|
- },
|
|
|
- // 绘制
|
|
|
- draw (event) {
|
|
|
- // 获取当前坐标点位
|
|
|
- const { pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
|
|
|
- // 修改最后一次绘制的坐标点
|
|
|
- this.client.endX = pageX
|
|
|
- this.client.endY = pageY
|
|
|
- const obj = {
|
|
|
- x: pageX,
|
|
|
- y: pageY
|
|
|
- }
|
|
|
-
|
|
|
- // 根据坐标点位移动添加线条
|
|
|
- this.ctx.lineTo(pageX, pageY)
|
|
|
-
|
|
|
+ // 初始化
|
|
|
+ init() {
|
|
|
+ const canvas = this.$refs.signature
|
|
|
+ canvas.width = this.config.width // 设置canvas的宽
|
|
|
+ canvas.height = this.config.height // 设置canvas的高
|
|
|
+ // 设置一个边框
|
|
|
+ canvas.style.border = '1px solid #000'
|
|
|
+
|
|
|
+ // 存储canvas节点
|
|
|
+ this.canvas = canvas
|
|
|
+ // 创建context对象
|
|
|
+ this.ctx = canvas.getContext('2d')
|
|
|
+ console.log(this.ctx);
|
|
|
+
|
|
|
+ // 设置相应配置
|
|
|
+ this.ctx.fillStyle = 'transparent'
|
|
|
+ this.ctx.lineWidth = this.config.lineWidth
|
|
|
+ this.ctx.strokeStyle = this.config.strokeStyle
|
|
|
+ this.ctx.lineCap = this.config.lineCap
|
|
|
+ this.ctx.lineJoin = this.config.lineJoin
|
|
|
+
|
|
|
+ // 绘制填充矩形
|
|
|
+ this.ctx.fillRect(
|
|
|
+ 0, // x 轴起始绘制位置
|
|
|
+ 0, // y 轴起始绘制位置
|
|
|
+ this.config.width, // 宽度
|
|
|
+ this.config.height // 高度
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+ // 创建鼠标/手势按下监听器
|
|
|
+ canvas.addEventListener(this.mobileStatus ? 'touchstart' : 'mousedown', this.startDraw)
|
|
|
+ // 创建鼠标/手势 弹起/离开 监听器
|
|
|
+ canvas.addEventListener(this.mobileStatus ? 'touchend' : 'mouseup', this.cloaseDraw)
|
|
|
+ },
|
|
|
+ // 开始绘制
|
|
|
+ startDraw(event) {
|
|
|
+ // 获取偏移量及坐标
|
|
|
+ const { clientX, clientY, pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
|
|
|
+ const rect = this.$refs.signature.getBoundingClientRect();
|
|
|
+ console.log(event);
|
|
|
+
|
|
|
+ // 修改上次的偏移量及坐标
|
|
|
+ this.client.offsetX = clientX-rect.left
|
|
|
+ this.client.offsetY = clientY-rect.top
|
|
|
+ this.client.endX = pageX-rect.left
|
|
|
+ this.client.endY = pageY-rect.top
|
|
|
+ console.log(this.client.offsetX,this.client.offsetY,this.client.endX,this.client.endY);
|
|
|
+
|
|
|
+
|
|
|
+ // 清除以上一次 beginPath 之后的所有路径,进行绘制
|
|
|
+ this.ctx.beginPath()
|
|
|
+ // 设置画线起始点位
|
|
|
+ this.ctx.moveTo(this.client.endX, this.client.endY)
|
|
|
+ // 监听 鼠标移动或手势移动
|
|
|
+ this.canvas.addEventListener(this.mobileStatus ? 'touchmove' : 'mousemove', this.draw)
|
|
|
+ },
|
|
|
// 绘制
|
|
|
- this.ctx.stroke()
|
|
|
-
|
|
|
- // 记录坐标
|
|
|
- this.points.push(obj)
|
|
|
- },
|
|
|
- // 结束绘制
|
|
|
- cloaseDraw () {
|
|
|
+ draw(event) {
|
|
|
+ // 获取当前坐标点位
|
|
|
+ const { pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
|
|
|
+ const rect = this.$refs.signature.getBoundingClientRect();
|
|
|
+ // 修改最后一次绘制的坐标点
|
|
|
+ this.client.endX = pageX-rect.left
|
|
|
+ this.client.endY = pageY-rect.top
|
|
|
+ const obj = {
|
|
|
+ x: pageX,
|
|
|
+ y: pageY
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(obj);
|
|
|
+
|
|
|
+ // 根据坐标点位移动添加线条
|
|
|
+ this.ctx.lineTo(pageX, pageY)
|
|
|
+
|
|
|
+ // 绘制
|
|
|
+ this.ctx.stroke()
|
|
|
+
|
|
|
+ // 记录坐标
|
|
|
+ this.points.push(obj)
|
|
|
+ },
|
|
|
// 结束绘制
|
|
|
- this.ctx.closePath()
|
|
|
- // 移除鼠标移动或手势移动监听器
|
|
|
- this.canvas.removeEventListener('mousemove', this.draw)
|
|
|
- },
|
|
|
- // 取消/清空画布
|
|
|
- clear () {
|
|
|
- // 清空当前画布上的所有绘制内容
|
|
|
- this.ctx.clearRect(0, 0, this.config.width, this.config.height)
|
|
|
- // 清空坐标
|
|
|
- this.points = []
|
|
|
- },
|
|
|
- // 保存
|
|
|
- save () {
|
|
|
- // 判断至少有20个坐标 才算有签名
|
|
|
- if (this.points.length < 20) {
|
|
|
- alert('签名不能为空!')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 操作事件
|
|
|
- const baseFile = this.canvas.toDataURL() // 转成base64,默认转成png格式的图片编码
|
|
|
- const filename = `${Date.now()}.png` // 文件名字
|
|
|
- const file = this.dataURLToFile(baseFile, filename) // 图片文件形式 传给后端存储即可
|
|
|
-
|
|
|
- this.uploadSignatrue(file)
|
|
|
-
|
|
|
- // this.dataUrlToPng()
|
|
|
-
|
|
|
- // this.dataToImg()
|
|
|
- },
|
|
|
- // img显示签名
|
|
|
- dataToImg () {
|
|
|
- // 转成base64
|
|
|
- const baseFile = this.canvas.toDataURL() // 默认转成png格式的图片编码
|
|
|
- this.imgurl = baseFile
|
|
|
- },
|
|
|
- // 将签名生成png图片
|
|
|
- dataUrlToPng () {
|
|
|
- // 将canvas上的内容转成blob流
|
|
|
- this.canvas.toBlob(blob => {
|
|
|
- // 获取当前时间并转成字符串,用来当做文件名
|
|
|
- const date = Date.now().toString()
|
|
|
- // 创建一个 a 标签
|
|
|
- const a = document.createElement('a')
|
|
|
- // 设置 a 标签的下载文件名
|
|
|
- a.download = `${date}.png`
|
|
|
- // 设置 a 标签的跳转路径为 文件流地址
|
|
|
- a.href = URL.createObjectURL(blob)
|
|
|
- // 手动触发 a 标签的点击事件
|
|
|
- a.click()
|
|
|
- // 移除 a 标签
|
|
|
- a.remove()
|
|
|
- })
|
|
|
- },
|
|
|
- // 将base64转成File文件对象
|
|
|
- dataURLToFile (dataURL, filename) {
|
|
|
- const arr = dataURL.split(',')
|
|
|
- // 获取图片格式
|
|
|
- const imgType = arr[0].match(/:(.*?);/)[1]
|
|
|
- // atob() 方法用于解码使用 base-64 编码的字符串
|
|
|
- const dec = atob(arr[1])
|
|
|
- let n = dec.length
|
|
|
- const u8arr = new Uint8Array(n)
|
|
|
- while (n--) {
|
|
|
- // 转成ASCII码
|
|
|
- u8arr[n] = dec.charCodeAt(n)
|
|
|
+ cloaseDraw() {
|
|
|
+ // 结束绘制
|
|
|
+ this.ctx.closePath()
|
|
|
+ // 移除鼠标移动或手势移动监听器
|
|
|
+ this.canvas.removeEventListener('mousemove', this.draw)
|
|
|
+ },
|
|
|
+ // 取消/清空画布
|
|
|
+ clear() {
|
|
|
+ // 清空当前画布上的所有绘制内容
|
|
|
+ this.ctx.clearRect(0, 0, this.config.width, this.config.height)
|
|
|
+ // 清空坐标
|
|
|
+ this.points = []
|
|
|
+ },
|
|
|
+ // 保存
|
|
|
+ save() {
|
|
|
+ // 判断至少有20个坐标 才算有签名
|
|
|
+ if (this.points.length < 20) {
|
|
|
+ alert('签名不能为空!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 操作事件
|
|
|
+ const baseFile = this.canvas.toDataURL() // 转成base64,默认转成png格式的图片编码
|
|
|
+ const filename = `${Date.now()}.png` // 文件名字
|
|
|
+ const file = this.dataURLToFile(baseFile, filename) // 图片文件形式 传给后端存储即可
|
|
|
+
|
|
|
+ this.uploadSignatrue(file)
|
|
|
+
|
|
|
+ // this.dataUrlToPng()
|
|
|
+
|
|
|
+ // this.dataToImg()
|
|
|
+ },
|
|
|
+ // img显示签名
|
|
|
+ dataToImg() {
|
|
|
+ // 转成base64
|
|
|
+ const baseFile = this.canvas.toDataURL() // 默认转成png格式的图片编码
|
|
|
+ this.imgurl = baseFile
|
|
|
+ },
|
|
|
+ // 将签名生成png图片
|
|
|
+ dataUrlToPng() {
|
|
|
+ // 将canvas上的内容转成blob流
|
|
|
+ this.canvas.toBlob(blob => {
|
|
|
+ // 获取当前时间并转成字符串,用来当做文件名
|
|
|
+ const date = Date.now().toString()
|
|
|
+ // 创建一个 a 标签
|
|
|
+ const a = document.createElement('a')
|
|
|
+ // 设置 a 标签的下载文件名
|
|
|
+ a.download = `${date}.png`
|
|
|
+ // 设置 a 标签的跳转路径为 文件流地址
|
|
|
+ a.href = URL.createObjectURL(blob)
|
|
|
+ // 手动触发 a 标签的点击事件
|
|
|
+ a.click()
|
|
|
+ // 移除 a 标签
|
|
|
+ a.remove()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 将base64转成File文件对象
|
|
|
+ dataURLToFile(dataURL, filename) {
|
|
|
+ const arr = dataURL.split(',')
|
|
|
+ // 获取图片格式
|
|
|
+ const imgType = arr[0].match(/:(.*?);/)[1]
|
|
|
+ // atob() 方法用于解码使用 base-64 编码的字符串
|
|
|
+ const dec = atob(arr[1])
|
|
|
+ let n = dec.length
|
|
|
+ const u8arr = new Uint8Array(n)
|
|
|
+ while (n--) {
|
|
|
+ // 转成ASCII码
|
|
|
+ u8arr[n] = dec.charCodeAt(n)
|
|
|
+ }
|
|
|
+ return new File([u8arr], filename, { type: imgType })
|
|
|
+ },
|
|
|
+ // 上传签名
|
|
|
+ uploadSignatrue(file) {
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file)
|
|
|
+ // formData.append('paramsOne', paramsOne)
|
|
|
+ // ...
|
|
|
+ console.log(formData)
|
|
|
+
|
|
|
+ // 上传接口 这里就不赘述了
|
|
|
+ // uploadFile(params, ...)
|
|
|
}
|
|
|
- return new File([u8arr], filename, { type: imgType })
|
|
|
- },
|
|
|
- // 上传签名
|
|
|
- uploadSignatrue (file) {
|
|
|
- const formData = new FormData()
|
|
|
- formData.append('file', file)
|
|
|
- // formData.append('paramsOne', paramsOne)
|
|
|
- // ...
|
|
|
- console.log(formData)
|
|
|
-
|
|
|
- // 上传接口 这里就不赘述了
|
|
|
- // uploadFile(params, ...)
|
|
|
- }
|
|
|
}
|
|
|
- }
|
|
|
- </script>
|
|
|
+}
|
|
|
+</script>
|
|
|
+<style>
|
|
|
+*{
|
|
|
+ padding: 0;
|
|
|
+ margin: 0;
|
|
|
+}
|
|
|
+.ml_sign-all{
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+.ml_sign-form .el-form-item{
|
|
|
+ width: 350px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.ml_sign-form .el-form-item__error{
|
|
|
+ top: 85%;
|
|
|
+}
|
|
|
+.ml_sign-form .el-form-item__label {
|
|
|
+ position: relative; /* 设置相对定位作为星号的定位基准 */
|
|
|
+ text-align: justify;
|
|
|
+ text-align-last: justify; /* 确保最后一行也两端对齐 */
|
|
|
+ padding-left: 10px; /* 为星号预留空间 */
|
|
|
+}
|
|
|
+
|
|
|
+.ml_sign-form .el-form-item__label:before {
|
|
|
+ content: '*';
|
|
|
+ color: red;
|
|
|
+ position: absolute;
|
|
|
+ left: 0; /* 星号定位到标签最左侧 */
|
|
|
+ top: 0; /* 调整垂直位置以适应行高 */
|
|
|
+}
|
|
|
+.ml_sign-form .el-form{
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+</style>
|