uniapp滑块验证码对接tianai-captcha

滑块验证码的功能大家都不陌生,天爱大佬的滑块验证码给我的体验感非常的好,下文详细讲解如何在uniapp中对接滑块验证码
在这里插入图片描述

tianai-captcha

tianai-captcha其实是滑块验证码的后端,从后端生成图片,前端操作后提交到后端进行验证

  • tianai-captcha 目前支持的行为验证码类型
  • 滑块验证码
  • 旋转验证码
  • 滑动还原验证码
  • 文字点选验证码

  • 本文只拿旋转验证码作为例子

    天爱大佬是有自己的前端的,但是不适配uniapp,这里采用uniapp中的插件进行改造适配

    1. 封装滑块验证码插件,组件代码如下
    <template>
      <view class="lang-dialog__wrapper" v-if="dialogVisible">
        <view class="lang-dialog">
          <view class="lang-dialog__header">
            <view class="lang-dialog__title"><slot name="title">{{title}}</slot></view>
            <view type="button" class="lang-dialog-header-btn" @click="close"> <i class="lang-icon-close"></i> </view>
          </view>
          <view class="lang-dialog__body">
            <view class="tips" v-show="failed"><slot name="errorText">{{ errorText }}</slot></view>
            <view class="tips" v-show="!failed"><slot name="tips">{{ tips }}</slot></view>
            <view class="slider-body" :class="{'slider-shock':shock}">
              <view class="loading-transparent-mask" v-show="mask"></view>
              <view class="loading-body" v-if="loading">
                <view class="loading slider-loading"></view>
              </view>
    		  <view class="view-slider">
    			  <image class="slider-bg slider-img rotate-image" :src="sliderImg"  v-show="sliderImg"></image>
    			  <view class="rotate-image-img"
    			  						v-show="sliderImg"
    			  						@touchstart.stop="sliderTouchStart"
    			  						@touchmove.catch="sliderTouchMove"
    			  						@touchend.stop="sliderEnd"
    			  						@mousedown="sliderStart">
    			  				<image  :src="rotateImg" v-show="sliderImg" :style="dynamicStyles" class="rotate-image-image"></image>		
    			   </view>
    		  </view>
              <view class="slider-progress"></view>
              <view class="slider-success" v-show="success">
                <view class="slider-success-icon">
    							<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAMAAADVRocKAAAAzFBMVEUAAAAs0AAu1AAs0AAr0AAq1QAs0AAt0AAs0AAs0AAr0AAq0wAs0AAq0QAs0AAs0AAs0AAs0AAr0AAs0AAr0AAs0AAr0QAs0QAs0AApygAs0AAs0AAs0AAs0AAs0QAr0QAt0gAs0AAt0QAtzwAq0QArzwAs0AAqzQAs0QAs0AAs0AAs0AD////l+d/8/vty4FVG1iCI5W/X989G1h881BNy31My0gd34Vr5/vjt++qk65Gd6omO5naB42Za2jdL1ybR9cjA8bNp3kpZ2jY9PuX7AAAAK3RSTlMA/APuFwfUZln0OxLIVEbr49vZzsKxmW1MDvmsqIxpXiGejlA3NGgk1pyDVbbBQAAAArxJREFUaN7Nmody2kAURZ9QQRIyTTTRDDauV3GsFNzjlP//p8CQjAZLwLY3w/mAc9EWZvftIzGS8HIyitp2AAR2OxpNLsOETFHzex5K8Hp+Td/u9DsWdmJ1+o6OvTLoWjiA1R1UFPVutQ0h2lVX5ddXmxCmWZX+ijCGFHEot3BuIM1cYkmd21DAPhcd/TMociY0E7MIysQzgdltQIPGxSH/yRBaBPX9/lMLmgz9vX4Y4HTP+FgwgHWyy78IYIThYsf6bMAQjasyvxvDGLFLRW5gkHnJBMMohYl2bBjFdmibMQwz3vbXYZz61j+0B+N4FcqpgoFq7nebYKDpFj6A6xMqLbDQ+j8LAzAxoA1dMNH9t4kt6PL+/Ov5DwpYm+3chy7fH9M0vctQoE9rOtr+u3TN3Ts+0ln7a5a+f8NrcYzWx0nflD99QQF/FdAz5U8fUKC3CvCgw+fc/2WJAh5RYsr/6R4lJHTB6kdIPqsfU7pl9WNCY1Y/RhSx+hFRCzv4/fPx6Xmp6UeLbJTzsFnb3/T8sClAKcunNE9Q9yMglPMjTfMEdT+wOyBP0PFjzxDlCRr+YN8k5wnKfti7l2lWSFDwo0URhBIU/YhoDLEENT9GdAvBhIL/KwSYkA/RBBU/pnQB4QQFP0JKIJzwJu9HQuQJJ6Tyfk/g2JJp+NETOXhl6n74QkfHTNlv1cQOv5miHx3R43um5kdf+AKSKfktR/wKlSn40ZW5BL49rvealB8DqWvs/evLwxIytCrMF/FL9lICdzGEvZzDXZBiLqmN2IuC7GXNAnMYZM5cWvZcKuGKqTiesxjCCMGC94FiWGd+YvG5H4m4n7nYH+oOMouhTDwjAVz1x1L3OJ57VzhjSDN2SIa6Bym8+rE1Daxwp6JtD1P3OBs3+FtP8uaZa5RwnTfP6JOE0+32n6lo+89fOWa8/GcTqREAAAAASUVORK5CYII="
                    alt="">
    						</view>
                <view class="slider-success-text"><slot name="successText">{{successText}}</slot></view>
              </view>
              <view class="slider-btn slider-move-btn"
    						:style="{'left': sliderMoveLeft}" 
    						:class="{'slider-shock':shock}"
    						@touchstart.stop="sliderTouchStart"
    						@touchmove.stop="sliderTouchMove"
    						@touchend.stop="sliderEnd"
    						@mousedown="sliderStart"
    					>
    						<i>&nbsp;</i>
    						<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAcAgMAAABuexVFAAAACVBMVEUAAADCwsL9/f1P0DqbAAAAAXRSTlMAQObYZgAAAB1JREFUGNNjCGVgYGANABKhyMwoEHMBkIgaZWIwAdyJJQnaJRg5AAAAAElFTkSuQmCC"
    									alt="">
    						<i>&nbsp;</i>
    					</view>
            </view>
            <view style="position:relative;">
    					<!-- #ifndef MP -->
              <view class="slider-refresh" style="font-size: 29px;" @click.stop="" @mouseenter="questionMessage = true" @mouseleave="questionMessage = false">
              <!-- #endif -->
    					<!-- #ifdef MP -->
    					<view class="slider-refresh" style="font-size: 29px;" @click.stop="" @touchstart="questionMessage = true" @touchend="questionMessage = false">
    					<!-- #endif -->
    					  <view class="slider-tools-btn lang-tips">
    					    <view class="lang-tips-item lang-tips-bottom" v-show="questionMessage">
    								<slot name="question">
    									<view v-if="question">{{question}}</view>
    									<view v-else>
    										拖动滑块完成拼图,欢迎提建议!
    									</view>
    								</slot>
    								</view>
    					    <image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAVgSURBVHgBzVrZUdxMEG7EcjzuH8EvIvA6Ai8ZmAhgqziLByACRASGB4qzChwBZOAlgl1HYDmDfaIoTn/ferQlNSNpRpJd/qrG8s7ZPdPT1zAlDeDq6qr98PDweWpqqoPyP6o6KG1TEsRoi19fX7/jO8D3bmtrK5aamJKKINHPz887b29vXRapADAyxNjDOsx4M5AQjkV3JbvDtQBmrl9eXg58GfFi4OLiYr9pwjWCIIjW1tYOXPs7MXBychJOT0/f4Lg7eX3Q1seH8t0Hk8P5+flRr9cbsY2ndn9/H7ZarRBtvCefULoFS/KuLLqcRikDl5eXyzjaQ7Hv+ggLHYHYw4RYV3BT8Olix/fxDW1zo/Q2NjZui+YpZMCITJQz+QEmP5QGAGZW8hgpE6lcBvKIh6gczc3NRb47XgZzIhEIXtZtRUxYGTg7O/uMz42laa+pXc8D1qaS+KLrcQdXVldXv+r6dwxwJ8DxQLIyT1lfwqXqy1/A8fFxBxf+m4WGj/pit/RgEK8HCvT+4vb29lAcQa3z9PQU4vInWiv2YZ5rYSOXDC0J2tSE+H5M982cQI7cO4tNiZEbQXXe+hgrmzhhbo6PRDNgROdHujOt4/r6ek8cYI6dOxSWdKVPtIR5nU709PT0EP13UlWj2dnZhUSJBKmGSC/E3RIHkHkjs6FD9xCa7Nv5+XnHoa9Q48lvtZ2AjuNu8iNICLCor8j1qG33houC0GsUGqJYtbVRfyMOMDt9oNbbobiO/2/qumpcDLn/Kg6gERK187QVGP/f5uZmD2UJ/1+g7KqhIWR82WUNcwetpzBmgBypMZE4ApohQwTvDYje1f148ciY6rsijqDLotb9xG9gLGBHdb4TR1gcvKO8vtio65KxuaC/pcZ2KUY8ga5q6Hv65BnZL9IuljZnt5x3gZ5uuo5RYMAwMF0JBpx3n1CTFqpGc9ppePlTDEfV2h2cavBBVQ7EA1C1Pco2C92Nor5QtZn7gjHO1t3Q1ldVYQuThOkaMPRTPGDEbbesH3a/q608L7x4gIES6EuP/0BfKEx3mpmZiaUhkGjOT01lCfxjH2VBMMp7fHxMV7UD3akpPx86/gsNHMpVTtYi8g3gLbS9Z6BB5IoVjZqroSwDGchwlZjoPwGqaBOsR1IBNtp4B8jApAG6tS2e6s0FhvC+1ABjDFUV8wQyqow3W/4AmojmtMbE7zjAP1ptLso/CuaUVNX3QBsTbdiqwsdCuyJx4FJrDALo1tt0ZeIkSU34WGgX0A3R6ph2pEXdiuion240vnYkNeBqoT3QVb+HXGOclcBu3UF0Jh1MfBBJDSRvBphrVJYedIHJ3KUxdq/Hhsz42pmIx2QEKoHEw+QPaIXx8wZz/agjlraoL3FDxgxQjHTEA+xXXZQ7rxYMwdCKVICJ1zO7TycwcUMmroTtFLDovlQA7lOT1jwStfvpbMmEgZxT2K0iSkxgSXYzOLf3PYBy2dHZEpPYiidr6UEgmAGNjpG93QAePRgZM893MF/P0yTKdHDFbMlCusI5uQtiFl2zaXXBOAI03IhDcvedO80O6LinqpmIGtTRTK4wYmNLlPVsp5j7wIFdiCy6t/JrYhmM6uV67zZJJ3Qz9EgB8piQ36nCqKmghLuOE47EkmYpIp4ofeQzrzVXYs/hxChRlYdqh/dmyvwe5r0umsf5mdXIZZg7EbxP45IM+ScFTA6kn1kZKKGtQ5+LHm/R6z5f8DHXUiPPrIqRPJFqCmNb5BNyev+pQdFrYg1Ufm+u/MceqYdqeq4dqQAG+UxlViE8QWUG0kiYMXlWRnS0wmGqCx87SCAN4U9GgQykmshB/QKLND1fMNqj0gAAAABJRU5ErkJggg==" alt="">
    					  </view>
    					</view>
    					<view class="slider-refresh" style="left: 35px;" @click.stop="refresh" @mouseenter="refreshMessage = true" @mouseleave="refreshMessage = false">
                <view class="slider-tools-btn lang-tips" style="width:24px;height: 24px;margin-top: -2px;"> <!-- 刷新 -->
                  <view class="lang-tips-item lang-tips-bottom" v-show="refreshMessage"> 刷新</view>
                  <image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPZSURBVHgB7VpLTuNAEC2b8NlNboDnBJgbJCdgOAFkAQixGDgB9gkIC4T4SIETTOYEhBOQOcF4buAVQojPvGfZGbv9iWMbE0vzpMam3e7Uq66u7q6yJhXh/PzcwKWjaZqJsop7E6XtF8JlwTPn7e3tF64jXMf7+/uOlIAmJTAYDNovLy/f39/fOyxSACSCd29A5r4ImUIEfG1buq5vyD8NlwbI3Ly+vtp5iFAGtpuJQKBxaOtQKhRcgQvF9Hd2duy0BldXV8eQQwMBKzeBs7Mzs9Vq/cCtkdYGpkBzuIcAY9r3ysqK2+v1aPse+cfHRwN9GL65rUHjnYyf5FzpqqNB4VFvodi5CVxeXtLOLUnWuoNn/eXl5dtA2LwIJj4IH0uyYthfb29vbxgWnve5CYRfUgVHsdD5rVQAkNnOIHKEZ1/CcgQE9KxO04SHxk+XlpbWqxKegDD0RF2UpD5PUpQoLUkBzSbhJRd1R/wx+QD49r6N0XD80ZiKxBGgbdKulWoXM7/7UcKHQdOAonp52sYI0FuA/Z1S7Ql/cHAwlpoAb7Wap12MAP28xCeSXafwGY4jhggBmo76IicsJmtfasIswhPqCFjK/w78uyU1YVbhiYkXovZh+1vKc2vWxako+PvcHuDWzvnKiH+0UAeW4rocmM5XmXNMTChJ+9IAeASwaPHwYYQfYKX9KQ2ARwB7cDNcyV1lXbZfFh4B/2AyAba5jdA+4RGAwJFtMvfy0hB4BGAyplLvSEMQeKHICJSNFNQJXRqO/wQ+GwGBiM/3D9uNQEDAUeoNaQg8AoxVRip13ZSGIFgHIgsXFrYNaQiClXgYruTCxrOxNAAeAX/hckL17efn521pACZuVA0oNcWMJgQQiI0c3BmAvbi4+CZzjgkB7v8Twnon8z4XpkUlDMyFXCG+z0KEACczo75Km8Pr6+stmVPEwus0GWj9QaKrMZNz3d3d3VoPOkyq+IvqKG2LH9vMcS4gPrMp0f1RG5P6zj/81wKOOuKjDyAwQPmN/VknqV3ibtSPg6qm5JGAZzqUDwZD+wg03CjVnaS2mRmahGBXgD7CLnbVkQvffAe4jblvmPB6kglPTTFlkHCkohRTjuznUVqAOVeSzzebk5THDopVJFGdQ/BIki8JudOsfvCXiQ8jtTNk3WG797gdLSwsuIuLi044zfr09MTkiclVHte1rOw++hqjr81pSpk5U59hUlWBhO28OYmynxpUucBxK3PKPdkszqHUxx6hRDXTUoXWCMZhcRkWSZQTpQiEEf7cBtc1FAP3RqiJC2EpIF3hH54Coe1hWVf8FxUNF4vY5AiIAAAAAElFTkSuQmCC" alt="">
                </view>
              </view>
            </view>
          </view>
        </view>
      </view>
    </template>
    
    <script>
    export default {
      name: 'SliderCaptcha',
      props:
     {
        value: {
    			type: Boolean,
    			defalut: false
    		},
    		modelValue: {
    			type: Boolean,
    			defalut: false
    		},
        loading: {
          type: Boolean,
          defalut: false
        },
        title: {
          type: String,
          default: '滑块安全验证'
        },
        tips: {
          type: String,
          default: '拖动下方滑块完成拼图'
        },
        successText: {
          type: String,
          default: '验证成功'
        },
        errorText:{
          type: String,
          default: '是不是太难了,咱换一个'
        },
    		question: {
    			type: String,
    			default: ''
    		},
        options: {
          type: Object,
          default() {
            return {
              sliderImg:'',
    		  rotateImg:'',
              sliderKey:'',
    		  bgImageWidth:'',
    		  bgImageHeight:'',
    		  templateImageWidth:'',
    		  templateImageHeight:'',
              sliderY: ''
            }
          }
        }
      },
      watch: {
        value: {
    	  handler: function(val) {
    		this.init(val)
    	  },
    	  immediate: true
    	},
    	modelValue: {
    	  handler: function(val) {
    		this.init(val)
    	  },
    	  immediate: true
    	},
        options: {
          handler: function(option) {
            this.clear()
            this.sliderImg = option.sliderImg
    		this.rotateImg = option.rotateImg
            this.sliderKey = option.sliderKey
    		this.bgImageWidth = option.bgImageWidth 
    		this.bgImageHeight = option.bgImageHeight 
    		this.templateImageWidth = option.templateImageWidth
    		this.templateImageHeight = option.templateImageHeight
    		this.sliderKey =  option.sliderKey
            this.sliderY = option.sliderY
          },
          deep:true,
          immediate: true
        }
      },
      data() {
        return {
          dialogVisible: false,
          mask: false,
          success: false,
          failed: false,
          sliderImg:'',
    	  rotateImg:'',
    	  bgImageWidth:'',
    	  bgImageHeight:'',
    	  templateImageWidth:'',
    	  templateImageHeight:'',
          sliderKey: '',
          sliderX: 27,
          sliderY: '',
    	  dynamicStyles:{
    		  transform:'',
    	  },
    	  currentCaptchaData:{
    	  	startTime: new Date(),
    	  	trackArr: [],
    	  	movePercent: 0,
    	  	clickCount: 0,
    	  	bgImageWidth: '',
    	  	bgImageHeight: '',
    	  	templateImageWidth: '',
    	  	templateImageHeight:'',
    	  	end: '',
    		pageX: 0,
    		left : 0 
    	  },
          sliderMoveDrawLeft: '27px',
          sliderMoveLeft: '20px',
          shock: false,
          tipEvents: {},
    			sliderMoveX: 0,
    			questionMessage: false,
    			refreshMessage: false,
        }
      },
      methods: {
        init(open)
    		{
    			this.dialogVisible = open
    			if (open) {
    				this.clear()
    			}
    			//初始化校验
    		},
        clear() {
          this.mask = false
          this.success = false
          this.failed = false
          this.sliderImg = ''
    	  this.rotateImg = ''
          this.sliderKey = ''
    	  this.bgImageWidth=''
    	  this.bgImageHeight=''
    	  this.templateImageWidth = ''
    	  this.templateImageHeight = ''
          this.sliderX = 27
          this.sliderMoveDrawLeft = '27px',
          this.sliderMoveLeft = '20px',
          this.sliderY = ''
        },
        check(sliderKey,type)
        {
    		const data = {
    			bgImageWidth: this.currentCaptchaData.bgImageWidth,
    			bgImageHeight: this.currentCaptchaData.bgImageHeight,
    			templateImageWidth: this.currentCaptchaData.templateImageWidth,
    			templateImageHeight: this.currentCaptchaData.templateImageHeight,
    			startTime: this.currentCaptchaData.startTime,
    			stopTime: this.currentCaptchaData.stopTime,
    			trackList: this.currentCaptchaData.trackArr
    		};
    		if (type === 'ROTATE') {
    			data.bgImageWidth = this.currentCaptchaData.end;
    		}
    		this.currentCaptchaData = undefined;
    		this.$emit('check', sliderKey, data, this.done, this.error)
        },
        done()
        {
          this.success = true
        },
        error() {
          this.failed = true
          this.mask = true
          this.shock = true
          setTimeout(() => {
            this.shock = false
            this.$emit('error')
          }, 1000)
        },
        close() {
          this.dialogVisible = false
          this.$emit('input', this.dialogVisible)
          this.$emit('update:modelValue', this.dialogVisible)
          this.$emit('close')
        },
        refresh()
        {
          this.$emit('refresh')
        },
    		sliderTouchStart(e) {
    			// 移动触摸移动
    			const that = this
    			const slider = e.target
    			//sliderMoveX是鼠标触摸下不在变化  e.touches[0].clientX会变化 
    			//slider.offsetLeft固定为20
    			that.sliderMoveX = e.touches[0].clientX - slider.offsetLeft
    			that.dynamicStyles.transform = "rotate(0deg)"
    			that.currentCaptchaData.end  =  e.touches[0].clientX
    		},
    		sliderTouchMove(e) {
    			const that = this
    			const left = e.touches[0].clientX - that.sliderMoveX
    			
    			if (left >= 20 && left <= 280) {
    				that.sliderMoveDrawLeft = 5 + left + 'px';
    				that.sliderMoveLeft = left + 'px';
    				that.dynamicStyles.transform = "rotate(" + ((left-20) / (260 / 360)) + "deg)"
    				that.currentCaptchaData.bgImageWidth = that.bgImageWidth
    				that.currentCaptchaData.bgImageHeight = that.bgImageHeight
    				that.currentCaptchaData.templateImageWidth = that.templateImageWidth
    				that.currentCaptchaData.templateImageHeight =that.templateImageHeight
    				//初始大概有10左右的误差在里面
    				that.currentCaptchaData.end  = 280 - 20 - 10
    				that.currentCaptchaData.stopTime = new Date(new Date().getTime()-that.currentCaptchaData.startTime.getTime())
    				
    				const trackArr = that.currentCaptchaData.trackArr;
    				trackArr.push({
    						x: left-20,
    						y: that.bgImageHeight,
    						type: "down",
    						t: (new Date().getTime() - that.currentCaptchaData.startTime.getTime())
    				});
    			}
    		},
    		sliderEnd(){
    	
    			this.check(this.sliderKey, 'ROTATE');
    		},
    		sliderStart(e) {
    			var that = this
    			const slider = e.target // 获取目标元素
    			// 算出鼠标相对元素的位置
    			that.sliderMoveX = e.clientX - slider.offsetLeft
    			
    			document.onmousemove = (e) => {
    				const left = e.clientX - that.sliderMoveX
    				if (left >= 20 && left <= 280) {
    					//   slider.style.left = left + 'px'
    					that.sliderMoveDrawLeft = 5 + left + 'px';
    					that.sliderMoveLeft = left + 'px';
    					that.sliderX = 5 + left;
    				}
    			}
    			
    			document.onmouseup = (e) => {
    				document.onmousemove = null
    				document.onmouseup = null
    				this.check(this.sliderKey, 'ROTATE');
    			}
    		}
      }
    }
    </script>
    <style scoped>
    a + a {
    	margin-left: 10px;
    }
    /* 弹窗开始  */
    .lang-dialog__wrapper {
      position: fixed;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      overflow: hidden;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, .5);
      text-align: left;
      z-index: 88888888;
    }
    
    .lang-dialog {
      position: relative;
      margin: 0 auto 50px;
      border-radius: 2px;
      box-shadow: 0 1px 3px rgba(0, 0, 0, 30%);
      box-sizing: border-box;
      background: #FFF;
      width: 50%;
      margin-top: calc(50vh - 185px);
      width: 360px;
    }
    
    .lang-dialog__header {
      padding: 20px 10px 10px;
    }
    
    .lang-dialog__title {
      line-height: 20px;
      font-size: 18px;
      color: #525252;
    }
    
    .lang-dialog-header-btn {
      position: absolute;
      top: 20px;
      right: 20px;
      padding: 0;
      background: 0 0;
      border: none;
      outline: 0;
      cursor: pointer;
      font-size: 16px
    }
    
    .lang-icon-close {
      color: rgba(0, 0, 0, 0.68);
      font-size: 20px;
    }
    
    .lang-icon-close:before {
      content: '×';
    }
    
    .lang-icon-close:hover {
      color: rgba(0, 0, 0, 0.88);
    }
    
    .lang-dialog__body {
      color: #828282;
      font-size: 14px;
      word-break: break-all;
    	padding: 0 10px;
    	height: 320px;
    }
    
    .loading-transparent-mask{
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: transparent;
      z-index: 999;
    }
    
    /* 弹窗结束  */
    /* loading start */
    .loading-body {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(255, 255, 255, 0.6);
      z-index: 999;
    }
    
    .loading {
      position: relative;
      display: inline-block;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      border-top: 2px solid transparent;
      border-bottom: 2px solid transparent;
      border-left: 2px solid #409eff;
      border-right: 2px solid #409eff;
      animation: loading 1s infinite linear;
    }
    
    @keyframes loading {
      to {
    	transform: rotate(360deg);
      }
    }
    
    .slider-loading {
      position: absolute;
      top: calc(50% - 15px);
      left: calc(50% - 15px);
      z-index: 999;
    }
    
    /* loading end */
    .tips {
      color: #525252;
      line-height: 36px;
      font-size: 18px;
    }
    
    .slider-body {
      position: relative;
      width: 100%;
      height: 245px;
    }
    
    .slider-bg {
      position: absolute;
      top: 0;
      left: 0;
      background-position: 0px 0px;
      background-size: 340px 251px;
      background-repeat: no-repeat;
      width: 340px;
      height: 191px;
      overflow: hidden;
    }
    
    .slider-draw {
      position: absolute;
      left: 27px;
      width: 50px;
      height: 50px;
      background: #fff;
      background-position: -5px -196px;
      background-size: 340px 251px;
      background-repeat: no-repeat;
      z-index: 1;
      cursor: pointer;
      opacity: 1;
      box-shadow: 0px 0px 10px 2px #000000;
    }
    
    .slider-progress {
      position: absolute;
      width: 100%;
      background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAqoAAAJsCAYAAAA1NIHDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAE7mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4xLWMwMDAgNzkuOWNjYzRkZSwgMjAyMi8wMy8xNC0xMToyNjoxOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIzLjMgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMi0xMi0wNlQxNzoyOToxNSswODowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjItMTItMDZUMTc6MzI6MDIrMDg6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjItMTItMDZUMTc6MzI6MDIrMDg6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmJhYmMyMDIzLTdkNDgtZTg0Zi05ZTFlLTNkZDQ2YTFiMWNkZCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpiYWJjMjAyMy03ZDQ4LWU4NGYtOWUxZS0zZGQ0NmExYjFjZGQiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpiYWJjMjAyMy03ZDQ4LWU4NGYtOWUxZS0zZGQ0NmExYjFjZGQiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhYmMyMDIzLTdkNDgtZTg0Zi05ZTFlLTNkZDQ2YTFiMWNkZCIgc3RFdnQ6d2hlbj0iMjAyMi0xMi0wNlQxNzoyOToxNSswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIzLjMgKFdpbmRvd3MpIi8+IDwvcmRmOlNlcT4gPC94bXBNTTpIaXN0b3J5PiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmbOvQUAABmXSURBVHic7d1rrKVXfd/x33qefebqwdgJl0ZpwdzKtZiYijQSzBhQFUUggsmxkrYUqZHavKERSGmlqtV42qpSG5W+SFW1jXpRkhdoTnAohCpVQOMhTYsru5AmQLhpEEJCwRCwPTOec/Zl9cUZD/axPZ7Lsfd/cz4faTQ6R/vZe82MNPqe9ay1ntZ7PxQAAChmWPYAAADgyQhVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAkoQoAQElCFQCAkoQqAAAlCVUAAEoSqgAAlCRUAQAoSagCAFCSUAUAoCShCgBASUIVAICShCoAACUJVQAAShKqAACUJFQBAChJqAIAUJJQBQCgJKEKAEBJQhUAgJKEKgAAJQlVAABKEqoAAJQkVAEAKEmoAgBQklAFAKAk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');
      background-position: 0px -213.512px;
      background-size: 345.06px 313.69px;
      width: 340px;
      height: 16px;
      left: 0px;
      top: 210px;
      z-index: 1;
      opacity: 1;
    }
    
    .slider-success {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 4;
      background: hsla(0, 0%, 100%, .8);
    }
    
    .slider-success .slider-success-icon {
      width: 64px;
      height: 64px;
      margin: 15% auto 0;
    }
    
    .slider-success .slider-success-icon image {
      width: 100%;
      height: 100%;
    }
    
    .slider-success .slider-success-text {
      color: #1bc300;
      text-align: center;
      margin-top: 16px;
      font-size: 18px;
    }
    
    .slider-btn {
      position: absolute;
      left: 20px;
      top: 200px;
      z-index: 2;
      width: 65px;
      height: 35px;
      line-height: 35px;
      background-color: rgb(0, 87, 212);
      box-shadow: rgba(0, 87, 212, 50%) 0px 0px 5.05952px 0.505952px;
      position: absolute;
      text-align: center;
      border-radius: 999px;
      font-size: 0;
      cursor: pointer;
      opacity: 1;
      z-index: 1;
      -moz-user-select: none;
      /* 禁止双击节点被选中 */
      -o-user-select: none;
      -khtml-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
      user-select: none;
      -moz-user-drag: none;
      /* 禁止被拖动 */
      -webkit-user-drag: none;
      /* 禁止被拖动 */
    }
    
    .slider-btn i {
      display: inline-block;
      width: 0;
      vertical-align: middle;
    }
    
    .slider-btn image {
      width: 18px;
      height: 13px;
      -moz-user-select: none;
      /* 禁止双击节点被选中 */
      -o-user-select: none;
      -khtml-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
      user-select: none;
      -moz-user-drag: none;
      /* 禁止被拖动 */
      -webkit-user-drag: none;
      /* 禁止被拖动 */
      pointer-events: none;
      display: inline-block;
      vertical-align: middle;
    }
    
    .slider-refresh {
      position: absolute;
      cursor: pointer;
      left: 0px;
      top: 0px;
      z-index: 2;
      font-size: 30px;
      height: 30px;
      line-height: 30px;
    }
    
    .slider-tools-btn {
      position: relative;
      width: 25px;
      height: 25px;
    }
    
    /* tips */
    .lang-tips a {
      text-decoration: none;
      color: #409eff;
    }
    
    .lang-tips-item {
      position: fixed;
      font-size: 14px;
      color: #525252;
      max-width: 340px;
      margin-top: 40px;
      margin-left: 0;
      background: white;
      padding: 7px 10px;
      border-radius: 4px;
    	margin-left: -8px;
    }
    
    .lang-tips-left {
      margin-top: -10px;
      margin-left: -140px;
    }
    
    .lang-tips-left:after {
      content: "";
      position: absolute;
      border-top: 10px solid transparent;
      border-bottom: 10px solid transparent;
      border-left: 10px solid #000;
      border-right: 10px solid transparent;
      right: -18px;
      top: 12px;
    }
    
    .lang-tips-bottom {
      margin-top: 50px;
    }
    
    .lang-tips-bottom:after {
      content: "";
      position: absolute;
      border-top: 10px solid transparent;
      border-bottom: 10px solid #fff;
      border-left: 10px solid transparent;
      border-right: 10px solid transparent;
      /* right: calc(50% - 10px); */
      left: 10px;
      top: -20px;
    }
    
    /* tips */
    .slider-tools-btn image {
      width: 100%;
      height: 100%;
    }
    
    .slider-shock {
      animation-delay: 0s;
      animation-name: shock;
      animation-duration: .1s;
      animation-iteration-count: 5;
      animation-direction: normal;
      animation-timing-function: linear;
    }
    .view-slider{
    	 position: relative;
    	 height: 200px; /* 设置为所需的高度 */
    }
    .rotate-img {
        width: 100%;
    	height: 100%;
    	object-fit: cover; /* 确保图片覆盖整个容器 */
    }
    .rotate-image-img{
    	position: absolute;
    	top: 50%;
    	left: 50%;
    	width: 115px;
    	height: 200px;
    	transform: translate(-50%, -50%); /* 使图片位于容器的中心 */
    	.rotate-image-image{
    		height: 200px;
    	}
    }
    
    @keyframes shock {
      0% {
    	margin-left: 2px;
      }
    
      100% {
    	margin-left: -2px;
      }
    }
    </style
    

    props中写入前端传递参数,将参数绑定相关组件上,旋转验证码最重要的是滑块移动的X轴的距离占整个背景图的距离比例 乘以360代表旋转的角度采用动态样式的方式处理

    初始值 that.dynamicStyles.transform = “rotate(0deg)”

    随着X轴的变化旋转的角度跟着改变
    that.dynamicStyles.transform = “rotate(” + ((left-20) / (260 / 360)) + “deg)”,入参和后台保持一致

    在这里插入图片描述

    2.应用代码如下
    <template>
      <view>
        <button @click="open">验证</button>
        <slider-captcha 
          v-model="visible"
          :options="options"
          :loading="loading"
          @check="check"
          @close="close"
          @refresh="getSliderOptions"
          @error="getSliderOptions"
        >
                <!-- vue2 -->
          <view slot="title">自定义标题-安全验证</view>
          <view slot="successText">自定义成功提示-登录中</view>
          <view slot="errorText">自定义错误提示-是不是太难了换一个</view>
          <view slot="tips">自定义提示拖动下方滑块完成拼图</view>
                <!-- <view slot="question">自定义提示</view> -->
                <!-- vue2 -->
    
                <!-- vue3 -->
                <template #title>自定义标题-安全验证</template>
                <template #successText>自定义成功提示-登录中</template>
                <template #errorText>自定义错误提示-是不是太难了换一个</template>
                <template #tips>自定义提示拖动下方滑块完成拼图</template>
                <!-- <template #question>自定义提示</template> -->
                <!-- vue3 -->
        </slider-captcha> 
      </view>
    </template>
    
    <script>
    import SliderCaptcha from '@/components/kkokk-slider-captcha/slider-captcha.vue'
    export default {
      components:{SliderCaptcha},
      data(){
        return {
          visible: false,
          loading: false,
          options: {}
        }
      },
      methods: {
        // 打开触发
        open() {
          this.visible = true
          this.getSliderOptions()
        },
        // 验证
        check(sliderKey, data, done, error)
        {	
    		
    		 const sendParam = {
    		            id: sliderKey,
    		            data: data
    		};
          // 这里是验证是否成功的接口
                this.loading = true
                uni.request({
                    url: '/wxminiCheck', 
                    header: {
                        // 'Content-Type': 'application/x-www-form-urlencoded'
                         'Content-Type': 'application/json' //自定义请求头信息
                    },
                    data: sendParam,
                    method:'POST',//请求方式,必须为大写
                    success: (res) => {
                        this.loading = false
                        done()
                    },
                    fail: () => {
                        this.loading = false
                        error()
                    }
                })
        },
        // 关闭触发
        close() {
    
        },
        // 获取滑块验证参数
        getSliderOptions()
        {
          this.loading = true
                uni.request({
                    url: '/wxminiCaptcha', 
                    header: {
                        // 'Content-Type': 'application/x-www-form-urlencoded'
                         'Content-Type': 'application/json' //自定义请求头信息
                    },
                    method:'GET',//请求方式,必须为大写
                    success: (res) => {
                        const {backgroundImage, templateImage ,backgroundImageWidth,backgroundImageHeight,templateImageWidth ,templateImageHeight,key, y} = res.data.data.captcha
                        const seqId  =  res.data.data.id
    					this.options = {
                            sliderImg: backgroundImage,
    						rotateImg: templateImage,
    						bgImageWidth: backgroundImageWidth,
    						bgImageHeight: backgroundImageHeight,
    						templateImageWidth: templateImageWidth,
    						templateImageHeight: templateImageHeight,
                            sliderKey: seqId,
                            sliderY: y
                        }
                        this.loading = false
                    }
                })
        }
      }
    }
    </script>
    
    
    3.最终效果图

    在这里插入图片描述
    完成前端对接tianai-captcha滑块验证码处理

    参考

    https://ext.dcloud.net.cn/plugin?id=15780
    
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