mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2025-12-30 09:52:27 +00:00
!255 发布 5.5.2-2.5.2 版本 2025年最后一版
Merge pull request !255 from 疯狂的狮子Li/dev
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
80
package.json
80
package.json
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
|
||||
158
src/utils/devtools-protection.ts
Normal file
158
src/utils/devtools-protection.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
|
||||
@@ -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">¥免费开源</el-tag>
|
||||
</p>
|
||||
@@ -77,7 +77,7 @@
|
||||
* 分布式监控 Prometheus、Grafana 全方位性能监控<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">¥免费开源</el-tag>
|
||||
</p>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user