Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into test
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/components/Captcha/SliderCaptcha.vue
  • Loading branch information
feiyuchuixue committed Jan 18, 2025
2 parents ef57d0e + 97d99ee commit 7d915b4
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 57 deletions.
38 changes: 38 additions & 0 deletions CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
# 更新日志
## v0.8.8 (20250118)

- sz-boot-parent:
- 修改:Jackson序列化添加对`MultipartFile`类型的支持。
- 修改:`router.whitelist` 属性为Set结构。
- 优化:系统用户更新时,同步更新缓存信息。
- 优化:[行为验证码-滑块验证] 增加对double精度的支持。
- 优化:接口白名单,删除非必要的放行接口。
- 修复:aop日志打印的一些问题( http-topic.log)。
- 修复:[代码生成器] 预览时插入按钮SQL问题。
- 修复:EntityChangeListener 在处理未登录用户数据初始化时的异常问题。
- 修复:验证码参数`sys.captcha.requestLimit`未启用时redis中仍然记录了次数的问题。
- 修复:[部门管理] 上级部门为`根部门`时编辑校验未通过的问题。
- 依赖升级:
- spotless-maven-plugin:2.43.0 -> 2.44.1。
- mybatis-flex.version:1.10.2 -> 1.10.5。
- aws.s3.version:2.29.23 -> 2.29.50。
- springdoc-openapi-starter-webmvc-ui:2.7.0 -> 2.8.3。
- modelmapper:3.2.1 -> 3.2.2。
- swagger-annotations:2.2.26 -> 2.2.27。
- sz-admin:

- 修复:个别浏览器Socket异常的问题。
- 优化:[行为验证码-滑块验证] 添加对移动端浏览器的支持。
## v0.8.7 (20250109)

