EvaluationForm.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <div class="ml_sign-all">
  3. <div class="ml_sign-form">
  4. <el-form :model="signinfo" :rules="signrules" ref="signinfo" label-width="80px" class="demo-ruleForm">
  5. <el-form-item label="团队:" prop="tname">
  6. <el-input size="small" v-model="signinfo.tname"></el-input>
  7. </el-form-item>
  8. <el-form-item label="人数:" prop="tname">
  9. <el-input size="small" v-model="signinfo.tname"></el-input>
  10. </el-form-item>
  11. <!-- <el-form-item label="领队:" prop="tname">
  12. <el-input size="small" v-model="signinfo.tname"></el-input>
  13. </el-form-item>
  14. <el-form-item label="导游:" prop="tname">
  15. <el-input size="small" v-model="signinfo.tname"></el-input>
  16. </el-form-item>
  17. <el-form-item label="行程:" prop="tname">
  18. <el-input size="small" v-model="signinfo.tname"></el-input>
  19. </el-form-item>
  20. <el-form-item label="天数:" prop="tname">
  21. <el-input size="small" v-model="signinfo.tname"></el-input>
  22. </el-form-item> -->
  23. </el-form>
  24. </div>
  25. <div class="ml_sign">
  26. <canvas ref="signature" id="signature"></canvas>
  27. <div class="btn-wrapper">
  28. <button @click="clear">取消</button>
  29. <button @click="save">保存</button>
  30. </div>
  31. <img :src="imgurl">
  32. </div>
  33. </div>
  34. </template>
  35. <script>
  36. export default {
  37. name: 'Signatrue',
  38. data() {
  39. return {
  40. signinfo:{
  41. tname:'',
  42. },
  43. signrules:{
  44. tname: [
  45. { required: true, message: '请输入活动名称', trigger: 'blur' },
  46. { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
  47. ],
  48. },
  49. canvas: null, // 存储canvas节点
  50. ctx: null, // 存储canvas的context上下文
  51. config: {
  52. width: 350, // 宽度
  53. height: 200, // 高度
  54. strokeStyle: 'red', // 线条颜色
  55. lineWidth: 4, // 线条宽度
  56. lineCap: 'round', // 设置线条两端圆角
  57. lineJoin: 'round' // 线条交汇处圆角
  58. },
  59. points: [], // 记录坐标 用来判断是否有签名的
  60. client: {
  61. offsetX: 0, // 偏移量
  62. offsetY: 0,
  63. endX: 0, // 坐标
  64. endY: 0
  65. },
  66. imgurl: ''
  67. }
  68. },
  69. computed: {
  70. // 判断是否为移动端
  71. mobileStatus() {
  72. return (/Mobile|Android|iPhone/i.test(navigator.userAgent))
  73. }
  74. },
  75. mounted() {
  76. this.init()
  77. },
  78. methods: {
  79. // 初始化
  80. init() {
  81. const canvas = this.$refs.signature
  82. canvas.width = this.config.width // 设置canvas的宽
  83. canvas.height = this.config.height // 设置canvas的高
  84. // 设置一个边框
  85. canvas.style.border = '1px solid #000'
  86. // 存储canvas节点
  87. this.canvas = canvas
  88. // 创建context对象
  89. this.ctx = canvas.getContext('2d')
  90. console.log(this.ctx);
  91. // 设置相应配置
  92. this.ctx.fillStyle = 'transparent'
  93. this.ctx.lineWidth = this.config.lineWidth
  94. this.ctx.strokeStyle = this.config.strokeStyle
  95. this.ctx.lineCap = this.config.lineCap
  96. this.ctx.lineJoin = this.config.lineJoin
  97. // 绘制填充矩形
  98. this.ctx.fillRect(
  99. 0, // x 轴起始绘制位置
  100. 0, // y 轴起始绘制位置
  101. this.config.width, // 宽度
  102. this.config.height // 高度
  103. )
  104. // 创建鼠标/手势按下监听器
  105. canvas.addEventListener(this.mobileStatus ? 'touchstart' : 'mousedown', this.startDraw)
  106. // 创建鼠标/手势 弹起/离开 监听器
  107. canvas.addEventListener(this.mobileStatus ? 'touchend' : 'mouseup', this.cloaseDraw)
  108. },
  109. // 开始绘制
  110. startDraw(event) {
  111. // 获取偏移量及坐标
  112. const { clientX, clientY, pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
  113. const rect = this.$refs.signature.getBoundingClientRect();
  114. console.log(event);
  115. // 修改上次的偏移量及坐标
  116. this.client.offsetX = clientX-rect.left
  117. this.client.offsetY = clientY-rect.top
  118. this.client.endX = pageX-rect.left
  119. this.client.endY = pageY-rect.top
  120. console.log(this.client.offsetX,this.client.offsetY,this.client.endX,this.client.endY);
  121. // 清除以上一次 beginPath 之后的所有路径,进行绘制
  122. this.ctx.beginPath()
  123. // 设置画线起始点位
  124. this.ctx.moveTo(this.client.endX, this.client.endY)
  125. // 监听 鼠标移动或手势移动
  126. this.canvas.addEventListener(this.mobileStatus ? 'touchmove' : 'mousemove', this.draw)
  127. },
  128. // 绘制
  129. draw(event) {
  130. // 获取当前坐标点位
  131. const { pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
  132. const rect = this.$refs.signature.getBoundingClientRect();
  133. // 修改最后一次绘制的坐标点
  134. this.client.endX = pageX-rect.left
  135. this.client.endY = pageY-rect.top
  136. const obj = {
  137. x: pageX,
  138. y: pageY
  139. }
  140. console.log(obj);
  141. // 根据坐标点位移动添加线条
  142. this.ctx.lineTo(pageX, pageY)
  143. // 绘制
  144. this.ctx.stroke()
  145. // 记录坐标
  146. this.points.push(obj)
  147. },
  148. // 结束绘制
  149. cloaseDraw() {
  150. // 结束绘制
  151. this.ctx.closePath()
  152. // 移除鼠标移动或手势移动监听器
  153. this.canvas.removeEventListener('mousemove', this.draw)
  154. },
  155. // 取消/清空画布
  156. clear() {
  157. // 清空当前画布上的所有绘制内容
  158. this.ctx.clearRect(0, 0, this.config.width, this.config.height)
  159. // 清空坐标
  160. this.points = []
  161. },
  162. // 保存
  163. save() {
  164. // 判断至少有20个坐标 才算有签名
  165. if (this.points.length < 20) {
  166. alert('签名不能为空!')
  167. return
  168. }
  169. // 操作事件
  170. const baseFile = this.canvas.toDataURL() // 转成base64,默认转成png格式的图片编码
  171. const filename = `${Date.now()}.png` // 文件名字
  172. const file = this.dataURLToFile(baseFile, filename) // 图片文件形式 传给后端存储即可
  173. this.uploadSignatrue(file)
  174. // this.dataUrlToPng()
  175. // this.dataToImg()
  176. },
  177. // img显示签名
  178. dataToImg() {
  179. // 转成base64
  180. const baseFile = this.canvas.toDataURL() // 默认转成png格式的图片编码
  181. this.imgurl = baseFile
  182. },
  183. // 将签名生成png图片
  184. dataUrlToPng() {
  185. // 将canvas上的内容转成blob流
  186. this.canvas.toBlob(blob => {
  187. // 获取当前时间并转成字符串,用来当做文件名
  188. const date = Date.now().toString()
  189. // 创建一个 a 标签
  190. const a = document.createElement('a')
  191. // 设置 a 标签的下载文件名
  192. a.download = `${date}.png`
  193. // 设置 a 标签的跳转路径为 文件流地址
  194. a.href = URL.createObjectURL(blob)
  195. // 手动触发 a 标签的点击事件
  196. a.click()
  197. // 移除 a 标签
  198. a.remove()
  199. })
  200. },
  201. // 将base64转成File文件对象
  202. dataURLToFile(dataURL, filename) {
  203. const arr = dataURL.split(',')
  204. // 获取图片格式
  205. const imgType = arr[0].match(/:(.*?);/)[1]
  206. // atob() 方法用于解码使用 base-64 编码的字符串
  207. const dec = atob(arr[1])
  208. let n = dec.length
  209. const u8arr = new Uint8Array(n)
  210. while (n--) {
  211. // 转成ASCII码
  212. u8arr[n] = dec.charCodeAt(n)
  213. }
  214. return new File([u8arr], filename, { type: imgType })
  215. },
  216. // 上传签名
  217. uploadSignatrue(file) {
  218. const formData = new FormData()
  219. formData.append('file', file)
  220. // formData.append('paramsOne', paramsOne)
  221. // ...
  222. console.log(formData)
  223. // 上传接口 这里就不赘述了
  224. // uploadFile(params, ...)
  225. }
  226. }
  227. }
  228. </script>
  229. <style>
  230. *{
  231. padding: 0;
  232. margin: 0;
  233. }
  234. .ml_sign-all{
  235. padding: 10px;
  236. }
  237. .ml_sign-form .el-form-item{
  238. width: 350px;
  239. margin-bottom: 10px;
  240. }
  241. .ml_sign-form .el-form-item__error{
  242. top: 85%;
  243. }
  244. .ml_sign-form .el-form-item__label {
  245. position: relative; /* 设置相对定位作为星号的定位基准 */
  246. text-align: justify;
  247. text-align-last: justify; /* 确保最后一行也两端对齐 */
  248. padding-left: 10px; /* 为星号预留空间 */
  249. }
  250. .ml_sign-form .el-form-item__label:before {
  251. content: '*';
  252. color: red;
  253. position: absolute;
  254. left: 0; /* 星号定位到标签最左侧 */
  255. top: 0; /* 调整垂直位置以适应行高 */
  256. }
  257. .ml_sign-form .el-form{
  258. display: flex;
  259. flex-wrap: wrap;
  260. justify-content: space-between;
  261. }
  262. </style>