|
|
@@ -0,0 +1,422 @@
|
|
|
+<template>
|
|
|
+
|
|
|
+ <div class="modal-overlay" @click.self="closeDialog">
|
|
|
+ <div class="modal-content">
|
|
|
+ <button class="modal-close" @click="closeDialog">×</button>
|
|
|
+
|
|
|
+ <div class="code-dialog">
|
|
|
+ <div class="title">请完成安全验证</div>
|
|
|
+ <div class="pt">
|
|
|
+ <div class="pt-verification-box">
|
|
|
+ <div class="pt-verification-images">
|
|
|
+ <div class="iconfont refresh" @click="refresh"></div>
|
|
|
+ <img :src="'data:image/jpeg;base64,' + bgImg" class="bg-img" alt="验证背景图">
|
|
|
+ <img :src="'data:image/jpeg;base64,' + maskImg" class="drag-img" alt="拼图块"
|
|
|
+ :style="{ left: dragWidth + 'px', top: top + 'px' }">
|
|
|
+ <div class="mask"></div>
|
|
|
+ </div>
|
|
|
+ <div class="pt-dragbar">
|
|
|
+ <div :class="['pt-drag-area', { fail: isFail, success: isSuccess }]" :style="{ width: dragWidth + 'px' }"
|
|
|
+ v-if="dragWidth"></div>
|
|
|
+ <div class="pt-dragbar-area" @mousemove="dragMove" @mouseup="dragEnd" @touchmove="dragMove"
|
|
|
+ @touchend="dragEnd">
|
|
|
+ <div :class="['pt-dragbar-view', {
|
|
|
+ active: dragWidth > 2,
|
|
|
+ fail: isFail,
|
|
|
+ success: isSuccess
|
|
|
+ }]" @mousedown="dragStart" @touchstart="dragStart"
|
|
|
+ :style="{ left: dragWidth + 'px' }">
|
|
|
+ <span class="iconfont">
|
|
|
+ <span v-if="isSuccess"></span>
|
|
|
+ <span v-else-if="isFail"></span>
|
|
|
+ <span v-else></span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <span v-if="dragWidth === 0" class="tips">{{ tips }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { toRefs, ref, watch } from 'vue'
|
|
|
+const SliderVerifySetup = {
|
|
|
+ name: 'SliderVerifySetup',
|
|
|
+ props: {
|
|
|
+ show: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ bgImg: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ maskImg: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ top: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ direction: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: 'horizontal'
|
|
|
+ },
|
|
|
+ isSuccess: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ isFail: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ }
|
|
|
+ },
|
|
|
+ emits: ['update:show', 'close', 'finish', 'refresh'],
|
|
|
+ setup(props, { emit }) {
|
|
|
+ // 使用 toRefs 解构 props
|
|
|
+ const { show, isSuccess: propIsSuccess, isFail: propIsFail } = toRefs(props);
|
|
|
+
|
|
|
+ // 响应式数据
|
|
|
+ const tips = ref('向右拖动滑块填充拼图');
|
|
|
+ const disabled = ref(false);
|
|
|
+ const dragWidth = ref(0);
|
|
|
+ const x = ref(0);
|
|
|
+ const isDragging = ref(false);
|
|
|
+ const startX = ref(0);
|
|
|
+ const isSuccess = ref(propIsSuccess.value);
|
|
|
+ const isFail = ref(propIsFail.value);
|
|
|
+
|
|
|
+ // 计算属性
|
|
|
+ const maxDragWidth = ref(300 - 40);
|
|
|
+
|
|
|
+ // 方法
|
|
|
+ const closeDialog = () => {
|
|
|
+ emit('update:show', false);
|
|
|
+ emit('close');
|
|
|
+ };
|
|
|
+
|
|
|
+ const dragStart = (e) => {
|
|
|
+ isDragging.value = true;
|
|
|
+ const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
|
+ startX.value = clientX;
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', dragMove);
|
|
|
+ document.addEventListener('touchmove', dragMove);
|
|
|
+ document.addEventListener('mouseup', dragEnd);
|
|
|
+ document.addEventListener('touchend', dragEnd);
|
|
|
+ };
|
|
|
+
|
|
|
+ const dragMove = (e) => {
|
|
|
+ if (!isDragging.value) return;
|
|
|
+
|
|
|
+ e.preventDefault();
|
|
|
+ const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
|
|
+ const deltaX = clientX - startX.value;
|
|
|
+
|
|
|
+ dragWidth.value = Math.max(0, Math.min(deltaX, maxDragWidth.value));
|
|
|
+ };
|
|
|
+
|
|
|
+ const dragEnd = (e) => {
|
|
|
+ if (!isDragging.value) return;
|
|
|
+
|
|
|
+ isDragging.value = false;
|
|
|
+ x.value = dragWidth.value;
|
|
|
+ emit('finish', dragWidth.value);
|
|
|
+
|
|
|
+ document.removeEventListener('mousemove', dragMove);
|
|
|
+ document.removeEventListener('touchmove', dragMove);
|
|
|
+ document.removeEventListener('mouseup', dragEnd);
|
|
|
+ document.removeEventListener('touchend', dragEnd);
|
|
|
+ };
|
|
|
+
|
|
|
+ const refresh = () => {
|
|
|
+ dragWidth.value = 0;
|
|
|
+ isFail.value = false;
|
|
|
+ isSuccess.value = false;
|
|
|
+ x.value = 0;
|
|
|
+ disabled.value = false;
|
|
|
+ emit('refresh');
|
|
|
+ };
|
|
|
+
|
|
|
+ // 监听器
|
|
|
+ watch(() => propIsSuccess, (newVal) => {
|
|
|
+ isSuccess.value = newVal;
|
|
|
+ });
|
|
|
+
|
|
|
+ watch(() => propIsFail, (newVal) => {
|
|
|
+ isFail.value = newVal;
|
|
|
+ });
|
|
|
+
|
|
|
+ watch(() => show, (newVal) => {
|
|
|
+ if (!newVal) {
|
|
|
+ dragWidth.value = 0;
|
|
|
+ isFail.value = false;
|
|
|
+ isSuccess.value = false;
|
|
|
+ x.value = 0;
|
|
|
+ disabled.value = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ tips,
|
|
|
+ disabled,
|
|
|
+ dragWidth,
|
|
|
+ x,
|
|
|
+ isDragging,
|
|
|
+ isSuccess,
|
|
|
+ isFail,
|
|
|
+ closeDialog,
|
|
|
+ dragStart,
|
|
|
+ dragMove,
|
|
|
+ dragEnd,
|
|
|
+ refresh
|
|
|
+ };
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export default SliderVerifySetup
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+/* 滑块验证组件样式 */
|
|
|
+@font-face {
|
|
|
+ font-family: 'iconfont';
|
|
|
+ src: url('https://at.alicdn.com/t/font_2047533_o8axbabfs3.ttf') format('truetype');
|
|
|
+}
|
|
|
+
|
|
|
+.iconfont {
|
|
|
+ font-family: iconfont !important;
|
|
|
+ font-size: 16px;
|
|
|
+ font-style: normal;
|
|
|
+ -webkit-font-smoothing: antialiased;
|
|
|
+ -moz-osx-font-smoothing: grayscale;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-overlay {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 1000;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-content {
|
|
|
+ background: white;
|
|
|
+ border-radius: 10px;
|
|
|
+ position: relative;
|
|
|
+ animation: modalAppear 0.3s ease;
|
|
|
+ max-width: 90vw;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes modalAppear {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.8);
|
|
|
+ }
|
|
|
+
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.modal-close {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ font-size: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #999;
|
|
|
+ z-index: 10;
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.modal-close:hover {
|
|
|
+ background: #f5f5f5;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.code-dialog {
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 10px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.title {
|
|
|
+ font-size: 18px;
|
|
|
+ color: #333333;
|
|
|
+ width: 100%;
|
|
|
+ text-align: left;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.pt {
|
|
|
+ width: 300px;
|
|
|
+ max-width: 100%;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-verification-images {
|
|
|
+ position: relative;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.refresh {
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 10px;
|
|
|
+ z-index: 10;
|
|
|
+ color: #FFF;
|
|
|
+ font-weight: bold;
|
|
|
+ cursor: pointer;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ padding: 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ width: 30px;
|
|
|
+ height: 30px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.bg-img {
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.drag-img {
|
|
|
+ position: absolute;
|
|
|
+ width: 56px;
|
|
|
+ height: 45px;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 1;
|
|
|
+ border: 2px dashed #1890ff;
|
|
|
+ border-radius: 4px;
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background: rgba(0, 0, 0, 0.2);
|
|
|
+ pointer-events: none;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar {
|
|
|
+ position: relative;
|
|
|
+ height: 40px;
|
|
|
+ background-color: #F7F7F7;
|
|
|
+ border: solid 1px #EEE;
|
|
|
+ margin-top: 10px;
|
|
|
+ border-radius: 20px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-drag-area {
|
|
|
+ position: absolute;
|
|
|
+ height: 40px;
|
|
|
+ border: solid 1px #1890ff;
|
|
|
+ background-color: #D1E9F1;
|
|
|
+ top: -1px;
|
|
|
+ border-radius: 20px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-drag-area.fail {
|
|
|
+ border-color: #ff4d4f;
|
|
|
+ background-color: #ffdbdb;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-drag-area.success {
|
|
|
+ border-color: #52c41a;
|
|
|
+ background-color: #d7ffe1;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-area {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ left: 0;
|
|
|
+ top: 0;
|
|
|
+ border-radius: 20px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.tips {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ pointer-events: none;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-view {
|
|
|
+ position: absolute;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border: solid 1px #EEE;
|
|
|
+ background-color: #FFF;
|
|
|
+ top: -1px;
|
|
|
+ left: 0;
|
|
|
+ border-radius: 50%;
|
|
|
+ cursor: grab;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ user-select: none;
|
|
|
+ z-index: 2;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-view.active {
|
|
|
+ background-color: #1890ff;
|
|
|
+ border-color: #1890ff;
|
|
|
+ color: #FFF;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-view.fail {
|
|
|
+ background-color: #ff4d4f;
|
|
|
+ border-color: #ff4d4f;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-view.success {
|
|
|
+ border-color: #52c41a;
|
|
|
+ background-color: #00a029;
|
|
|
+}
|
|
|
+
|
|
|
+.pt-dragbar-view:active {
|
|
|
+ cursor: grabbing;
|
|
|
+}
|
|
|
+</style>
|