Showing
14 changed files
with
566 additions
and
1 deletions
57.4 KB
20.7 KB
13.2 KB
23.4 KB
11.9 KB
20.7 KB
46.6 KB
72.8 KB
20.6 KB
17.8 KB
38.3 KB
src/components/ImageSliderVerify/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <!-- 滑块验证功能 --> | ||
| 3 | + <div class="sliderModel" v-if="visible"> | ||
| 4 | + <div class="cont"> | ||
| 5 | + <div class="title">滑块验证</div> | ||
| 6 | + <div class="slider-refresh"> | ||
| 7 | + <span @click="handleRefresh">刷新</span> | ||
| 8 | + <span @click="handleClose">关闭</span> | ||
| 9 | + </div> | ||
| 10 | + <!-- canvas 图片 --> | ||
| 11 | + <div class="imgWrap"> | ||
| 12 | + <canvas ref="sliderBlock" class="slider-block"></canvas> | ||
| 13 | + <canvas ref="codeImg" class="code-img"></canvas> | ||
| 14 | + </div> | ||
| 15 | + <!-- 滑块 --> | ||
| 16 | + <div class="sliderBox"> | ||
| 17 | + <div class="sliderF"> | ||
| 18 | + <div class="sliderS" @touchstart.prevent="handleTouch"> | ||
| 19 | + <div class="btn">>></div> | ||
| 20 | + </div> | ||
| 21 | + </div> | ||
| 22 | + <div class="bgC"> | ||
| 23 | + {{ tips }} | ||
| 24 | + <div class="bgC_left"></div> | ||
| 25 | + </div> | ||
| 26 | + </div> | ||
| 27 | + </div> | ||
| 28 | + </div> | ||
| 29 | +</template> | ||
| 30 | + | ||
| 31 | +<script> | ||
| 32 | +import img1 from './images/1.jpg' | ||
| 33 | +import img2 from './images/2.jpg' | ||
| 34 | +import img3 from './images/3.jpg' | ||
| 35 | +import img4 from './images/4.jpg' | ||
| 36 | +import img5 from './images/5.jpg' | ||
| 37 | +import img6 from './images/6.jpg' | ||
| 38 | + | ||
| 39 | +export default { | ||
| 40 | + props: { | ||
| 41 | + isShow: { | ||
| 42 | + type: Boolean, | ||
| 43 | + default: false | ||
| 44 | + }, | ||
| 45 | + options: { | ||
| 46 | + // 传入的参数不影响组件 | ||
| 47 | + type: Object, | ||
| 48 | + default: () => ({}) | ||
| 49 | + }, | ||
| 50 | + imgList: { // 背景图片 | ||
| 51 | + type: Array, | ||
| 52 | + default: () => { | ||
| 53 | + return [img1, img2, img3, img4, img5, img6] | ||
| 54 | + } | ||
| 55 | + } | ||
| 56 | + }, | ||
| 57 | + data() { | ||
| 58 | + return { | ||
| 59 | + // 滑块x轴数据 | ||
| 60 | + slider: { | ||
| 61 | + mx: 0, | ||
| 62 | + bx: 0 | ||
| 63 | + }, | ||
| 64 | + tips: '', | ||
| 65 | + visible: false, | ||
| 66 | + mainDom: '', | ||
| 67 | + blockDom: '' | ||
| 68 | + } | ||
| 69 | + }, | ||
| 70 | + watch: { | ||
| 71 | + isShow: { | ||
| 72 | + handler(newVal) { | ||
| 73 | + this.visible = newVal | ||
| 74 | + if (newVal === true) { | ||
| 75 | + this.tips = '拖动左边滑块完成上方拼图' | ||
| 76 | + this.$nextTick(() => { | ||
| 77 | + this.getDom() | ||
| 78 | + this.canvasInit() | ||
| 79 | + }) | ||
| 80 | + } | ||
| 81 | + }, | ||
| 82 | + immediate: true | ||
| 83 | + } | ||
| 84 | + }, | ||
| 85 | + | ||
| 86 | + methods: { | ||
| 87 | + // 获取 dom | ||
| 88 | + getDom() { | ||
| 89 | + this.mainDom = this.$refs.codeImg | ||
| 90 | + this.blockDom = this.$refs.sliderBlock | ||
| 91 | + }, | ||
| 92 | + | ||
| 93 | + handleClose() { | ||
| 94 | + this.$emit('on-close', false) | ||
| 95 | + }, | ||
| 96 | + | ||
| 97 | + // 刷新 | ||
| 98 | + handleRefresh() { | ||
| 99 | + this.canvasInit() | ||
| 100 | + }, | ||
| 101 | + | ||
| 102 | + // 移动端事件 | ||
| 103 | + handleTouch(e) { | ||
| 104 | + const ev = e || window.event | ||
| 105 | + const dom = ev.target // dom元素 | ||
| 106 | + | ||
| 107 | + const downCoordinate = { | ||
| 108 | + x: ev.touches[0].pageX, | ||
| 109 | + y: ev.touches[0].pageY | ||
| 110 | + } | ||
| 111 | + // 正确的滑块数据 | ||
| 112 | + const checkx = Number(this.slider.mx) - 0 | ||
| 113 | + // x轴数据 | ||
| 114 | + let x = 0 | ||
| 115 | + const move = (moveEV) => { | ||
| 116 | + x = moveEV.touches[0].pageX - downCoordinate.x | ||
| 117 | + // //y = moveEV.y - downCoordinate.y; | ||
| 118 | + if (x >= 251 || x <= 0) return false | ||
| 119 | + dom.style.left = x + 'px' | ||
| 120 | + // dom.style.top = y + "px"; | ||
| 121 | + this.blockDom.style.left = x + 'px' | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + const up = () => { | ||
| 125 | + document.removeEventListener('touchmove', move) | ||
| 126 | + document.removeEventListener('touchend', up) | ||
| 127 | + dom.style.left = '' | ||
| 128 | + | ||
| 129 | + // console.log(x, checkx) | ||
| 130 | + const max = checkx - 5 | ||
| 131 | + const min = checkx - 15 | ||
| 132 | + // 允许正负误差1 | ||
| 133 | + if ((max >= x && x >= min) || x === checkx) { | ||
| 134 | + this.tips = '验证成功' | ||
| 135 | + this.$emit('done', this.options.type) | ||
| 136 | + } else { | ||
| 137 | + this.tips = '验证失败,请重试' | ||
| 138 | + this.blockDom.style.left = 0 | ||
| 139 | + this.canvasInit() | ||
| 140 | + } | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + document.addEventListener('touchmove', move) | ||
| 144 | + document.addEventListener('touchend', up) | ||
| 145 | + }, | ||
| 146 | + | ||
| 147 | + // 拼图验证码初始化 | ||
| 148 | + canvasInit() { | ||
| 149 | + // 生成指定区间的随机数 | ||
| 150 | + const random = (min, max) => { | ||
| 151 | + return Math.floor(Math.random() * (max - min + 1) + min) | ||
| 152 | + } | ||
| 153 | + // x: 254, y: 109 | ||
| 154 | + const mx = random(127, 230) | ||
| 155 | + const bx = random(10, 128) | ||
| 156 | + const y = random(10, 99) | ||
| 157 | + | ||
| 158 | + this.slider = { mx, bx } | ||
| 159 | + | ||
| 160 | + this.draw(mx, bx, y) | ||
| 161 | + }, | ||
| 162 | + | ||
| 163 | + draw(mx = 200, bx = 20, y = 50) { | ||
| 164 | + const bg = this.mainDom.getContext('2d') | ||
| 165 | + const block = this.blockDom.getContext('2d') | ||
| 166 | + | ||
| 167 | + const width = this.mainDom.width | ||
| 168 | + const height = this.mainDom.height | ||
| 169 | + | ||
| 170 | + // 重新赋值,让canvas进行重新绘制 | ||
| 171 | + this.blockDom.height = height | ||
| 172 | + this.mainDom.height = height | ||
| 173 | + this.mainDom.width = width | ||
| 174 | + // 随机背景图片 | ||
| 175 | + const randomImg = () => { | ||
| 176 | + const num = Math.floor(Math.random() * this.imgList.length) | ||
| 177 | + return this.imgList[num] | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + const imgsrc = randomImg() | ||
| 181 | + const img = document.createElement('img') | ||
| 182 | + img.style.objectFit = 'scale-down' | ||
| 183 | + img.src = imgsrc | ||
| 184 | + img.onload = () => { | ||
| 185 | + bg.drawImage(img, 0, 0, width, height) | ||
| 186 | + block.drawImage(img, 0, 0, width, height) | ||
| 187 | + const ImageData = block.getImageData(mx, y, width, height) | ||
| 188 | + block.putImageData(ImageData, 0, y) | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + const mainxy = { x: mx, y: y, r: 9 } | ||
| 192 | + const blockxy = { x: mx, y: y, r: 9 } | ||
| 193 | + this.drawBlock(bg, mainxy, 'fill') | ||
| 194 | + this.drawBlock(block, blockxy, 'clip') | ||
| 195 | + }, | ||
| 196 | + | ||
| 197 | + // 绘制拼图 | ||
| 198 | + drawBlock(ctx, xy = { x: 254, y: 109, r: 9 }, type) { | ||
| 199 | + const x = xy.x | ||
| 200 | + const y = xy.y | ||
| 201 | + const r = xy.r | ||
| 202 | + const w = 40 | ||
| 203 | + const PI = Math.PI | ||
| 204 | + // 绘制 | ||
| 205 | + ctx.beginPath() | ||
| 206 | + // left | ||
| 207 | + // ctx.moveTo(x, y) | ||
| 208 | + // top | ||
| 209 | + ctx.arc(x + (w + 5) / 2, y, r, -PI, 0.15, true) | ||
| 210 | + ctx.lineTo(x + w + 5, y) | ||
| 211 | + // right | ||
| 212 | + ctx.arc(x + w + 5, y + w / 2, r, 1.5 * PI, 0.5 * PI, false) | ||
| 213 | + ctx.lineTo(x + w + 5, y + w) | ||
| 214 | + // bottom | ||
| 215 | + ctx.arc(x + (w + 5) / 2, y + w, r, 0, PI, false) | ||
| 216 | + ctx.lineTo(x, y + w) | ||
| 217 | + ctx.arc(x, y + w / 2, r, 0.5 * PI, 1.5 * PI, true) | ||
| 218 | + ctx.lineTo(x, y) | ||
| 219 | + // 修饰,没有会看不出效果 | ||
| 220 | + ctx.lineWidth = 1 | ||
| 221 | + ctx.fillStyle = 'rgba(255, 255, 255, 0.5)' | ||
| 222 | + ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)' | ||
| 223 | + ctx.stroke() | ||
| 224 | + ctx[type]() | ||
| 225 | + ctx.globalCompositeOperation = 'xor' | ||
| 226 | + } | ||
| 227 | + } | ||
| 228 | +} | ||
| 229 | +</script> | ||
| 230 | + | ||
| 231 | +<style scoped lang="less"> | ||
| 232 | +.sliderModel { | ||
| 233 | + position: fixed; | ||
| 234 | + left: 0; | ||
| 235 | + top: 0; | ||
| 236 | + width: 100%; | ||
| 237 | + height: 100%; | ||
| 238 | + background: rgba(0, 0, 0, 0.5); | ||
| 239 | + display: flex; | ||
| 240 | + justify-content: center; | ||
| 241 | + align-items: center; | ||
| 242 | +} | ||
| 243 | + | ||
| 244 | +.title { | ||
| 245 | + width: 100%; | ||
| 246 | + height: 60px; | ||
| 247 | + font-size: 18px; | ||
| 248 | + color: #333; | ||
| 249 | + display: flex; | ||
| 250 | + align-items: center; | ||
| 251 | + justify-content: center; | ||
| 252 | +} | ||
| 253 | + | ||
| 254 | +.cont { | ||
| 255 | + position: relative; | ||
| 256 | + background: #fff; | ||
| 257 | + width: 300px; | ||
| 258 | + border-radius: 8px; | ||
| 259 | + overflow: hidden; | ||
| 260 | + padding-bottom: 10px; | ||
| 261 | +} | ||
| 262 | + | ||
| 263 | +.imgWrap { | ||
| 264 | + position: relative; | ||
| 265 | + width: 280px; | ||
| 266 | + height: 150px; | ||
| 267 | + margin: 0 auto; | ||
| 268 | + overflow: hidden; | ||
| 269 | + | ||
| 270 | + .code-img, | ||
| 271 | + .slider-block { | ||
| 272 | + border-radius: 8px; | ||
| 273 | + height: inherit; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + .code-img { | ||
| 277 | + width: 280px; | ||
| 278 | + margin: 0 auto; | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + .slider-block { | ||
| 282 | + position: absolute; | ||
| 283 | + z-index: 4000; | ||
| 284 | + left: 0; | ||
| 285 | + } | ||
| 286 | +} | ||
| 287 | + | ||
| 288 | +.slider-refresh { | ||
| 289 | + font-size: 14px; | ||
| 290 | + position: absolute; | ||
| 291 | + top: 20px; | ||
| 292 | + right: 20px; | ||
| 293 | + cursor: pointer; | ||
| 294 | + color: green; | ||
| 295 | + | ||
| 296 | + span { | ||
| 297 | + padding: 0 2px; | ||
| 298 | + } | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +.img { | ||
| 302 | + display: block; | ||
| 303 | + width: 100%; | ||
| 304 | + height: 100%; | ||
| 305 | +} | ||
| 306 | + | ||
| 307 | +.sliderOver { | ||
| 308 | + position: absolute; | ||
| 309 | + left: 0; | ||
| 310 | + top: 0; | ||
| 311 | + width: 50px; | ||
| 312 | + height: 50px; | ||
| 313 | + background: #ddd; | ||
| 314 | + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3); | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +.smartImg { | ||
| 318 | + position: absolute; | ||
| 319 | + z-index: 2; | ||
| 320 | + left: 0; | ||
| 321 | + top: 0; | ||
| 322 | + width: 50px; | ||
| 323 | + height: 50px; | ||
| 324 | + overflow: hidden; | ||
| 325 | + box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); | ||
| 326 | +} | ||
| 327 | + | ||
| 328 | +.simg { | ||
| 329 | + position: absolute; | ||
| 330 | + display: block; | ||
| 331 | + width: 280px; | ||
| 332 | + height: 150px; | ||
| 333 | +} | ||
| 334 | + | ||
| 335 | +.sliderBox { | ||
| 336 | + width: 280px; | ||
| 337 | + margin: 15px auto 0; | ||
| 338 | + height: 36px; | ||
| 339 | + position: relative; | ||
| 340 | +} | ||
| 341 | + | ||
| 342 | +.sliderF { | ||
| 343 | + width: 100%; | ||
| 344 | + height: 100%; | ||
| 345 | + z-index: 3; | ||
| 346 | +} | ||
| 347 | + | ||
| 348 | +.sliderS { | ||
| 349 | + cursor: pointer; | ||
| 350 | + position: absolute; | ||
| 351 | + left: 0; | ||
| 352 | + top: 0; | ||
| 353 | + z-index: 2; | ||
| 354 | + height: 36px; | ||
| 355 | + width: 36px; | ||
| 356 | + border-radius: 36px; | ||
| 357 | + display: flex; | ||
| 358 | + justify-content: center; | ||
| 359 | + align-items: center; | ||
| 360 | +} | ||
| 361 | + | ||
| 362 | +.icon { | ||
| 363 | + width: 20px; | ||
| 364 | + height: 20px; | ||
| 365 | +} | ||
| 366 | + | ||
| 367 | +.bgC { | ||
| 368 | + position: absolute; | ||
| 369 | + z-index: 1; | ||
| 370 | + left: 0; | ||
| 371 | + top: 50%; | ||
| 372 | + transform: translateY(-50%); | ||
| 373 | + width: 100%; | ||
| 374 | + height: 30px; | ||
| 375 | + border-radius: 30px; | ||
| 376 | + line-height: 30px; | ||
| 377 | + font-size: 14px; | ||
| 378 | + color: #999999; | ||
| 379 | + box-shadow: inset 0 0 4px #ccc; | ||
| 380 | + text-align: center; | ||
| 381 | + overflow: hidden; | ||
| 382 | +} | ||
| 383 | + | ||
| 384 | +.bgC_left { | ||
| 385 | + position: absolute; | ||
| 386 | + left: 0px; | ||
| 387 | + top: 50%; | ||
| 388 | + transform: translateY(-50%); | ||
| 389 | + width: 0; | ||
| 390 | + height: 28px; | ||
| 391 | + border-top-left-radius: 28px; | ||
| 392 | + border-bottom-left-radius: 28px; | ||
| 393 | + line-height: 28px; | ||
| 394 | + font-size: 14px; | ||
| 395 | + background-color: #eee; | ||
| 396 | + box-shadow: inset 0 0 4px #ccc; | ||
| 397 | + text-align: center; | ||
| 398 | +} | ||
| 399 | + | ||
| 400 | +.showMessage { | ||
| 401 | + text-align: center; | ||
| 402 | + font-size: 14px; | ||
| 403 | + height: 30px; | ||
| 404 | + line-height: 30px; | ||
| 405 | +} | ||
| 406 | + | ||
| 407 | +#closeBtn { | ||
| 408 | + position: fixed; | ||
| 409 | + z-index: 10; | ||
| 410 | + bottom: 10px; | ||
| 411 | + left: 50%; | ||
| 412 | +} | ||
| 413 | + | ||
| 414 | +.btn { | ||
| 415 | + width: 36px; | ||
| 416 | + height: 36px; | ||
| 417 | + position: absolute; | ||
| 418 | + border: 1px solid #ccc; | ||
| 419 | + cursor: move; | ||
| 420 | + font-family: "宋体"; | ||
| 421 | + text-align: center; | ||
| 422 | + line-height: 36px; | ||
| 423 | + background-color: #fff; | ||
| 424 | + user-select: none; | ||
| 425 | + color: #666; | ||
| 426 | + font-size: 16px; | ||
| 427 | +} | ||
| 428 | +</style> |
src/components/图形验证码.vue
0 → 100644
| 1 | +// ts script setup 写法 | ||
| 2 | +<template> | ||
| 3 | + <div class="img-verify"> | ||
| 4 | + <canvas ref="verify" :width="state.width" :height="state.height" @click="handleDraw" /> | ||
| 5 | + </div> | ||
| 6 | +</template> | ||
| 7 | + | ||
| 8 | +<script setup lang="ts"> | ||
| 9 | +import { reactive, onMounted, ref } from 'vue' | ||
| 10 | + | ||
| 11 | +const verify = ref({} as HTMLCanvasElement) | ||
| 12 | +const state = reactive({ | ||
| 13 | + pool: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', // 字符串 | ||
| 14 | + width: 120, | ||
| 15 | + height: 40, | ||
| 16 | + imgCode: '' | ||
| 17 | +}) | ||
| 18 | +onMounted(() => { | ||
| 19 | + // 初始化绘制图片验证码 | ||
| 20 | + state.imgCode = draw() | ||
| 21 | +}) | ||
| 22 | + | ||
| 23 | +// 点击图片重新绘制 | ||
| 24 | +const handleDraw = () => { | ||
| 25 | + state.imgCode = draw() | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +// 随机数 | ||
| 29 | +const randomNum = (min: number, max: number) => { | ||
| 30 | + return parseInt((Math.random() * (max - min) + min + '') as string) | ||
| 31 | +} | ||
| 32 | +// 随机颜色 | ||
| 33 | +const randomColor = (min: number, max: number) => { | ||
| 34 | + const r = randomNum(min, max) | ||
| 35 | + const g = randomNum(min, max) | ||
| 36 | + const b = randomNum(min, max) | ||
| 37 | + return `rgb(${r},${g},${b})` | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +// 绘制图片 | ||
| 41 | +const draw = () => { | ||
| 42 | + // 3.填充背景颜色,背景颜色要浅一点 | ||
| 43 | + const ctx = verify.value.getContext('2d') as any | ||
| 44 | + // 填充颜色 | ||
| 45 | + ctx.fillStyle = randomColor(180, 230) | ||
| 46 | + // 填充的位置 | ||
| 47 | + ctx.fillRect(0, 0, state.width, state.height) | ||
| 48 | + // 定义paramText | ||
| 49 | + let imgCode = '' | ||
| 50 | + // 4.随机产生字符串,并且随机旋转 | ||
| 51 | + for (let i = 0; i < 4; i++) { | ||
| 52 | + // 随机的四个字 | ||
| 53 | + const text = state.pool[randomNum(0, state.pool.length)] | ||
| 54 | + imgCode += text | ||
| 55 | + // 随机的字体大小 | ||
| 56 | + const fontSize = randomNum(18, 40) | ||
| 57 | + // 字体随机的旋转角度 | ||
| 58 | + const deg = randomNum(-30, 30) | ||
| 59 | + /* | ||
| 60 | + * 绘制文字并让四个文字在不同的位置显示的思路 : | ||
| 61 | + * 1、定义字体 | ||
| 62 | + * 2、定义对齐方式 | ||
| 63 | + * 3、填充不同的颜色 | ||
| 64 | + * 4、保存当前的状态(以防止以上的状态受影响) | ||
| 65 | + * 5、平移translate() | ||
| 66 | + * 6、旋转 rotate() | ||
| 67 | + * 7、填充文字 | ||
| 68 | + * 8、restore出栈 | ||
| 69 | + * */ | ||
| 70 | + ctx.font = fontSize + 'px Simhei' | ||
| 71 | + ctx.textBaseline = 'top' | ||
| 72 | + ctx.fillStyle = randomColor(80, 150) | ||
| 73 | + /* | ||
| 74 | + * save() 方法把当前状态的一份拷贝压入到一个保存图像状态的栈中。 | ||
| 75 | + * 这就允许您临时地改变图像状态, | ||
| 76 | + * 然后,通过调用 restore() 来恢复以前的值。 | ||
| 77 | + * save是入栈,restore是出栈。 | ||
| 78 | + * 用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。 | ||
| 79 | + * | ||
| 80 | + * */ | ||
| 81 | + ctx.save() | ||
| 82 | + ctx.translate(30 * i + 15, 15) | ||
| 83 | + ctx.rotate((deg * Math.PI) / 180) | ||
| 84 | + // fillText() 方法在画布上绘制填色的文本。文本的默认颜色是黑色。 | ||
| 85 | + // 请使用 font 属性来定义字体和字号,并使用 fillStyle 属性以另一种颜色/渐变来渲染文本。 | ||
| 86 | + // context.fillText(text,x,y,maxWidth); | ||
| 87 | + ctx.fillText(text, -15 + 5, -15) | ||
| 88 | + ctx.restore() | ||
| 89 | + } | ||
| 90 | + // 5.随机产生5条干扰线,干扰线的颜色要浅一点 | ||
| 91 | + for (let i = 0; i < 5; i++) { | ||
| 92 | + ctx.beginPath() | ||
| 93 | + ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height)) | ||
| 94 | + ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height)) | ||
| 95 | + ctx.strokeStyle = randomColor(180, 230) | ||
| 96 | + ctx.closePath() | ||
| 97 | + ctx.stroke() | ||
| 98 | + } | ||
| 99 | + // 6.随机产生40个干扰的小点 | ||
| 100 | + for (let i = 0; i < 40; i++) { | ||
| 101 | + ctx.beginPath() | ||
| 102 | + ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI) | ||
| 103 | + ctx.closePath() | ||
| 104 | + ctx.fillStyle = randomColor(150, 200) | ||
| 105 | + ctx.fill() | ||
| 106 | + } | ||
| 107 | + return imgCode | ||
| 108 | +} | ||
| 109 | + | ||
| 110 | +</script> | ||
| 111 | +<style type="text/css"> | ||
| 112 | +.img-verify canvas { | ||
| 113 | + cursor: pointer; | ||
| 114 | +} | ||
| 115 | +</style> |
| ... | @@ -39,6 +39,14 @@ | ... | @@ -39,6 +39,14 @@ |
| 39 | </div> | 39 | </div> |
| 40 | 40 | ||
| 41 | <van-number-keyboard v-model="phone" :show="keyboard_show" :maxlength="11" @blur="onBlur" /> | 41 | <van-number-keyboard v-model="phone" :show="keyboard_show" :maxlength="11" @blur="onBlur" /> |
| 42 | + | ||
| 43 | + <!-- 图片滑块验证 --> | ||
| 44 | + <image-slider-verify | ||
| 45 | + :isShow="sliderShow" | ||
| 46 | + @done="handleConfirm" | ||
| 47 | + @on-close="handleClose" | ||
| 48 | + > | ||
| 49 | + </image-slider-verify> | ||
| 42 | </template> | 50 | </template> |
| 43 | 51 | ||
| 44 | <script setup> | 52 | <script setup> |
| ... | @@ -199,11 +207,14 @@ const themeVars = { | ... | @@ -199,11 +207,14 @@ const themeVars = { |
| 199 | // FIXME: VUE2写法 | 207 | // FIXME: VUE2写法 |
| 200 | import mixin from 'common/mixin'; | 208 | import mixin from 'common/mixin'; |
| 201 | 209 | ||
| 210 | +import ImageSliderVerify from '@/components/ImageSliderVerify/index.vue' | ||
| 211 | + | ||
| 202 | export default { | 212 | export default { |
| 213 | + components: { ImageSliderVerify }, | ||
| 203 | mixins: [mixin.init], | 214 | mixins: [mixin.init], |
| 204 | data() { | 215 | data() { |
| 205 | return { | 216 | return { |
| 206 | - | 217 | + sliderShow: false |
| 207 | } | 218 | } |
| 208 | }, | 219 | }, |
| 209 | mounted() { | 220 | mounted() { |
| ... | @@ -223,6 +234,17 @@ export default { | ... | @@ -223,6 +234,17 @@ export default { |
| 223 | icon: 'cross', | 234 | icon: 'cross', |
| 224 | }); | 235 | }); |
| 225 | }) | 236 | }) |
| 237 | + }, | ||
| 238 | + // 滑块验证成功后回调 | ||
| 239 | + handleConfirm (val) { | ||
| 240 | + this.sliderShow = false | ||
| 241 | + console.warn('验证成功'); | ||
| 242 | + }, | ||
| 243 | + handleClose () { | ||
| 244 | + this.sliderShow = false | ||
| 245 | + }, | ||
| 246 | + imageVerify () { | ||
| 247 | + this.sliderShow = true; | ||
| 226 | } | 248 | } |
| 227 | }, | 249 | }, |
| 228 | } | 250 | } | ... | ... |
-
Please register or login to post a comment