| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- <template>
- <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>
- </div>
- </template>
-
- <script>
- export default {
- name: 'Signatrue',
- 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))
- }
- },
- 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')
- 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)
- },
- // 绘制
- 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)
- },
- // 结束绘制
- 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, ...)
- }
- }
- }
- </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>
|