!255 发布 5.5.2-2.5.2 版本 2025年最后一版

Merge pull request !255 from 疯狂的狮子Li/dev
This commit is contained in:
疯狂的狮子Li
2025-12-23 01:41:45 +00:00
committed by Gitee
13 changed files with 248 additions and 57 deletions

View File

@@ -17,6 +17,8 @@
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
@@ -35,6 +37,7 @@
"createInjectionState": true,
"createPinia": true,
"createReactiveFn": true,
"createRef": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
@@ -277,6 +280,7 @@
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeAgoIntl": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
@@ -315,9 +319,6 @@
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"Slot": true,
"Slots": true,
"createRef": true
"whenever": true
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"version": "5.5.1-2.5.1",
"version": "5.5.2-2.5.2",
"description": "RuoYi-Vue-Plus多租户管理系统",
"author": "LionLi",
"license": "MIT",
@@ -20,65 +20,65 @@
"url": "https://gitee.com/JavaLionLi/plus-ui.git"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0",
"@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.1.0",
"@vueuse/core": "13.9.0",
"animate.css": "4.1.1",
"await-to-js": "3.0.0",
"axios": "1.8.4",
"axios": "1.13.1",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"element-plus": "2.9.8",
"element-plus": "2.11.7",
"file-saver": "2.0.5",
"highlight.js": "11.9.0",
"highlight.js": "11.11.1",
"image-conversion": "2.1.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"jsencrypt": "3.5.4",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"pinia": "3.0.3",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",
"vue-i18n": "11.1.3",
"vue-json-pretty": "2.4.0",
"vue-router": "4.5.0",
"vue": "3.5.22",
"vue-cropper": "1.1.4",
"vue-i18n": "11.1.12",
"vue-json-pretty": "2.6.0",
"vue-router": "4.6.3",
"vue-types": "6.0.0",
"vxe-table": "4.13.7"
"vxe-table": "4.17.7"
},
"devDependencies": {
"@iconify/json": "^2.2.276",
"@iconify/json": "^2.2.403",
"@types/crypto-js": "4.2.2",
"@types/file-saver": "2.0.7",
"@types/js-cookie": "3.0.6",
"@types/node": "^22.13.4",
"@types/node": "^22.19.0",
"@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "66.5.2",
"@unocss/preset-icons": "66.5.2",
"@unocss/preset-uno": "66.5.2",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13",
"@unocss/preset-attributify": "66.5.4",
"@unocss/preset-icons": "66.5.4",
"@unocss/preset-uno": "66.5.4",
"@vitejs/plugin-vue": "5.2.4",
"@vue/compiler-sfc": "3.5.22",
"@vue/eslint-config-prettier": "10.2.0",
"@vue/eslint-config-typescript": "14.4.0",
"autoprefixer": "10.4.20",
"eslint": "9.21.0",
"eslint-plugin-prettier": "5.2.3",
"eslint-plugin-vue": "9.32.0",
"globals": "16.0.0",
"prettier": "3.5.2",
"sass": "1.87.0",
"typescript": "~5.8.3",
"unocss": "66.5.2",
"unplugin-auto-import": "19.1.2",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.5.0",
"@vue/eslint-config-typescript": "14.6.0",
"autoprefixer": "10.4.21",
"eslint": "9.39.1",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-vue": "9.33.0",
"globals": "16.5.0",
"prettier": "3.6.2",
"sass": "1.93.3",
"typescript": "~5.9.3",
"unocss": "66.5.4",
"unplugin-auto-import": "19.3.0",
"unplugin-icons": "22.5.0",
"unplugin-vue-components": "28.8.0",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "6.3.2",
"vite": "6.4.1",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons-ng": "^1.4.0",
"vite-plugin-vue-devtools": "7.7.5",
"vitest": "3.1.2",
"vue-tsc": "^2.2.8"
"vite-plugin-svg-icons-ng": "^1.5.2",
"vite-plugin-vue-devtools": "8.0.3",
"vitest": "3.2.4",
"vue-tsc": "^2.2.12"
},
"overrides": {
"quill": "2.0.2"

View File

@@ -1,7 +1,7 @@
import request from '@/utils/request';
// 绑定账号
export function authBinding(source: string, tenantId: string) {
// 获取跳转URL
export function authRouterUrl(source: string, tenantId: string) {
return request({
url: '/auth/binding/' + source,
method: 'get',

View File

@@ -1,7 +1,7 @@
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<template v-if="isValueMatch(item.value)">
<span
v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
:key="item.value"
@@ -50,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
const values = computed(() => {
if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value]
return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);
});
@@ -58,7 +59,7 @@ const unmatch = computed(() => {
// 传入值为非数组
let unmatch = false; // 添加一个标志来判断是否有未匹配项
values.value.forEach((item) => {
if (!props.options.some((v) => v.value === item)) {
if (!props.options.some((v) => v.value == item)) {
unmatch = true; // 如果有未匹配项将标志设置为true
}
});
@@ -85,6 +86,10 @@ const handleArray = (array: Array<string | number>) => {
return pre + ' ' + cur;
});
};
const isValueMatch = (itemValue: any) => {
return values.value.some(val => val == itemValue)
}
</script>
<style lang="scss" scoped>

View File

@@ -8,7 +8,7 @@
<el-checkbox value="3" name="type">短信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="附件">
<el-form-item label="附件" v-if="buttonObj.file">
<fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
</el-form-item>
<el-form-item label="抄送" v-if="buttonObj.copy">

View File

@@ -28,6 +28,9 @@ import ElementIcons from '@/plugins/svgicon';
// permission control
import './permission';
// 开发者工具保护
import { initDevToolsProtection } from '@/utils/devtools-protection';
// 国际化
import i18n from '@/lang/index';
@@ -55,3 +58,6 @@ app.use(plugins);
directive(app);
app.mount('#app');
// 初始化开发者工具保护(仅生产环境)
initDevToolsProtection();

View File

@@ -0,0 +1,158 @@
/**
* 开发者工具保护
* 检测开发者工具是否打开,如果打开则循环执行 debugger 阻止调试
*/
// 检测开发者工具是否打开
function detectDevTools(): boolean {
try {
// 方法1: 检测窗口尺寸差异(最可靠的方法)
const widthThreshold = 160; // 开发者工具最小宽度
const heightThreshold = 160; // 开发者工具最小高度
const widthDiff = window.outerWidth - window.innerWidth;
const heightDiff = window.outerHeight - window.innerHeight;
if (widthDiff > widthThreshold || heightDiff > heightThreshold) {
return true;
}
// 方法2: 检测 debugger 执行时间(最准确的方法)
const start = performance.now();
debugger; // 这个 debugger 用于检测,不会被移除
const end = performance.now();
const timeDiff = end - start;
// 如果 debugger 被跳过(开发者工具关闭),时间差会很小(通常 < 1ms
// 如果 debugger 暂停(开发者工具打开),时间差会很大(通常 > 100ms
// 降低阈值以提高检测灵敏度
if (timeDiff > 10) {
return true;
}
// 方法3: 检测控制台对象
let devtoolsDetected = false;
const element = document.createElement('div');
Object.defineProperty(element, 'id', {
get: function () {
devtoolsDetected = true;
return '';
}
});
// 使用 console 来触发 getter仅在开发者工具打开时
try {
console.log(element);
console.clear();
} catch (e) {
// 忽略错误
}
if (devtoolsDetected) {
return true;
}
// 方法4: 检测控制台是否被重写(开发者工具打开时)
const devtoolsRegex = /./;
// @ts-expect-error - 动态添加属性
devtoolsRegex.toString = function () {
// @ts-expect-error - 动态添加属性
this.opened = true;
};
console.log('%c', devtoolsRegex);
// @ts-expect-error - 检查动态添加的属性
if (devtoolsRegex.opened) {
return true;
}
} catch (e) {
// 如果检测过程中出错,默认返回 false
return false;
}
return false;
}
// 开发者工具保护主函数
export function initDevToolsProtection(): void {
// 可以通过环境变量控制是否启用
// 生产环境默认启用,开发环境可以通过 VITE_ENABLE_ANTI_DEBUG=true 来启用测试
const isProduction = import.meta.env.MODE === 'production';
const enableAntiDebug = import.meta.env.VITE_ENABLE_ANTI_DEBUG === 'true' || isProduction;
if (!enableAntiDebug) {
return;
}
let devToolsOpen = false;
let debuggerInterval: number | null = null;
// 立即执行一次检测
const initialCheck = detectDevTools();
if (initialCheck) {
devToolsOpen = true;
debuggerInterval = window.setInterval(() => {
debugger; // 循环执行 debugger
}, 50); // 更频繁的 debugger每 50ms 一次
}
// 循环检测开发者工具(更频繁的检测)
const checkInterval = setInterval(() => {
const isOpen = detectDevTools();
if (isOpen && !devToolsOpen) {
// 开发者工具刚打开
devToolsOpen = true;
// 开始循环执行 debugger更频繁
if (debuggerInterval === null) {
debuggerInterval = window.setInterval(() => {
debugger; // 循环执行 debugger
}, 50); // 每 50ms 执行一次,更激进
}
} else if (!isOpen && devToolsOpen) {
// 开发者工具关闭了
devToolsOpen = false;
// 停止循环 debugger
if (debuggerInterval !== null) {
clearInterval(debuggerInterval);
debuggerInterval = null;
}
}
}, 500); // 每 500ms 检测一次,更频繁
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
clearInterval(checkInterval);
if (debuggerInterval !== null) {
clearInterval(debuggerInterval);
}
});
// 额外的检测:监听窗口大小变化
let lastWidth = window.innerWidth;
let lastHeight = window.innerHeight;
window.addEventListener('resize', () => {
const currentWidth = window.innerWidth;
const currentHeight = window.innerHeight;
// 如果窗口尺寸变化很大,可能是开发者工具打开/关闭
if (Math.abs(currentWidth - lastWidth) > 200 || Math.abs(currentHeight - lastHeight) > 200) {
// 重新检测
const isOpen = detectDevTools();
if (isOpen && !devToolsOpen) {
devToolsOpen = true;
if (debuggerInterval === null) {
debuggerInterval = window.setInterval(() => {
debugger; // 循环执行 debugger
}, 50); // 更频繁的 debugger
}
}
}
lastWidth = currentWidth;
lastHeight = currentHeight;
});
}

View File

@@ -1,4 +1,4 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min.js';
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;

View File

@@ -28,7 +28,11 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000
timeout: 50000,
transitional: {
// 超时错误更明确
clarifyTimeoutError: true
}
});
// 请求拦截器

