VSliderVerification.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <script lang="ts" setup>
  2. import { useI18n } from 'vue-i18n'
  3. //滑动图片验证码部分
  4. import Img01 from './image/verification-img-1.png'
  5. import Img02 from './image/verification-img-2.png'
  6. import Img03 from './image/verification-img-3.png'
  7. import Img04 from './image/verification-img-4.png'
  8. import { ref } from 'vue'
  9. //引入'vue3-puzzle-vcode'插件
  10. import Vcode from './components/SliderVerification.vue'
  11. // import Vcode from 'vue3-puzzle-vcode'
  12. const { t } = useI18n()
  13. const openDialog = () => {
  14. isShow.value = true
  15. nextTick(() => {
  16. addElement()
  17. onSuccess()
  18. updateSliderBackground('success')
  19. })
  20. }
  21. // 添加自定义元素
  22. const addElement = () => {
  23. addTipsNode()
  24. addSliderBtnNode('start')
  25. updateRefreshImg()
  26. }
  27. const updateRefreshImg = () => {
  28. const targetNode: any = document.querySelector('img.reset_')
  29. if (targetNode) {
  30. // 使用 import.meta.url 结合 new URL 获取图片的正确路径
  31. targetNode.src = new URL('./image/icon_refresh_bold_b.png', import.meta.url).href
  32. }
  33. }
  34. const addTipsNode = () => {
  35. if (document.querySelector('.v-slider-verification-tips')) {
  36. return
  37. }
  38. const childNode = document.createElement('div')
  39. childNode.className = 'v-slider-verification-tips'
  40. childNode.innerHTML = `
  41. <div style="margin-bottom: 5px; text-align: right;">
  42. <span class="font_family icon-icon_reject_b close-icon" style="margin-right: -20px;">
  43. </span>
  44. </div>
  45. <p>${t('common.sliderVerifyTipLine1')}</p>
  46. <p>${t('common.sliderVerifyTipLine2')}</p>
  47. `
  48. const parentNode = document.querySelector('.vue-auth-box_')
  49. if (parentNode.firstChild) {
  50. parentNode.insertBefore(childNode, parentNode.firstChild)
  51. } else {
  52. parentNode.appendChild(childNode)
  53. }
  54. nextTick(() => {
  55. const targetNode: any = document.querySelector('.font_family.icon-icon_reject_b.close-icon')
  56. targetNode.onclick = closeDialog
  57. })
  58. }
  59. const closeDialog = () => {
  60. isShow.value = false
  61. emit('close')
  62. }
  63. const styleMap = {
  64. start: {
  65. thumbColor: 'var(--color-slider-thumb-start)',
  66. thumbIcon: 'icon-icon_drag__line_b',
  67. trackBackground: 'var(--color-success)'
  68. },
  69. success: {
  70. thumbColor: '#fff',
  71. thumbIcon: 'icon-icon_confirm_b',
  72. trackBackground: 'var(--color-success)'
  73. },
  74. error: {
  75. thumbColor: '#fff',
  76. thumbIcon: 'icon-icon_reject_b',
  77. trackBackground: '#c7353f'
  78. }
  79. }
  80. const addSliderBtnNode = (state: string) => {
  81. const parentNode = document.querySelector('.range-btn')
  82. if (state === 'start') {
  83. parentNode.innerHTML = `
  84. <span style="color: ${styleMap[state].thumbColor}" class="font_family ${styleMap[state].thumbIcon}"></span>
  85. `
  86. } else {
  87. parentNode.innerHTML = `
  88. <div class="icon-border" style="background: ${styleMap[state].trackBackground}">
  89. <span style="color: ${styleMap[state].thumbColor}" class="font_family ${styleMap[state].thumbIcon}"></span>
  90. </div>
  91. `
  92. }
  93. }
  94. const isShow = ref(false)
  95. // 根据不同的状态,设置已滑动区域的背景颜色
  96. const updateSliderBackground = (state: string) => {
  97. const targetNode: any = document.querySelector('.range-slider')
  98. targetNode.style.background = styleMap[state].trackBackground
  99. }
  100. const emit = defineEmits<{
  101. close: []
  102. success: []
  103. }>()
  104. // 监听验证成功事件,因为这里库中的成功事件有0.8秒的延迟,所以这里手动监听验证成功事件
  105. const onSuccess = () => {
  106. // 选择要监听的目标元素
  107. const targetNode = document.querySelector('.info-box_') // 将此选择器替换为你的实际目标
  108. if (targetNode) {
  109. const observer = new MutationObserver((mutationsList) => {
  110. mutationsList.forEach((mutation) => {
  111. if (mutation.type === 'childList') {
  112. if ((mutation.target as any).innerHTML === t('common.verificationSuccessful')) {
  113. updateSliderBackground('success')
  114. addSliderBtnNode('success')
  115. setTimeout(() => {
  116. emit('success')
  117. isShow.value = false
  118. }, 500)
  119. }
  120. }
  121. })
  122. })
  123. // 配置 MutationObserver,监听子节点的变化
  124. const config = { childList: true, subtree: true }
  125. // 启动监听
  126. observer.observe(targetNode, config)
  127. }
  128. }
  129. const close = () => {
  130. isShow.value = true
  131. emit('close')
  132. }
  133. const fail = () => {
  134. updateSliderBackground('error')
  135. addSliderBtnNode('error')
  136. setTimeout(() => {
  137. updateSliderBackground('start')
  138. addSliderBtnNode('start')
  139. }, 800)
  140. }
  141. defineExpose({
  142. openDialog
  143. })
  144. </script>
  145. <template>
  146. <div>
  147. <Vcode
  148. :show="isShow"
  149. @close="close"
  150. @fail="fail"
  151. :canvasWidth="320"
  152. :canvasHeight="180"
  153. :sliderText="t('common.swipeToVerify')"
  154. :sliderSize="38"
  155. :successText="t('common.verificationSuccessful')"
  156. :failText="t('common.verificationFailed')"
  157. :range="5"
  158. :imgs="[Img01, Img02, Img03, Img04]"
  159. ></Vcode>
  160. </div>
  161. </template>
  162. <style lang="scss" scoped>
  163. .slider-verification {
  164. width: 400px;
  165. height: 365px;
  166. padding: 40px;
  167. }
  168. </style>
  169. <style lang="scss">
  170. .vue-puzzle-vcode {
  171. // 整体框架
  172. div.vue-auth-box_ {
  173. width: 400px;
  174. height: 365px;
  175. padding: 40px;
  176. padding-top: 16px;
  177. background-color: var(--color-slider-bg);
  178. border-radius: 16px;
  179. box-shadow: -2px 2px 12px 0 rgba(0, 0, 0, 0.5);
  180. .v-slider-verification-tips {
  181. margin-bottom: 16px;
  182. text-align: center;
  183. .close-icon {
  184. cursor: pointer;
  185. &:hover {
  186. color: var(--color-theme);
  187. }
  188. }
  189. }
  190. .icon-border {
  191. display: flex;
  192. justify-content: center;
  193. align-items: center;
  194. width: 16px !important;
  195. height: 16px !important;
  196. border-radius: 50%;
  197. border: none !important;
  198. span {
  199. font-size: 14px !important;
  200. }
  201. }
  202. }
  203. div.vue-puzzle-vcode {
  204. background-color: rgba(43, 47, 54, 0.7);
  205. }
  206. .vue-auth-box_ .auth-control_ div.range-box {
  207. background-color: #87909e;
  208. box-shadow: none;
  209. }
  210. .vue-auth-box_ div.auth-body_ {
  211. border-radius: 6px;
  212. }
  213. // 已滑动区域的样式
  214. .vue-auth-box_ .auth-control_ .range-box div.range-slider {
  215. background-color: var(--color-success);
  216. }
  217. .vue-auth-box_ .auth-body_ div.loading-box_.hide_ {
  218. display: none;
  219. }
  220. // 成功的提示
  221. .vue-auth-box_ .auth-body_ div.info-box_ {
  222. background: var(--color-success);
  223. }
  224. // 失败的提示
  225. .vue-auth-box_ .auth-body_ div.info-box_.fail {
  226. background: #c7353f;
  227. }
  228. .vue-auth-box_ .auth-control_ .range-box .range-slider {
  229. border-radius: 6px;
  230. }
  231. .vue-auth-box_ .auth-control_ div.range-box {
  232. border-radius: 6px;
  233. }
  234. // 滑块
  235. .vue-auth-box_ .auth-control_ .range-box .range-slider .range-btn {
  236. border-radius: 6px;
  237. }
  238. .vue-auth-box_ .auth-body_ .reset_ {
  239. width: 20px;
  240. height: 20px;
  241. top: 10px;
  242. right: 10px;
  243. }
  244. .vue-auth-box_ .auth-control_ .range-box .range-text {
  245. color: #fff;
  246. }
  247. }
  248. </style>