| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- <template>
- <view class="slide_code_container">
- <!-- 背景区域 -->
- <view class="slide_bg" :style="{ backgroundImage: sliderBgUrl ? 'url(' + sliderBgUrl + ')' : '' }">
- <!-- 拼图缺口示意 -->
- <view class="block_bg"
- :style="{ left: sliderPositionX + 'px',top:sliderPositionY+'px', backgroundImage: blockBgUrl ? 'url(' + blockBgUrl + ')' : '' }">
- </view>
- </view>
- <!-- 滑块轨道 -->
- <view class="slide_track" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseUp">
- <!-- 已滑过的进度条 -->
- <view class="slide_progress" :style="{ width: sliderPositionX + 'px' }"
- :class="{ 'success': sliderVerified==1, 'fail': sliderVerified==-1 }">
- </view>
- <!-- 提示文字 -->
- <view class="slide_text" :class="{ 'hide': isDragging || sliderVerified ==1 }">
- {{ statusText }}
- </view>
- <!-- 可拖动滑块 -->
- <view class="slide_btn" :style="{ left: sliderPositionX + 'px' }"
- :class="{ 'success': sliderVerified==1, 'fail': sliderVerified==-1 }" @touchstart="onTouchStart"
- @touchmove="onTouchMove" @touchend="onTouchEnd" @mousedown="onMouseDown">
- <text v-if="sliderVerified==0" class="icon">>></text>
- <text v-else-if="sliderVerified===1" class="icon">✔</text>
- <text v-else class="icon">✖</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import {
- ref,
- computed,
- onMounted,
- watch
- } from 'vue';
- import user from '@/reqApi/user.js';
- // --- Props ---
- // 1. 显式接收 props 对象
- const props = defineProps({
- // 背景图片地址
- sliderBgUrl: {
- type: String,
- default: ''
- },
- blockBgUrl: {
- type: String,
- default: ''
- }, //拼图缺口图片地址
- sliderUUID: {
- type: String,
- default: '',
- }, //uuid
- // sliderVerified: {
- // type: Number,
- // default: 0
- // },//是否验证通过 0 未验证 1 验证通过 -1 验证失败
- sliderPositionY: {
- type: Number,
- default: 0
- },
- });
- // --- Emits ---
- const emit = defineEmits(['success', 'fail', 'reset', 'CheckResult']);
- // --- State ---
- const sliderPositionX = ref(0); //当前滑块移动的X轴距离 (相对位移)
- const sliderVerified = ref(0); //0 未验证 1 验证通过 -1 验证失败
- // const sliderPositionY = ref(0); //拼图缺口的Y轴位置 后端返回
- const targetX = ref(0); // 拼图缺口的目标X轴位置 (后端返回或随机生成)
- const isDragging = ref(false); // 是否正在拖动
- const sliderPhone = ref(''); //验证的手机号
- const startX = ref(0); //鼠标或者手指按下时的初始x坐标
- // --- Computed ---
- const statusText = computed(() => {
- if (sliderVerified.value == 1) return '验证成功';
- if (sliderVerified.value == -1) {
- return '验证失败';
- }
- return '向右滑动填充拼图';
- });
- watch(() => props.sliderVerified, (newVal) => {
- if (newVal === -1) {
- // 如果验证失败,重置滑块位置为 0
- sliderPositionX.value = 0;
- }
- });
- // --- Methods ---
- // 初始化/重置
- const Reset = () => {
- isDragging.value = false;
- // 随机生成目标位置 (模拟)
- sliderPositionX.value = 0;
- };
- // 开始拖动 (Touch)
- const onTouchStart = (e) => {
- if (props.sliderVerified == 1) return; //验证通过不允许再拖动
- isDragging.value = true;
- // 记录手指按下的屏幕坐标
- startX.value = e.touches[0].clientX;
- // 阻止默认行为,防止页面滚动或浏览器手势导航
- e.preventDefault();
- };
- // 拖动中 (Touch)
- const onTouchMove = (e) => {
- if (!isDragging.value) return;
- // 【可选】再次阻止默认行为,确保拖动流畅且不滚动页面
- e.preventDefault();
- // 计算移动距离 = 当前坐标 - 初始坐标
- const currentX = e.touches[0].clientX;
- let moveDistance = currentX - startX.value; //相对距离
- updatePosition(moveDistance);
- };
- // 结束拖动 (Touch)
- const onTouchEnd = () => {
- if (!isDragging.value) return;
- isDragging.value = false;
- console.log("拖动结束,验证结果");
- VerifySlideCaptcha();
- // emit('CheckResult',sliderPositionX.value)
- };
- // 开始拖动 (Mouse - PC端)
- const onMouseDown = (e) => {
- if (props.sliderVerified == 1) return; // 验证通过不允许再拖动
- isDragging.value = true;
- // 记录鼠标按下时的屏幕坐标
- startX.value = e.clientX;
- // 防止文本选中
- e.preventDefault();
- };
- // 拖动中 (Mouse - PC端)
- // 注意:这个函数由父容器 .slide_track 触发
- const onMouseMove = (e) => {
- if (!isDragging.value) return;
- // 计算移动距离 = 当前坐标 - 初始坐标
- const currentX = e.clientX;
- let moveDistance = currentX - startX.value;
- updatePosition(moveDistance);
- };
- // 结束拖动 (Mouse - PC端)
- // 注意:这个函数由父容器 .slide_track 触发
- const onMouseUp = () => {
- if (!isDragging.value) return;
- isDragging.value = false;
- console.log("鼠标拖动结束,验证结果");
- VerifySlideCaptcha();
- };
- // 更新位置逻辑
- const updatePosition = (moveDistance) => {
- // 限制移动范围 [0, maxSlideDistance]
- if (moveDistance < 0) moveDistance = 0;
- if (moveDistance > 250) moveDistance = 250;
- sliderPositionX.value = moveDistance;
- };
- //验证滑动验证码位置是否正确
- const VerifySlideCaptcha = () => {
- let params = {
- sliderUUID: props.sliderUUID,
- x: Math.round(sliderPositionX.value), //滑动验证码块切块图位置x坐标
- };
- user.checkSlideCaptcha(params).then(res => {
- console.log("333验证滑动验证码结果", res);
- if (res.code == 200) {
- //验证成功
- sliderVerified.value = 1;
- sliderPhone.value = res.data; //手机号
- emit('CheckResult', sliderPhone.value)
- } else if (res.code == 601) {
- //验证成功
- sliderVerified.value = 1;
- uni.showToast({
- title: res.msg || '滑块验证通过,您上次的验证码还在有效期,请输入上次发送的验证码进行登录!',
- icon: 'none', // 可选值:'success', 'loading', 'none'
- duration: 2000 // 提示持续时间,单位毫秒
- });
- sliderPhone.value = res.data; //手机号
- emit('CheckResult', sliderPhone.value)
- } else if (res.code == 602) {
- //滑动验证码过期 重新获取验证码
- uni.showToast({
- title: '验证码已过期,已重新获取,请重新拖动滑块验证',
- icon: 'none', // 可选值:'success', 'loading', 'none'
- duration: 2000 // 提示持续时间,单位毫秒
- });
- // GetObtainSliderImg();//重新获取验证码
- emit('Refresh')
- } else {
- sliderVerified.value = 0; //验证失败
- sliderPositionX.value = 0; //重置滑块位置
- }
- });
- }
- // 验证结果
- const checkResult = () => {
- };
- // 生命周期
- onMounted(() => {
- Reset();
- console.log("打印初始值", props.sliderPositionY);
- console.log("打印sliderBgUrl", props.sliderBgUrl);
- console.log("打印blockBgUrl", props.blockBgUrl);
- });
- // 暴露方法给父组件
- defineExpose({
- Reset
- });
- </script>
- <style scoped>
- .slide_code_container {
- width: 300px;
- /* 可根据需求调整宽度 */
- user-select: none;
- height: auto;
- padding-top: 5px;
- box-sizing: border-box;
- }
- .slide_bg {
- width: 100%;
- height: 180px;
- background-image: url('@/static/image/bg/captcha_bg.png');
- background-size: 100% 100%;
- background-position: 0 0;
- background-repeat: no-repeat;
- border-radius: 4px;
- position: relative;
- }
- .block_bg {
- width: 50px;
- height: 50px;
- background-image: url('@/static/image/bg/captcha_block.png');
- background-position: 0 0;
- background-repeat: no-repeat;
- background-size: 100% 100%;
- position: absolute;
- left: 0;
- top: 35px;
- border-radius: 5px;
- border: 1px solid red;
- }
- .puzzle_hole {
- position: absolute;
- top: 30px;
- /* 假设缺口高度 */
- width: 40px;
- height: 40px;
- background: rgba(0, 0, 0, 0.5);
- box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8);
- border-radius: 4px;
- }
- /* 滑块轨道 */
- .slide_track {
- position: relative;
- width: 100%;
- height: 40px;
- background-color: #eef1f8;
- border-radius: 4px;
- border: 1px solid #dcdcdc;
- box-sizing: border-box;
- }
- /* 已滑过的进度条 */
- .slide_progress {
- position: absolute;
- left: 0;
- top: 0;
- height: 100%;
- background-color: #19be6b;
- border-radius: 20px 0 0 20px;
- transition: width 0.1s;
- }
- /* 成功的背景 */
- .slide_progress.success {
- background-color: #19be6b;
- }
- .slide_progress.fail {
- background-color: #ff4949;
- }
- /* 提示文字 */
- .slide_text {
- position: absolute;
- width: 100%;
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 14px;
- color: #666;
- z-index: 1;
- transition: opacity 0.3s;
- }
- .slide_text.hide {
- opacity: 0.7;
- text-align: right;
- }
- /* 滑块的按钮样式 */
- .slide_btn {
- position: absolute;
- left: 0;
- top: -1px;
- /* 微调对齐 */
- width: 50px;
- height: 38px;
- background-color: #fff;
- border: 1px solid #dcdcdc;
- border-radius: 5px;
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- z-index: 2;
- transition: left 0.1s;
- /* 平滑拖动 */
- }
- .slide_btn.success {
- background-color: #19be6b;
- border-color: #19be6b;
- color: #fff;
- }
- .slide_btn.fail {
- background-color: #ff4949;
- border-color: #ff4949;
- color: #fff;
- }
- .icon {
- font-weight: bold;
- font-size: 16px;
- }
- </style>
|