View File

@@ -33,7 +33,7 @@
* 部署方式 Docker 容器编排 一键部署业务集群<br />
* 国际化 SpringMessage Spring标准国际化方案<br />
</p>
<p><b>当前版本:</b> <span>v5.5.1</span></p>
<p><b>当前版本:</b> <span>v5.5.2</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
@@ -77,7 +77,7 @@
* 分布式监控 PrometheusGrafana 全方位性能监控<br />
* 其余与 Vue 版本一致<br />
</p>
<p><b>当前版本:</b> <span>v2.5.1</span></p>
<p><b>当前版本:</b> <span>v2.5.2</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>

View File

@@ -73,14 +73,14 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getCodeImg, getTenantList } from '@/api/login';
import { authBinding } from '@/api/system/social/auth';
import { authRouterUrl } from '@/api/system/social/auth';
import { useUserStore } from '@/store/modules/user';
import { LoginData, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
@@ -176,6 +176,8 @@ const getCode = async () => {
const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) {
// 刷新验证码时清空输入框
loginForm.value.code = '';
codeUrl.value = 'data:image/gif;base64,' + data.img;
loginForm.value.uuid = data.uuid;
}
@@ -213,7 +215,7 @@ const initTenantList = async () => {
* @param type
*/
const doSocialLogin = (type: string) => {
authBinding(type, loginForm.value.tenantId).then((res: any) => {
authRouterUrl(type, loginForm.value.tenantId).then((res: any) => {
if (res.code === HttpStatus.SUCCESS) {
// 获取授权地址跳转
window.location.href = res.data;

View File

@@ -67,7 +67,7 @@
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>

View File

@@ -252,6 +252,21 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.visible !== '0'" :span="12">
<el-form-item label="激活路径" prop="form.remark">
<template #label>
<span>
<el-tooltip content="隐藏菜单填写默认激活路由,比如激活父菜单的路由 /system/user" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
激活路由
</span>
</template>
<el-input v-model="form.remark" placeholder="请输入激活路径" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>