|  | @@ -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>
 |