VerifySlide.vue 10 KB

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