- sz-boot-parent:
- 新增:`sz.cors.allowedOrigins`配置项,允许用户通过配置的方式指定限定域名
- 修复:springboot启动时打印logback配置信息的问题 && 优化logback配置
- 优化:接口防抖逻辑
- 当全局设置忽略GET请求的参数为true时,如果GET请求的Controller未标注@Debounce注解,则跳过防抖处理;但若Controller标注了@Debounce注解,即使是GET请求,也会执行防抖逻辑。
- 新增:行为验证码-滑块验证。感谢([阳纸伞](https://github.com/1327614618))
- 新增:[演示案例] 远程搜索下拉选择组件
- sz-admin:

- 新增:行为验证码-滑块验证
- 新增:远程搜索下拉选择组件
- 新增:[演示案例] 远程搜索下拉选择组件
## v0.8.6 (20250102)

- sz-boot-parent:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sz-admin",
"version": "0.8.6",
"version": "0.8.8",
"private": true,
"scripts": {
"dev": "vite",
Expand Down
133 changes: 78 additions & 55 deletions src/components/Captcha/SliderCaptcha.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@
>
<div class="sliderBox">
<div class="sliderBox_content">
<!-- 大图显示 -->
<img v-show="slideData.big" :src="'data:image/png;base64,' + slideData.big" class="bigImg" />
<span class="sliderBox_refresh" @click="refreshSlider">
<el-icon class="el-input__icon"><refresh /></el-icon>
</span>
<!-- 小图显示 -->
<img v-show="slideData.small" :src="'data:image/png;base64,' + slideData.small" class="smallImg" ref="imgK" />
<!-- 验证结果提示 -->
<div v-if="overlayVisible" class="overlay" :class="{ success: slideData.isSuccess, failure: !slideData.isSuccess }">
<span>{{ overlayMessage }}</span>
</div>
</div>
<div class="btnBox" ref="wrap">
<div class="sliderBox_text">向右滑动,完成验证</div>
<!-- 滑块轨迹显示 -->
<div class="sliderBox_track" :style="{ width: trackWidth + 'px' }" />
<!-- 滑块按钮 -->
<div class="sliderBox_btn" ref="slider">&gt;</div>
</div>
</div>
Expand All @@ -34,12 +39,14 @@ import { Refresh } from '@element-plus/icons-vue';
import { getImageCodeApi, verifyImageCodeApi } from '@/api/modules/system/captcha';
import { aesEncrypt } from '@/utils';
// 定义组件选项
defineOptions({
name: 'SliderCaptcha'
});
const dialogVisible = ref(false);
const dialogVisible = ref(false); // 控制对话框可见性
// 定义滑动验证数据结构
const slideData = reactive({
big: '',
small: '',
Expand All @@ -50,23 +57,29 @@ const slideData = reactive({
secretKey: ''
});
const slider = ref<HTMLDivElement | null>(null);
const wrap = ref<HTMLDivElement | null>(null);
const imgK = ref<HTMLImageElement | null>(null);
const trackWidth = ref(0);
const overlayMessage = ref('');
const overlayVisible = ref(false);
const isVerifying = ref(false);
let clearEventListeners: (() => void) | null = null;
const slider = ref<HTMLDivElement | null>(null); // 滑块元素引用
const wrap = ref<HTMLDivElement | null>(null); // 滑动容器引用
const imgK = ref<HTMLImageElement | null>(null); // 小图元素引用
const trackWidth = ref(0); // 滑动轨迹宽度
const overlayMessage = ref(''); // 验证结果提示信息
const overlayVisible = ref(false); // 验证结果提示可见性
const isVerifying = ref(false); // 是否正在验证标志
let clearEventListeners: (() => void) | null = null; // 清理事件监听器的函数
const emit = defineEmits(['success']);
// 检测是否为移动端
const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
// 接收父组件的参数并初始化滑动验证
const acceptParams = () => {
fetchSlideData();
dialogVisible.value = true;
};
// 获取滑动验证数据
const fetchSlideData = async () => {
const { data } = await getImageCodeApi();
// 将API返回的数据赋值给滑动验证数据
Object.assign(slideData, {
big: data.bigImageBase64,
small: data.smallImageBase64,
Expand All @@ -78,22 +91,25 @@ const fetchSlideData = async () => {
});
overlayVisible.value = false;
overlayMessage.value = '';
await nextTick(initializeSlider);
await nextTick(initializeSlider); // 等待DOM更新后初始化滑块
};
// 刷新滑动验证
const refreshSlider = async () => {
slideData.isSuccess = false;
overlayVisible.value = false;
overlayMessage.value = '';
await fetchSlideData();
};
// 关闭滑动验证对话框
const closeSlider = () => {
dialogVisible.value = false;
clearEventListeners && clearEventListeners();
clearEventListeners && clearEventListeners(); // 清理事件监听器
resetSlider();
};
// 重置滑块数据
const resetSlider = () => {
slideData.big = '';
slideData.small = '';
Expand All @@ -104,6 +120,7 @@ const resetSlider = () => {
slideData.bigWidth = 320;
};
// 初始化滑块事件
const initializeSlider = () => {
if (!slider.value || !imgK.value || !wrap.value) return;
Expand All @@ -116,63 +133,66 @@ const initializeSlider = () => {
let initX = 0;
let startTime = 0;
const handleMousemove = (e: MouseEvent) => {
offsetX = Math.max(0, Math.min(e.clientX - initX, limit));
// 移动滑块
const moveSlider = (clientX: number) => {
offsetX = Math.max(0, Math.min(clientX - initX, limit));
slider.value!.style.left = `${offsetX}px`;
imgK.value!.style.left = `${offsetX}px`;
trackWidth.value = offsetX;
};
const handleMouseup = () => {
document.removeEventListener('mousemove', handleMousemove);
document.removeEventListener('mouseup', handleMouseup);
verifyImageCode({ requestId: slideData.requestId, startTime, moveEncrypted: aesEncrypt(offsetX + '', slideData.secretKey) });
};
const handleMousedown = (e: MouseEvent) => {
initX = e.clientX;
// 滑块鼠标和触摸按下事件处理
const handleStart = (e: MouseEvent | TouchEvent) => {
initX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
startTime = Date.now();
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mouseup', handleMouseup);
};
const handleTouchMove = (e: TouchEvent) => {
const touch = e.touches[0];
offsetX = Math.max(0, Math.min(touch.clientX - initX, limit));
slider.value!.style.left = `${offsetX}px`;
imgK.value!.style.left = `${offsetX}px`;
trackWidth.value = offsetX;
};
const handleTouchEnd = () => {
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
verifyImageCode({ requestId: slideData.requestId, startTime, moveEncrypted: aesEncrypt(offsetX + '', slideData.secretKey) });
};
const handleTouchStart = (e: TouchEvent) => {
const touch = e.touches[0];
initX = touch.clientX;
startTime = Date.now();
document.addEventListener('touchmove', handleTouchMove);
document.addEventListener('touchend', handleTouchEnd);
const handleMove = (e: MouseEvent | TouchEvent) => {
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
moveSlider(clientX);
};
const handleEnd = () => {
document.removeEventListener('mousemove', handleMove);
document.removeEventListener('mouseup', handleEnd);
document.removeEventListener('touchmove', handleMove);
document.removeEventListener('touchend', handleEnd);
verifyImageCode({
requestId: slideData.requestId,
startTime,
moveEncrypted: aesEncrypt(offsetX + '', slideData.secretKey)
});
};
if (isMobile) {
document.addEventListener('touchmove', handleMove);
document.addEventListener('touchend', handleEnd);
} else {
document.addEventListener('mousemove', handleMove);
document.addEventListener('mouseup', handleEnd);
}
};
slider.value.addEventListener('mousedown', handleMousedown);
slider.value.addEventListener('touchstart', handleTouchStart);
// 根据设备类型添加滑块按下事件监听
if (isMobile) {
slider.value.addEventListener('touchstart', handleStart);
} else {
slider.value.addEventListener('mousedown', handleStart);
}
// 清理事件监听器
clearEventListeners = () => {
document.removeEventListener('mousemove', handleMousemove);
document.removeEventListener('mouseup', handleMouseup);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
slider.value?.removeEventListener('mousedown', handleMousedown);
slider.value?.removeEventListener('touchstart', handleTouchStart);
document.removeEventListener('mousemove', handleStart);
document.removeEventListener('mouseup', handleStart);
document.removeEventListener('touchmove', handleStart);
document.removeEventListener('touchend', handleStart);
slider.value?.removeEventListener('mousedown', handleStart);
slider.value?.removeEventListener('touchstart', handleStart);
};
};
// 验证滑动结果
const verifyImageCode = async (params: { requestId: string; startTime: number; moveEncrypted: string }) => {
if (isVerifying.value) return;
if (isVerifying.value) return; // 防止重复验证
isVerifying.value = true;
try {
Expand All @@ -186,7 +206,7 @@ const verifyImageCode = async (params: { requestId: string; startTime: number; m
setTimeout(() => {
emit('success');
closeSlider();
}, 2000);
}, 2000); // 2秒后关闭
} else {
handleVerificationFailure();
}
Expand All @@ -197,17 +217,20 @@ const verifyImageCode = async (params: { requestId: string; startTime: number; m
}
};
// 处理验证失败
const handleVerificationFailure = () => {
slideData.isSuccess = false;
overlayMessage.value = '验证失败';
overlayVisible.value = true;
setTimeout(refreshSlider, 2000);
setTimeout(refreshSlider, 2000); // 2秒后刷新
};
// 组件卸载前清理事件监听器
onBeforeUnmount(() => {
clearEventListeners && clearEventListeners();
});
// 公开的组件方法
defineExpose({
acceptParams,
dialogVisible
Expand Down
3 changes: 2 additions & 1 deletion src/stores/modules/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { useAuthStore } from '@/stores/modules/auth';

// 是否使用socket 当 import.meta.env.VITE_SOCKET_URL 不为空时,启用websocket
const useSocket = Object.prototype.hasOwnProperty.call(import.meta.env, 'VITE_SOCKET_URL');
const socketUrl = import.meta.env.VITE_SOCKET_URL;
const socketUrl = new URL(import.meta.env.VITE_SOCKET_URL, window.location.origin);
socketUrl.protocol = socketUrl.protocol === 'https:' ? 'wss:' : 'ws:';
const MAX_RECONNECT_COUNT = 10;

/**
Expand Down

0 comments on commit 7d915b4

Please sign in to comment.