浏览代码

!72 发布 vue 版本 5.1.2 与 cloud 版本 2.1.2
Merge pull request !72 from 疯狂的狮子Li/temp

疯狂的狮子Li 1 年之前
父节点
当前提交
cc9b67d467

+ 2 - 0
.env.development

@@ -20,6 +20,8 @@ VITE_APP_PORT = 80
 
 # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
 VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
+VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
 
 # 客户端id
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'

+ 2 - 0
.env.production

@@ -23,6 +23,8 @@ VITE_APP_PORT = 80
 
 # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
 VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
+# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
+VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
 
 # 客户端id
 VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'

+ 3 - 1
README.md

@@ -17,7 +17,9 @@ npm install --registry=https://registry.npmmirror.com
 # 启动服务
 npm run dev
 
-# 构建生产环境 yarn build:prod
+# 构建生产环境 
+npm run build:prod
+
 # 前端访问地址 http://localhost:80
 ```
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ruoyi-vue-plus",
-  "version": "5.1.1",
+  "version": "5.1.2",
   "description": "RuoYi-Vue-Plus多租户管理系统",
   "author": "LionLi",
   "license": "MIT",

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

@@ -2,7 +2,7 @@
   <div>
     <template v-for="(item, index) in options">
       <template v-if="values.includes(item.value)">
-        <span v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
+        <span v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
               :key="item.value" :index="index" :class="item.elTagClass">
           {{ item.label + " " }}
         </span>
@@ -11,7 +11,7 @@
           :disable-transitions="true"
           :key="item.value + ''"
           :index="index"
-          :type="item.elTagType === 'primary' ? '' : item.elTagType"
+          :type="(item.elTagType === 'primary' || item.elTagType === 'default')? '' : item.elTagType"
           :class="item.elTagClass"
         >
           {{ item.label + " " }}

+ 1 - 1
src/components/IconSelect/index.vue

@@ -2,7 +2,7 @@
   <div class="relative" :style="{ width: width }">
     <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
       <template #prepend>
-        <svg-icon :icon-class="modelValue as string" />
+        <svg-icon :icon-class="modelValue" />
       </template>
     </el-input>
 

+ 2 - 2
src/layout/components/Sidebar/SidebarItem.vue

@@ -18,8 +18,8 @@
       </template>
 
       <sidebar-item
-        v-for="(child) in item.children as RouteOption[]"
-        :key="child.path"
+        v-for="(child, index) in item.children"
+        :key="child.path + index"
         :is-nest="true"
         :item="child"
         :base-path="resolvePath(child.path)"

+ 2 - 0
src/permission.ts

@@ -20,6 +20,8 @@ router.beforeEach(async (to, from, next) => {
     if (to.path === '/login') {
       next({ path: '/' });
       NProgress.done();
+    } else if (whiteList.indexOf(to.path) !== -1) {
+      next()
     } else {
       if (useUserStore().roles.length === 0) {
         isRelogin.show = true;

+ 1 - 1
src/router/index.ts

@@ -139,7 +139,7 @@ export const dynamicRoutes: RouteOption[] = [
     path: '/system/oss-config',
     component: Layout,
     hidden: true,
-    permissions: ['system:oss:list'],
+    permissions: ['system:ossConfig:list'],
     children: [
       {
         path: 'index',

+ 9 - 2
src/store/modules/tagsView.ts

@@ -1,4 +1,4 @@
-import { TagView } from 'vue-router';
+import { TagView, RouteRecordNormalized } from 'vue-router';
 
 export const useTagsViewStore = defineStore('tagsView', () => {
   const visitedViews = ref<TagView[]>([]);
@@ -35,7 +35,9 @@ export const useTagsViewStore = defineStore('tagsView', () => {
   const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => {
     return new Promise((resolve) => {
       delVisitedView(view);
-      delCachedView(view);
+      if (!isDynamicRoute(view)) {
+        delCachedView(view);
+      }
       resolve({
         visitedViews: [...visitedViews.value],
         cachedViews: [...cachedViews.value]
@@ -177,6 +179,11 @@ export const useTagsViewStore = defineStore('tagsView', () => {
     }
   };
 
+  const isDynamicRoute = (view: any): boolean => {
+    // 检查匹配的路由记录中是否有动态段
+    return view.matched.some((m: RouteRecordNormalized) => m.path.includes(':'));
+  };
+
   return {
     visitedViews,
     cachedViews,

+ 6 - 1
src/store/modules/user.ts

@@ -63,6 +63,10 @@ export const useUserStore = defineStore('user', () => {
     removeToken();
   };
 
+  const setAvatar = (value: string) => {
+    avatar.value = value;
+  };
+
   return {
     userId,
     token,
@@ -72,7 +76,8 @@ export const useUserStore = defineStore('user', () => {
     permissions,
     login,
     getInfo,
-    logout
+    logout,
+    setAvatar
   };
 });
 

+ 1 - 0
src/types/env.d.ts

@@ -68,6 +68,7 @@ interface ImportMetaEnv {
   VITE_APP_POWERJOB_ADMIN: string;
   VITE_APP_ENV: string;
   VITE_APP_RSA_PUBLIC_KEY: string;
+  VITE_APP_RSA_PRIVATE_KEY: string;
   VITE_APP_CLIENT_ID: string;
   VITE_APP_WEBSOCKET: string;
 }

+ 21 - 0
src/utils/crypto.ts

@@ -30,6 +30,13 @@ export const encryptBase64 = (str: CryptoJS.lib.WordArray) => {
   return CryptoJS.enc.Base64.stringify(str);
 };
 
+/**
+ * 解密base64
+ */
+export const decryptBase64 = (str: string) => {
+  return CryptoJS.enc.Base64.parse(str);
+};
+
 /**
  * 使用密钥对数据进行加密
  * @param message
@@ -43,3 +50,17 @@ export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray)
   });
   return encrypted.toString();
 };
+
+/**
+ * 使用密钥对数据进行解密
+ * @param message
+ * @param aesKey
+ * @returns {string}
+ */
+export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
+  const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
+    mode: CryptoJS.mode.ECB,
+    padding: CryptoJS.pad.Pkcs7
+  });
+  return decrypted.toString(CryptoJS.enc.Utf8);
+};

+ 1 - 1
src/utils/jsencrypt.ts

@@ -4,7 +4,7 @@ import JSEncrypt from 'jsencrypt';
 const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;
 
 // 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大
-const privateKey = '**********';
+const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY;
 
 // 加密
 export const encrypt = (txt: string) => {

+ 18 - 3
src/utils/request.ts

@@ -8,9 +8,10 @@ import { errorCode } from '@/utils/errorCode';
 import { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
 import FileSaver from 'file-saver';
 import { getLanguage } from '@/lang';
-import { encryptBase64, encryptWithAes, generateAesKey } from '@/utils/crypto';
-import { encrypt } from '@/utils/jsencrypt';
+import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
+import { encrypt, decrypt } from '@/utils/jsencrypt';
 
+const encryptHeader = 'encrypt-key';
 let downloadLoadingInstance: LoadingInstance;
 // 是否显示重新登录
 export const isRelogin = { show: false };
@@ -78,7 +79,7 @@ service.interceptors.request.use(
     if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
       // 生成一个 AES 密钥
       const aesKey = generateAesKey();
-      config.headers['encrypt-key'] = encrypt(encryptBase64(aesKey));
+      config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
       config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);
     }
     // FormData数据去请求头Content-Type
@@ -96,6 +97,20 @@ service.interceptors.request.use(
 // 响应拦截器
 service.interceptors.response.use(
   (res: AxiosResponse) => {
+    // 加密后的 AES 秘钥
+    const keyStr = res.headers[encryptHeader];
+    // 加密
+    if (keyStr != null && keyStr != '') {
+      const data = res.data;
+      // 请求体 AES 解密
+      const base64Str = decrypt(keyStr);
+      // base64 解码 得到请求头的 AES 秘钥
+      const aesKey = decryptBase64(base64Str.toString());
+      // aesKey 解码 data
+      const decryptData = decryptWithAes(data, aesKey);
+      // 将结果 (得到的是 JSON 字符串) 转为 JSON
+      res.data = JSON.parse(decryptData);
+    }
     // 未设置状态码则默认成功状态
     const code = res.data.code || HttpStatus.SUCCESS;
     // 获取错误信息

+ 2 - 2
src/views/index.vue

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

+ 8 - 4
src/views/monitor/online/index.vue

@@ -63,6 +63,8 @@
 <script setup name="Online" lang="ts">
 import { forceLogout, list as initData } from "@/api/monitor/online";
 import { OnlineQuery, OnlineVO } from "@/api/monitor/online/types";
+import api from "@/api/system/user";
+import {to} from "await-to-js";
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
@@ -100,10 +102,12 @@ const resetQuery = () => {
 }
 /** 强退按钮操作 */
 const handleForceLogout = async (row: OnlineVO) => {
-  await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
-  await forceLogout(row.tokenId);
-  await getList();
-  proxy?.$modal.msgSuccess("删除成功");
+  const [err] = await to(proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?') as any);
+  if (!err) {
+    await forceLogout(row.tokenId);
+    await getList();
+    proxy?.$modal.msgSuccess("删除成功");
+  }
 }
 
 onMounted(() => {

+ 2 - 2
src/views/register.vue

@@ -105,12 +105,12 @@ const registerRules: ElFormRules = {
 const codeUrl = ref("");
 const loading = ref(false);
 const captchaEnabled = ref(true);
-const registerRef = ref(ElForm);
+const registerRef = ref<ElFormInstance>();
 // 租户列表
 const tenantList = ref<TenantVO[]>([]);
 
 const handleRegister = () => {
-  registerRef.value.validate(async (valid: boolean) => {
+  registerRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       loading.value = true;
       const [err] = await to(register(registerForm.value));

+ 1 - 1
src/views/system/dept/index.vue

@@ -70,7 +70,7 @@
       </el-table>
     </el-card>
 
-    <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px">
+    <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-body width="600px">
       <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
         <el-row>
           <el-col :span="24" v-if="form.parentId !== 0">

+ 2 - 2
src/views/system/dict/data.vue

@@ -49,8 +49,8 @@
         <el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" />
         <el-table-column label="字典标签" align="center" prop="dictLabel">
           <template #default="scope">
-            <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
-            <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
+            <span v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
+            <el-tag v-else :type="(scope.row.listClass === 'primary' || scope.row.listClass === 'default') ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column label="字典键值" align="center" prop="dictValue" />

+ 1 - 1
src/views/system/menu/index.vue

@@ -169,7 +169,7 @@
               <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
               <template #label>
                 <span>
-                  <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top">
+                  <el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>

+ 5 - 5
src/views/system/oss/config.vue

@@ -29,13 +29,13 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:oss:add']">新增</el-button>
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:ossConfig:add']">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:oss:edit']">修改</el-button>
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:ossConfig:edit']">修改</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:ossConfig:remove']">
               删除
             </el-button>
           </el-col>
@@ -67,10 +67,10 @@
         <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding">
           <template #default="scope">
             <el-tooltip content="修改" placement="top">
-              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:oss:edit']"></el-button>
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:ossConfig:edit']"></el-button>
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button>
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:ossConfig:remove']"></el-button>
             </el-tooltip>
           </template>
         </el-table-column>

+ 1 - 1
src/views/system/role/index.vue

@@ -107,7 +107,7 @@
         <el-form-item prop="roleKey">
           <template #label>
             <span>
-              <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
+              <el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
               权限字符

+ 3 - 3
src/views/system/user/profile/userAvatar.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="user-info-head" @click="editCropper()">
-    <img :src="options.img as string" title="点击上传头像" class="img-circle img-lg" />
+    <img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
     <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
       <el-row>
         <el-col :xs="24" :md="12" :style="{ height: '350px' }">
@@ -62,7 +62,7 @@ import { uploadAvatar } from "@/api/system/user";
 import useUserStore from "@/store/modules/user";
 
 interface Options {
-  img: string | ArrayBuffer | null; // 裁剪图片的地址
+  img: string | any; // 裁剪图片的地址
   autoCrop: boolean; // 是否默认生成截图框
   autoCropWidth: number; // 默认生成截图框宽度
   autoCropHeight: number; // 默认生成截图框高度
@@ -140,7 +140,7 @@ const uploadImg = async () => {
     const res = await uploadAvatar(formData);
     open.value = false;
     options.img = res.data.imgUrl;
-    userStore.avatar = options.img as string
+    userStore.setAvatar(options.img as string)
     proxy?.$modal.msgSuccess("修改成功");
     visible.value = false;
   });

+ 2 - 2
src/views/tool/gen/index.vue

@@ -97,8 +97,8 @@
       <el-tabs v-model="preview.activeName">
         <el-tab-pane
           v-for="(value, key) in preview.data"
-          :label="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))"
-          :name="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))"
+          :label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
+          :name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
           :key="value"
         >
           <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">