Răsfoiți Sursa

Merge branch 'ts' of https://gitee.com/JavaLionLi/plus-ui into ts

Zhangbw 3 luni în urmă
părinte
comite
ad4e6b1a00

+ 5 - 4
.eslintrc-auto-import.json

@@ -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
   }
 }

+ 40 - 40
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"

+ 2 - 2
src/api/system/social/auth.ts

@@ -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',

+ 7 - 2
src/components/DictTag/index.vue

@@ -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>

+ 1 - 1
src/components/Process/submitVerify.vue

@@ -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">

+ 6 - 0
src/main.ts

@@ -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 - 0
src/utils/devtools-protection.ts

@@ -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 - 1
src/utils/jsencrypt.ts

@@ -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;

+ 5 - 1
src/utils/request.ts

@@ -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
+  }
 });
 
 // 请求拦截器

+ 5 - 3
src/views/login.vue

@@ -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;

+ 1 - 1
src/views/register.vue

@@ -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>

+ 15 - 0
src/views/system/menu/index.vue

@@ -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>

+ 2 - 2
src/views/system/user/profile/thirdParty.vue

@@ -56,7 +56,7 @@
 </template>
 
 <script setup lang="ts">
-import { authUnlock, authBinding } from '@/api/system/social/auth';
+import { authUnlock, authRouterUrl } from '@/api/system/social/auth';
 import { propTypes } from '@/utils/propTypes';
 import { useUserStore } from '@/store/modules/user';
 
@@ -84,7 +84,7 @@ const unlockAuth = (row: any) => {
 };
 
 const authUrl = (source: string) => {
-  authBinding(source, useUserStore().tenantId).then((res: any) => {
+  authRouterUrl(source, useUserStore().tenantId).then((res: any) => {
     if (res.code === 200) {
       window.location.href = res.data;
     } else {

+ 2 - 1
vite/plugins/icons.ts

@@ -3,6 +3,7 @@ import Icons from 'unplugin-icons/vite';
 export default () => {
   return Icons({
     // 自动安装图标库
-    autoInstall: true
+    autoInstall: true,
+    compiler: "vue3"
   });
 };