VerifySlide.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <template>
  2. <div style="position: relative;">
  3. <div
  4. v-if="type === '2'"
  5. class="verify-img-out"
  6. :style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
  7. >
  8. <div
  9. class="verify-img-panel"
  10. :style="{width: setSize.imgWidth,
  11. height: setSize.imgHeight,}"
  12. >
  13. <img :src="backImgBase?('data:image/png;base64,'+backImgBase):defaultImg" alt="" style="width:100%;height:100%;display:block">
  14. <div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh" />
  15. </div>
  16. <transition name="tips">
  17. <span v-if="tipWords" class="verify-tips" :class="passFlag ?'suc-bg':'err-bg'">{{ tipWords }}</span>
  18. </transition>
  19. </div>
  20. </div>
  21. <!-- 公共部分 -->
  22. <div
  23. class="verify-bar-area"
  24. :style="{width: setSize.imgWidth,
  25. height: barSize.height,
  26. 'line-height':barSize.height}"
  27. >
  28. <span class="verify-msg" v-text="text" />
  29. <div
  30. class="verify-left-bar"
  31. :style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}"
  32. >
  33. <span class="verify-msg" v-text="finishText" />
  34. <div
  35. class="verify-move-block"
  36. :style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}"
  37. @touchstart="start"
  38. @mousedown="start"
  39. >
  40. <i
  41. :class="['verify-icon iconfont', iconClass]"
  42. :style="{color: iconColor}"
  43. />
  44. <div
  45. v-if="type === '2'"
  46. class="verify-sub-block"
  47. :style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
  48. 'height': setSize.imgHeight,
  49. 'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
  50. 'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
  51. }"
  52. >
  53. <img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block">
  54. </div>
  55. </div>
  56. </div>
  57. </div>
  58. </div>
  59. </template>
  60. <script type="text/babel">
  61. /**
  62. * VerifySlide
  63. * @description 滑块
  64. * */
  65. import { aesEncrypt } from '@/utils/ase'
  66. import { resetSize } from './../utils/util'
  67. import { reqGet, reqCheck } from '@/api/login'
  68. // "captchaType":"blockPuzzle",
  69. export default {
  70. name: 'VerifySlide',
  71. props: {
  72. captchaType: {
  73. type: String,
  74. },
  75. type: {
  76. type: String,
  77. default: '1'
  78. },
  79. // 弹出式pop,固定fixed
  80. mode: {
  81. type: String,
  82. default: 'fixed'
  83. },
  84. vSpace: {
  85. type: Number,
  86. default: 5
  87. },
  88. explain: {
  89. type: String,
  90. default: '向右滑动完成验证'
  91. },
  92. imgSize: {
  93. type: Object,
  94. default() {
  95. return {
  96. width: '310px',
  97. height: '155px'
  98. }
  99. }
  100. },
  101. blockSize: {
  102. type: Object,
  103. default() {
  104. return {
  105. width: '50px',
  106. height: '50px'
  107. }
  108. }
  109. },
  110. barSize: {
  111. type: Object,
  112. default() {
  113. return {
  114. width: '310px',
  115. height: '40px'
  116. }
  117. }
  118. },
  119. defaultImg: {
  120. type: String,
  121. default: ''
  122. }
  123. },
  124. data() {
  125. return {
  126. secretKey: '', // 后端返回的加密秘钥 字段
  127. passFlag: '', // 是否通过的标识
  128. backImgBase: '', // 验证码背景图片
  129. blockBackImgBase: '', // 验证滑块的背景图片
  130. backToken: '', // 后端返回的唯一token值
  131. startMoveTime: '', // 移动开始的时间
  132. endMovetime: '', // 移动结束的时间
  133. tipsBackColor: '', // 提示词的背景颜色
  134. tipWords: '',
  135. text: '',
  136. finishText: '',
  137. setSize: {
  138. imgHeight: 0,
  139. imgWidth: 0,
  140. barHeight: 0,
  141. barWidth: 0
  142. },
  143. top: 0,
  144. left: 0,
  145. moveBlockLeft: undefined,
  146. leftBarWidth: undefined,
  147. // 移动中样式
  148. moveBlockBackgroundColor: undefined,
  149. leftBarBorderColor: '#ddd',
  150. iconColor: undefined,
  151. iconClass: 'icon-right',
  152. status: false, // 鼠标状态
  153. isEnd: false, // 是够验证完成
  154. showRefresh: true,
  155. transitionLeft: '',
  156. transitionWidth: ''
  157. }
  158. },
  159. computed: {
  160. barArea() {
  161. return this.$el.querySelector('.verify-bar-area')
  162. },
  163. resetSize() {
  164. return resetSize
  165. }
  166. },
  167. watch: {
  168. // type变化则全面刷新
  169. type: {
  170. immediate: true,
  171. handler() {
  172. this.init()
  173. }
  174. }
  175. },
  176. mounted() {
  177. // 禁止拖拽
  178. this.$el.onselectstart = function() {
  179. return false
  180. }
  181. console.log(this.defaultImg)
  182. },
  183. methods: {
  184. init() {
  185. this.text = this.explain
  186. this.getPictrue()
  187. this.$nextTick(() => {
  188. const setSize = this.resetSize(this) // 重新设置宽度高度
  189. for (const key in setSize) {
  190. this.$set(this.setSize, key, setSize[key])
  191. }
  192. this.$parent.$emit('ready', this)
  193. })
  194. const _this = this
  195. window.removeEventListener('touchmove', function(e) {
  196. _this.move(e)
  197. })
  198. window.removeEventListener('mousemove', function(e) {
  199. _this.move(e)
  200. })
  201. // 鼠标松开
  202. window.removeEventListener('touchend', function() {
  203. _this.end()
  204. })
  205. window.removeEventListener('mouseup', function() {
  206. _this.end()
  207. })
  208. window.addEventListener('touchmove', function(e) {
  209. _this.move(e)
  210. })
  211. window.addEventListener('mousemove', function(e) {
  212. _this.move(e)
  213. })
  214. // 鼠标松开
  215. window.addEventListener('touchend', function() {
  216. _this.end()
  217. })
  218. window.addEventListener('mouseup', function() {
  219. _this.end()
  220. })
  221. },
  222. // 鼠标按下
  223. start: function(e) {
  224. let x
  225. e = e || window.event
  226. if (!e.touches) { // 兼容PC端
  227. x = e.clientX
  228. } else { // 兼容移动端
  229. x = e.touches[0].pageX
  230. }
  231. this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left)
  232. this.startMoveTime = +new Date() // 开始滑动的时间
  233. if (this.isEnd === false) {
  234. this.text = ''
  235. this.moveBlockBackgroundColor = '#337ab7'
  236. this.leftBarBorderColor = '#337AB7'
  237. this.iconColor = '#fff'
  238. e.stopPropagation()
  239. this.status = true
  240. }
  241. },
  242. // 鼠标移动
  243. move: function(e) {
  244. let x
  245. e = e || window.event
  246. if (this.status && this.isEnd === false) {
  247. if (!e.touches) { // 兼容PC端
  248. x = e.clientX
  249. } else { // 兼容移动端
  250. x = e.touches[0].pageX
  251. }
  252. const bar_area_left = this.barArea.getBoundingClientRect().left
  253. let move_block_left = x - bar_area_left // 小方块相对于父元素的left值
  254. if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) {
  255. move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
  256. }
  257. if (move_block_left <= 0) {
  258. move_block_left = parseInt(parseInt(this.blockSize.width) / 2)
  259. }
  260. // 拖动后小方块的left值
  261. this.moveBlockLeft = (move_block_left - this.startLeft) + 'px'
  262. this.leftBarWidth = (move_block_left - this.startLeft) + 'px'
  263. }
  264. },
  265. // 鼠标松开
  266. end: function() {
  267. this.endMovetime = +new Date()
  268. const _this = this
  269. // 判断是否重合
  270. if (this.status && this.isEnd === false) {
  271. let moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''))
  272. moveLeftDistance = moveLeftDistance * 310 / parseInt(this.setSize.imgWidth)
  273. const data = {
  274. captchaType: this.captchaType,
  275. 'pointJson': this.secretKey ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
  276. 'token': this.backToken
  277. }
  278. reqCheck(data).then(res => {
  279. if (res.repCode === '0000') {
  280. this.moveBlockBackgroundColor = '#5cb85c'
  281. this.leftBarBorderColor = '#5cb85c'
  282. this.iconColor = '#fff'
  283. this.iconClass = 'icon-check'
  284. this.showRefresh = false
  285. this.isEnd = true
  286. if (this.mode === 'pop') {
  287. setTimeout(() => {
  288. this.$parent.clickShow = false
  289. this.refresh()
  290. }, 1500)
  291. }
  292. this.passFlag = true
  293. this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功`
  294. const captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify({
  295. x: moveLeftDistance,
  296. y: 5.0
  297. }), this.secretKey) : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
  298. setTimeout(() => {
  299. this.tipWords = ''
  300. this.$parent.closeBox()
  301. this.$parent.$emit('success', { captchaVerification })
  302. }, 1000)
  303. } else {
  304. this.moveBlockBackgroundColor = '#d9534f'
  305. this.leftBarBorderColor = '#d9534f'
  306. this.iconColor = '#fff'
  307. this.iconClass = 'icon-close'
  308. this.passFlag = false
  309. setTimeout(function() {
  310. _this.refresh()
  311. }, 1000)
  312. this.$parent.$emit('error', this)
  313. this.tipWords = '验证失败'
  314. setTimeout(() => {
  315. this.tipWords = ''
  316. }, 1000)
  317. }
  318. })
  319. this.status = false
  320. }
  321. },
  322. refresh: function() {
  323. this.showRefresh = true
  324. this.finishText = ''
  325. this.transitionLeft = 'left .3s'
  326. this.moveBlockLeft = 0
  327. this.leftBarWidth = undefined
  328. this.transitionWidth = 'width .3s'
  329. this.leftBarBorderColor = '#ddd'
  330. this.moveBlockBackgroundColor = '#fff'
  331. this.iconColor = '#000'
  332. this.iconClass = 'icon-right'
  333. this.isEnd = false
  334. this.getPictrue()
  335. setTimeout(() => {
  336. this.transitionWidth = ''
  337. this.transitionLeft = ''
  338. this.text = this.explain
  339. }, 300)
  340. },
  341. // 请求背景图片和验证图片
  342. getPictrue() {
  343. const data = {
  344. captchaType: this.captchaType,
  345. clientUid: localStorage.getItem('slider'),
  346. ts: Date.now(), // 现在的时间戳
  347. }
  348. reqGet(data).then(res => {
  349. if (res.repCode === '0000') {
  350. this.backImgBase = res.repData.originalImageBase64
  351. this.blockBackImgBase = res.repData.jigsawImageBase64
  352. this.backToken = res.repData.token
  353. this.secretKey = res.repData.secretKey
  354. } else {
  355. this.tipWords = res.repMsg
  356. }
  357. // 判断接口请求次数是否失效
  358. if (res.repCode === '6201') {
  359. this.backImgBase = null
  360. this.blockBackImgBase = null
  361. }
  362. })
  363. },
  364. },
  365. }
  366. </script>