Bladeren bron

!212 发布 5.4.0-2.4.0 正式版
Merge pull request !212 from 疯狂的狮子Li/dev

疯狂的狮子Li 10 maanden geleden
bovenliggende
commit
2dc094c1db
82 gewijzigde bestanden met toevoegingen van 605 en 642 verwijderingen
  1. 4 1
      .eslintrc-auto-import.json
  2. 6 0
      README.md
  3. 26 23
      package.json
  4. 1 1
      src/api/system/dept/index.ts
  5. 8 0
      src/api/system/menu/index.ts
  6. 1 1
      src/api/system/user/index.ts
  7. 1 0
      src/api/system/user/types.ts
  8. 2 2
      src/api/workflow/instance/index.ts
  9. 1 1
      src/assets/styles/sidebar.scss
  10. 1 1
      src/components/Breadcrumb/index.vue
  11. 3 3
      src/components/Editor/index.vue
  12. 1 1
      src/components/FileUpload/index.vue
  13. 3 3
      src/components/ImagePreview/index.vue
  14. 2 2
      src/components/ImageUpload/index.vue
  15. 56 0
      src/components/Process/approvalButton.vue
  16. 6 159
      src/components/Process/approvalRecord.vue
  17. 40 0
      src/components/Process/flowChart.vue
  18. 154 0
      src/components/Process/flowChartImg.vue
  19. 2 2
      src/components/Process/submitVerify.vue
  20. 5 5
      src/components/TopNav/index.vue
  21. 0 147
      src/components/TreeSelect/index.vue
  22. 6 6
      src/layout/components/AppMain.vue
  23. 1 1
      src/layout/components/IframeToggle/index.vue
  24. 3 2
      src/layout/components/Navbar.vue
  25. 8 0
      src/layout/components/Settings/index.vue
  26. 1 1
      src/layout/components/Sidebar/SidebarItem.vue
  27. 2 2
      src/layout/components/SocialCallback/index.vue
  28. 6 1
      src/layout/components/TagsView/index.vue
  29. 1 1
      src/layout/components/TopBar/search.vue
  30. 3 4
      src/layout/components/notice/index.vue
  31. 1 1
      src/main.ts
  32. 2 2
      src/permission.ts
  33. 1 98
      src/router/index.ts
  34. 5 8
      src/settings.ts
  35. 1 1
      src/store/index.ts
  36. 2 2
      src/store/modules/permission.ts
  37. 3 1
      src/store/modules/settings.ts
  38. 4 2
      src/types/global.d.ts
  39. 0 1
      src/types/module.d.ts
  40. 2 1
      src/utils/request.ts
  41. 2 2
      src/utils/ruoyi.ts
  42. 1 1
      src/views/demo/demo/index.vue
  43. 1 0
      src/views/demo/tree/index.vue
  44. 1 1
      src/views/error/401.vue
  45. 1 1
      src/views/error/404.vue
  46. 3 3
      src/views/index.vue
  47. 1 2
      src/views/login.vue
  48. 1 0
      src/views/monitor/logininfor/index.vue
  49. 1 0
      src/views/monitor/online/index.vue
  50. 1 0
      src/views/monitor/operlog/index.vue
  51. 0 1
      src/views/register.vue
  52. 2 2
      src/views/system/client/index.vue
  53. 1 1
      src/views/system/config/index.vue
  54. 1 0
      src/views/system/dept/index.vue
  55. 1 1
      src/views/system/dict/data.vue
  56. 1 1
      src/views/system/dict/index.vue
  57. 74 7
      src/views/system/menu/index.vue
  58. 1 1
      src/views/system/notice/index.vue
  59. 14 4
      src/views/system/oss/config.vue
  60. 5 4
      src/views/system/oss/index.vue
  61. 1 1
      src/views/system/post/index.vue
  62. 1 1
      src/views/system/role/authUser.vue
  63. 10 10
      src/views/system/role/index.vue
  64. 1 1
      src/views/system/role/selectUser.vue
  65. 3 3
      src/views/system/tenant/index.vue
  66. 6 6
      src/views/system/tenantPackage/index.vue
  67. 10 3
      src/views/system/user/authRole.vue
  68. 5 2
      src/views/system/user/index.vue
  69. 1 1
      src/views/system/user/profile/onlineDevice.vue
  70. 1 1
      src/views/system/user/profile/thirdParty.vue
  71. 1 1
      src/views/system/user/profile/userAvatar.vue
  72. 1 1
      src/views/tool/gen/editTable.vue
  73. 1 1
      src/views/tool/gen/importTable.vue
  74. 3 3
      src/views/tool/gen/index.vue
  75. 40 43
      src/views/workflow/category/index.vue
  76. 1 1
      src/views/workflow/leave/index.vue
  77. 17 34
      src/views/workflow/leave/leaveEdit.vue
  78. 3 3
      src/views/workflow/processDefinition/design.vue
  79. 6 3
      src/views/workflow/processDefinition/index.vue
  80. 2 2
      src/views/workflow/processInstance/index.vue
  81. 2 1
      src/views/workflow/task/allTaskWaiting.vue
  82. 1 1
      src/views/workflow/task/myDocument.vue

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

@@ -315,6 +315,9 @@
     "watchThrottled": true,
     "watchTriggerable": true,
     "watchWithFilter": true,
-    "whenever": true
+    "whenever": true,
+    "Slot": true,
+    "Slots": true,
+    "createRef": true
   }
 }

+ 6 - 0
README.md

@@ -2,6 +2,7 @@
 
 - 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
 - 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
+- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
 
 ## 配套后端代码仓库地址
 
@@ -10,6 +11,11 @@
 | 🔥 分布式集群框架 | RuoYi-Vue-Plus   | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus)      |
 | 🔥 微服务框架   | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
 
+## 分支说明
+
+- ts分支(稳定发布主分支 生产可用)
+- dev分支(开发分支 开发过程中使用)
+
 ## 前端运行
 
 ```bash

+ 26 - 23
package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package",
   "name": "ruoyi-vue-plus",
-  "version": "5.3.1-2.3.0",
+  "version": "5.4.0-2.4.0",
   "description": "RuoYi-Vue-Plus多租户管理系统",
   "author": "LionLi",
   "license": "MIT",
@@ -23,31 +23,31 @@
     "@element-plus/icons-vue": "2.3.1",
     "@highlightjs/vue-plugin": "2.1.0",
     "@vueup/vue-quill": "1.2.0",
-    "@vueuse/core": "12.7.0",
+    "@vueuse/core": "13.1.0",
     "animate.css": "4.1.1",
     "await-to-js": "3.0.0",
-    "axios": "1.7.8",
+    "axios": "1.8.4",
     "crypto-js": "4.2.0",
-    "echarts": "5.5.0",
-    "element-plus": "2.8.8",
+    "echarts": "5.6.0",
+    "element-plus": "2.9.8",
     "file-saver": "2.0.5",
     "highlight.js": "11.9.0",
     "image-conversion": "2.1.1",
     "js-cookie": "3.0.5",
     "jsencrypt": "3.3.2",
     "nprogress": "0.2.0",
-    "pinia": "2.2.6",
+    "pinia": "3.0.2",
     "screenfull": "6.0.2",
     "vue": "3.5.13",
     "vue-cropper": "1.1.1",
-    "vue-i18n": "10.0.5",
+    "vue-i18n": "11.1.3",
     "vue-json-pretty": "2.4.0",
-    "vue-router": "4.4.5",
-    "vue-types": "5.1.3",
-    "vxe-table": "4.5.22"
+    "vue-router": "4.5.0",
+    "vue-types": "6.0.0",
+    "vxe-table": "4.13.7"
   },
   "devDependencies": {
-    "@iconify/json": "2.2.276",
+    "@iconify/json": "^2.2.276",
     "@types/crypto-js": "4.2.2",
     "@types/file-saver": "2.0.7",
     "@types/js-cookie": "3.0.6",
@@ -56,8 +56,8 @@
     "@unocss/preset-attributify": "66.0.0",
     "@unocss/preset-icons": "66.0.0",
     "@unocss/preset-uno": "66.0.0",
-    "@vitejs/plugin-vue": "5.2.1",
-    "@vue/compiler-sfc": "3.4.23",
+    "@vitejs/plugin-vue": "5.2.3",
+    "@vue/compiler-sfc": "3.5.13",
     "@vue/eslint-config-prettier": "10.2.0",
     "@vue/eslint-config-typescript": "14.4.0",
     "autoprefixer": "10.4.20",
@@ -66,19 +66,22 @@
     "eslint-plugin-vue": "9.32.0",
     "globals": "16.0.0",
     "prettier": "3.5.2",
-    "sass": "1.84.0",
-    "typescript": "~5.7.3",
+    "sass": "1.87.0",
+    "typescript": "~5.8.3",
     "unocss": "66.0.0",
-    "unplugin-auto-import": "0.17.5",
-    "unplugin-icons": "0.18.5",
-    "unplugin-vue-components": "28.0.0",
+    "unplugin-auto-import": "19.1.2",
+    "unplugin-icons": "22.1.0",
+    "unplugin-vue-components": "28.5.0",
     "unplugin-vue-setup-extend-plus": "1.0.1",
-    "vite": "5.4.11",
+    "vite": "6.3.2",
     "vite-plugin-compression": "0.5.1",
-    "vite-plugin-svg-icons-ng": "^1.2.2",
-    "vite-plugin-vue-devtools": "7.7.1",
-    "vitest": "3.0.5",
-    "vue-tsc": "^2.2.2"
+    "vite-plugin-svg-icons-ng": "^1.4.0",
+    "vite-plugin-vue-devtools": "7.7.5",
+    "vitest": "3.1.2",
+    "vue-tsc": "^2.2.8"
+  },
+  "overrides": {
+    "quill": "2.0.2"
   },
   "engines": {
     "node": ">=18.18.0",

+ 1 - 1
src/api/system/dept/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types';
+import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
 
 // 查询部门列表
 export const listDept = (query?: DeptQuery) => {

+ 8 - 0
src/api/system/menu/index.ts

@@ -68,3 +68,11 @@ export const delMenu = (menuId: string | number) => {
     method: 'delete'
   });
 };
+
+// 级联删除菜单
+export const cascadeDelMenu = (menuIds: Array<string | number>) => {
+  return request({
+    url: '/system/menu/cascade/' + menuIds,
+    method: 'delete'
+  });
+};

+ 1 - 1
src/api/system/user/index.ts

@@ -1,4 +1,4 @@
-import {DeptTreeVO, DeptVO} from './../dept/types';
+import { DeptTreeVO, DeptVO } from './../dept/types';
 import { RoleVO } from '@/api/system/role/types';
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';

+ 1 - 0
src/api/system/user/types.ts

@@ -15,6 +15,7 @@ export interface UserInfo {
  */
 export interface UserQuery extends PageQuery {
   userName?: string;
+  nickName?: string;
   phonenumber?: string;
   status?: string;
   deptId?: string | number;

+ 2 - 2
src/api/workflow/instance/index.ts

@@ -31,9 +31,9 @@ export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<FlowInstanc
 /**
  * 通过业务id获取历史流程图
  */
-export const flowImage = (businessId: string | number) => {
+export const flowHisTaskList = (businessId: string | number) => {
   return request({
-    url: `/workflow/instance/flowImage/${businessId}` + '?t' + Math.random(),
+    url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
     method: 'get'
   });
 };

+ 1 - 1
src/assets/styles/sidebar.scss

@@ -26,7 +26,7 @@
     z-index: 1001;
     overflow: hidden;
     -webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
-    box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+    box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
 
     // reset element-ui css
     .horizontal-collapse-transition {

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

@@ -44,7 +44,7 @@ const findPathNum = (str, char = '/') => {
   return str.split(char).length - 1;
 };
 const getMatched = (pathList, routeList, matched) => {
-  let data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
+  const data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
   if (data) {
     matched.push(data);
     if (data.children && pathList.length) {

+ 3 - 3
src/components/Editor/index.vue

@@ -95,7 +95,7 @@ const options = ref<any>({
 });
 
 const styles = computed(() => {
-  let style: any = {};
+  const style: any = {};
   if (props.minHeight) {
     style.minHeight = `${props.minHeight}px`;
   }
@@ -121,9 +121,9 @@ const handleUploadSuccess = (res: any) => {
   // 如果上传成功
   if (res.code === 200) {
     // 获取富文本实例
-    let quill = toRaw(quillEditorRef.value).getQuill();
+    const quill = toRaw(quillEditorRef.value).getQuill();
     // 获取光标位置
-    let length = quill.selection.savedRange.index;
+    const length = quill.selection.savedRange.index;
     // 插入图片,res为服务器返回的图片链接地址
     quill.insertEmbed(length, 'image', res.data.url);
     // 调整光标到最后

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

@@ -176,7 +176,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
 
 // 删除文件
 const handleDelete = (index: number) => {
-  let ossId = fileList.value[index].ossId;
+  const ossId = fileList.value[index].ossId;
   delOss(ossId);
   fileList.value.splice(index, 1);
   emit('update:modelValue', listToString(fileList.value));

+ 3 - 3
src/components/ImagePreview/index.vue

@@ -27,7 +27,7 @@ const realSrc = computed(() => {
   if (!props.src) {
     return;
   }
-  let real_src = props.src.split(',')[0];
+  const real_src = props.src.split(',')[0];
   return real_src;
 });
 
@@ -35,8 +35,8 @@ const realSrcList = computed(() => {
   if (!props.src) {
     return [];
   }
-  let real_src_list = props.src.split(',');
-  let srcList: string[] = [];
+  const real_src_list = props.src.split(',');
+  const srcList: string[] = [];
   real_src_list.forEach((item: string) => {
     if (item.trim() === '') {
       return;

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

@@ -189,7 +189,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
 const handleDelete = (file: UploadFile): boolean => {
   const findex = fileList.value.map((f) => f.name).indexOf(file.name);
   if (findex > -1 && uploadList.value.length === number.value) {
-    let ossId = fileList.value[findex].ossId;
+    const ossId = fileList.value[findex].ossId;
     delOss(ossId);
     fileList.value.splice(findex, 1);
     emit('update:modelValue', listToString(fileList.value));
@@ -225,7 +225,7 @@ const handlePictureCardPreview = (file: any) => {
 const listToString = (list: any[], separator?: string) => {
   let strs = '';
   separator = separator || ',';
-  for (let i in list) {
+  for (const i in list) {
     if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
       strs += list[i].ossId + separator;
     }

+ 56 - 0
src/components/Process/approvalButton.vue

@@ -0,0 +1,56 @@
+<template>
+  <div style="display: flex; justify-content: space-between">
+    <div>
+      <el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft')">暂存</el-button>
+      <el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit')">提 交</el-button>
+      <el-button v-if="approvalButtonShow" :loading="props.buttonLoading" type="primary" @click="approvalVerifyOpen">审批</el-button>
+      <el-button v-if="props.id && props.status !== 'draft'" type="primary" @click="handleApprovalRecord">流程进度</el-button>
+      <slot />
+    </div>
+    <div>
+      <el-button style="float: right" @click="goBack()">返回</el-button>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { propTypes } from '@/utils/propTypes';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const props = defineProps({
+  status: propTypes.string.def(''),
+  pageType: propTypes.string.def(''),
+  buttonLoading: propTypes.bool.def(false),
+  id: propTypes.string.def('') || propTypes.number.def()
+});
+const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
+//暂存,提交
+const submitForm = async (type) => {
+  emits('submitForm', type);
+};
+//审批
+const approvalVerifyOpen = async () => {
+  emits('approvalVerifyOpen');
+};
+//审批记录
+const handleApprovalRecord = () => {
+  emits('handleApprovalRecord');
+};
+
+//校验提交按钮是否显示
+const submitButtonShow = computed(() => {
+  return (
+    props.pageType === 'add' ||
+    (props.pageType === 'update' && props.status && (props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
+  );
+});
+
+//校验审批按钮是否显示
+const approvalButtonShow = computed(() => {
+  return props.pageType === 'approval' && props.status && props.status === 'waiting';
+});
+
+//返回
+const goBack = () => {
+  proxy.$tab.closePage(proxy.$route);
+  proxy.$router.go(-1);
+};
+</script>

+ 6 - 159
src/components/Process/approvalRecord.vue

@@ -3,21 +3,7 @@
     <el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false">
       <el-tabs v-model="tabActiveName" class="demo-tabs">
         <el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh">
-          <div
-            ref="imageWrapperRef"
-            class="image-wrapper"
-            @wheel="handleMouseWheel"
-            @mousedown="handleMouseDown"
-            @mousemove="handleMouseMove"
-            @mouseup="handleMouseUp"
-            @mouseleave="handleMouseLeave"
-            @dblclick="resetTransform"
-            :style="transformStyle"
-          >
-            <el-card class="box-card">
-              <el-image :src="imgUrl" class="scalable-image" />
-            </el-card>
-          </div>
+          <flowChart :ins-id="insId" v-if="insId" />
         </el-tab-pane>
         <el-tab-pane v-loading="loading" label="审批信息" name="info">
           <div>
@@ -73,10 +59,10 @@
   </div>
 </template>
 <script setup lang="ts">
-import { flowImage } from '@/api/workflow/instance';
+import { flowHisTaskList } from '@/api/workflow/instance';
 import { propTypes } from '@/utils/propTypes';
 import { listByIds } from '@/api/system/oss';
-
+import FlowChart from '@/components/Process/flowChart.vue';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
 const props = defineProps({
@@ -87,7 +73,7 @@ const loading = ref(false);
 const visible = ref(false);
 const historyList = ref<Array<any>>([]);
 const tabActiveName = ref('image');
-const imgUrl = ref('');
+const insId = ref(null);
 
 //初始化查询审批记录
 const init = async (businessId: string | number) => {
@@ -95,10 +81,10 @@ const init = async (businessId: string | number) => {
   loading.value = true;
   tabActiveName.value = 'image';
   historyList.value = [];
-  flowImage(businessId).then((resp) => {
+  flowHisTaskList(businessId).then((resp) => {
     if (resp.data) {
       historyList.value = resp.data.list;
-      imgUrl.value = 'data:image/gif;base64,' + resp.data.image;
+      insId.value = resp.data.instanceId;
       if (historyList.value.length > 0) {
         historyList.value.forEach((item) => {
           if (item.ext) {
@@ -124,109 +110,6 @@ const handleDownload = (ossId: string) => {
   proxy?.$download.oss(ossId);
 };
 
-const imageWrapperRef = ref<HTMLElement | null>(null);
-const scale = ref(1); // 初始缩放比例
-const maxScale = 3; // 最大缩放比例
-const minScale = 0.5; // 最小缩放比例
-
-let isDragging = false;
-let startX = 0;
-let startY = 0;
-let currentTranslateX = 0;
-let currentTranslateY = 0;
-
-const handleMouseWheel = (event: WheelEvent) => {
-  event.preventDefault();
-  let newScale = scale.value - event.deltaY / 1000;
-  newScale = Math.max(minScale, Math.min(newScale, maxScale));
-  if (newScale !== scale.value) {
-    scale.value = newScale;
-    resetDragPosition(); // 重置拖拽位置,使图片居中
-  }
-};
-
-const handleMouseDown = (event: MouseEvent) => {
-  if (scale.value > 1) {
-    event.preventDefault(); // 阻止默认行为,防止拖拽
-    isDragging = true;
-    startX = event.clientX;
-    startY = event.clientY;
-  }
-};
-
-const handleMouseMove = (event: MouseEvent) => {
-  if (!isDragging || !imageWrapperRef.value) return;
-
-  const deltaX = event.clientX - startX;
-  const deltaY = event.clientY - startY;
-  startX = event.clientX;
-  startY = event.clientY;
-
-  currentTranslateX += deltaX;
-  currentTranslateY += deltaY;
-
-  // 边界检测,防止图片被拖出容器
-  const bounds = getBounds();
-  if (currentTranslateX > bounds.maxTranslateX) {
-    currentTranslateX = bounds.maxTranslateX;
-  } else if (currentTranslateX < bounds.minTranslateX) {
-    currentTranslateX = bounds.minTranslateX;
-  }
-
-  if (currentTranslateY > bounds.maxTranslateY) {
-    currentTranslateY = bounds.maxTranslateY;
-  } else if (currentTranslateY < bounds.minTranslateY) {
-    currentTranslateY = bounds.minTranslateY;
-  }
-
-  applyTransform();
-};
-
-const handleMouseUp = () => {
-  isDragging = false;
-};
-
-const handleMouseLeave = () => {
-  isDragging = false;
-};
-
-const resetTransform = () => {
-  scale.value = 1;
-  currentTranslateX = 0;
-  currentTranslateY = 0;
-  applyTransform();
-};
-
-const resetDragPosition = () => {
-  currentTranslateX = 0;
-  currentTranslateY = 0;
-  applyTransform();
-};
-
-const applyTransform = () => {
-  if (imageWrapperRef.value) {
-    imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
-  }
-};
-
-const getBounds = () => {
-  if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
-
-  const imgRect = imageWrapperRef.value.getBoundingClientRect();
-  const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
-
-  const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
-  const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
-  const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
-  const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
-
-  return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
-};
-
-const transformStyle = computed(() => ({
-  transition: isDragging ? 'none' : 'transform 0.2s ease'
-}));
-
 /**
  * 对外暴露子组件方法
  */
@@ -235,46 +118,10 @@ defineExpose({
 });
 </script>
 <style lang="scss" scoped>
-.triangle {
-  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
-  border-radius: 6px;
-}
-
-.triangle::after {
-  content: ' ';
-  position: absolute;
-  top: 8em;
-  right: 215px;
-  border: 15px solid;
-  border-color: transparent #fff transparent transparent;
-}
-
 .container {
   :deep(.el-dialog .el-dialog__body) {
     max-height: calc(100vh - 170px) !important;
     min-height: calc(100vh - 170px) !important;
   }
 }
-
-.image-wrapper {
-  width: 100%;
-  overflow: hidden;
-  position: relative;
-  margin: 0 auto;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  user-select: none; /* 禁用文本选择 */
-  cursor: grab; /* 设置初始鼠标指针为可拖动 */
-}
-
-.image-wrapper:active {
-  cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
-}
-
-.scalable-image {
-  object-fit: contain;
-  width: 100%;
-  padding: 15px;
-}
 </style>

+ 40 - 0
src/components/Process/flowChart.vue

@@ -0,0 +1,40 @@
+<template>
+  <div>
+    <div style="height: 68vh" class="iframe-wrapper">
+      <iframe :src="iframeUrl" style="width: 100%; height: 100%" frameborder="0" scrolling="no" class="custom-iframe" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getToken } from '@/utils/auth';
+
+// Props 定义方式变化
+const props = defineProps({
+  insId: {
+    type: [String, Number],
+    default: null
+  }
+});
+
+const iframeUrl = ref('');
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
+onMounted(async () => {
+  const url = baseUrl + `/warm-flow-ui/index.html?id=${props.insId}&type=FlowChart`;
+  iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
+});
+</script>
+<style scoped>
+.iframe-wrapper {
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.custom-iframe {
+  width: 100%;
+  border: none;
+  background: transparent;
+}
+</style>

+ 154 - 0
src/components/Process/flowChartImg.vue

@@ -0,0 +1,154 @@
+<template>
+  <div
+    ref="imageWrapperRef"
+    class="image-wrapper"
+    @wheel="handleMouseWheel"
+    @mousedown="handleMouseDown"
+    @mousemove="handleMouseMove"
+    @mouseup="handleMouseUp"
+    @mouseleave="handleMouseLeave"
+    @dblclick="resetTransform"
+    :style="transformStyle"
+  >
+    <el-card class="box-card">
+      <el-image :src="props.imgUrl" class="scalable-image" />
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+// Props 定义方式变化
+const props = defineProps({
+  imgUrl: {
+    type: String,
+    default: () => ''
+  }
+});
+
+const imageWrapperRef = ref<HTMLElement | null>(null);
+const scale = ref(1); // 初始缩放比例
+const maxScale = 3; // 最大缩放比例
+const minScale = 0.5; // 最小缩放比例
+
+let isDragging = false;
+let startX = 0;
+let startY = 0;
+let currentTranslateX = 0;
+let currentTranslateY = 0;
+
+const handleMouseWheel = (event: WheelEvent) => {
+  event.preventDefault();
+  let newScale = scale.value - event.deltaY / 1000;
+  newScale = Math.max(minScale, Math.min(newScale, maxScale));
+  if (newScale !== scale.value) {
+    scale.value = newScale;
+    resetDragPosition(); // 重置拖拽位置,使图片居中
+  }
+};
+
+const handleMouseDown = (event: MouseEvent) => {
+  if (scale.value > 1) {
+    event.preventDefault(); // 阻止默认行为,防止拖拽
+    isDragging = true;
+    startX = event.clientX;
+    startY = event.clientY;
+  }
+};
+
+const handleMouseMove = (event: MouseEvent) => {
+  if (!isDragging || !imageWrapperRef.value) return;
+
+  const deltaX = event.clientX - startX;
+  const deltaY = event.clientY - startY;
+  startX = event.clientX;
+  startY = event.clientY;
+
+  currentTranslateX += deltaX;
+  currentTranslateY += deltaY;
+
+  // 边界检测,防止图片被拖出容器
+  const bounds = getBounds();
+  if (currentTranslateX > bounds.maxTranslateX) {
+    currentTranslateX = bounds.maxTranslateX;
+  } else if (currentTranslateX < bounds.minTranslateX) {
+    currentTranslateX = bounds.minTranslateX;
+  }
+
+  if (currentTranslateY > bounds.maxTranslateY) {
+    currentTranslateY = bounds.maxTranslateY;
+  } else if (currentTranslateY < bounds.minTranslateY) {
+    currentTranslateY = bounds.minTranslateY;
+  }
+
+  applyTransform();
+};
+
+const handleMouseUp = () => {
+  isDragging = false;
+};
+
+const handleMouseLeave = () => {
+  isDragging = false;
+};
+
+const resetTransform = () => {
+  scale.value = 1;
+  currentTranslateX = 0;
+  currentTranslateY = 0;
+  applyTransform();
+};
+
+const resetDragPosition = () => {
+  currentTranslateX = 0;
+  currentTranslateY = 0;
+  applyTransform();
+};
+
+const applyTransform = () => {
+  if (imageWrapperRef.value) {
+    imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
+  }
+};
+
+const getBounds = () => {
+  if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
+
+  const imgRect = imageWrapperRef.value.getBoundingClientRect();
+  const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
+
+  const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
+  const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
+  const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
+  const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
+
+  return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
+};
+
+const transformStyle = computed(() => ({
+  transition: isDragging ? 'none' : 'transform 0.2s ease'
+}));
+</script>
+
+<style scoped>
+.image-wrapper {
+  width: 100%;
+  overflow: hidden;
+  position: relative;
+  margin: 0 auto;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  user-select: none; /* 禁用文本选择 */
+  cursor: grab; /* 设置初始鼠标指针为可拖动 */
+}
+
+.image-wrapper:active {
+  cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
+}
+
+.scalable-image {
+  object-fit: contain;
+  width: 100%;
+  padding: 15px;
+}
+</style>

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

@@ -8,10 +8,10 @@
           <el-checkbox value="3" name="type">短信</el-checkbox>
         </el-checkbox-group>
       </el-form-item>
-      <el-form-item v-if="task.flowStatus === 'waiting'" label="附件">
+      <el-form-item label="附件">
         <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="task.flowStatus === 'waiting' && buttonObj.copy">
+      <el-form-item label="抄送" v-if="buttonObj.copy">
         <el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
         <el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
           {{ user.nickName }}

+ 5 - 5
src/components/TopNav/index.vue

@@ -47,11 +47,11 @@ const routers = computed(() => permissionStore.getTopbarRoutes());
 
 // 顶部显示菜单
 const topMenus = computed(() => {
-  let topMenus: RouteRecordRaw[] = [];
+  const topMenus: RouteRecordRaw[] = [];
   routers.value.map((menu) => {
     if (menu.hidden !== true) {
       // 兼容顶部栏一级菜单内部跳转
-      if (menu.path === '/') {
+      if (menu.path === '/' && menu.children) {
         topMenus.push(menu.children ? menu.children[0] : menu);
       } else {
         topMenus.push(menu);
@@ -63,7 +63,7 @@ const topMenus = computed(() => {
 
 // 设置子路由
 const childrenMenus = computed(() => {
-  let childrenMenus: RouteRecordRaw[] = [];
+  const childrenMenus: RouteRecordRaw[] = [];
   routers.value.map((router) => {
     router.children?.forEach((item) => {
       if (item.parentPath === undefined) {
@@ -118,7 +118,7 @@ const handleSelect = (key: string) => {
     // 没有子路由路径内部打开
     const routeMenu = childrenMenus.value.find((item) => item.path === key);
     if (routeMenu && routeMenu.query) {
-      let query = JSON.parse(routeMenu.query);
+      const query = JSON.parse(routeMenu.query);
       router.push({ path: key, query: query });
     } else {
       router.push({ path: key });
@@ -132,7 +132,7 @@ const handleSelect = (key: string) => {
 };
 
 const activeRoutes = (key: string) => {
-  let routes: RouteRecordRaw[] = [];
+  const routes: RouteRecordRaw[] = [];
   if (childrenMenus.value && childrenMenus.value.length > 0) {
     childrenMenus.value.map((item) => {
       if (key == item.parentPath || (key == 'index' && '' == item.path)) {

+ 0 - 147
src/components/TreeSelect/index.vue

@@ -1,147 +0,0 @@
-<template>
-  <div class="el-tree-select">
-    <el-select
-      ref="treeSelect"
-      v-model="valueId"
-      style="width: 100%"
-      :filterable="true"
-      :clearable="true"
-      :filter-method="selectFilterData as any"
-      :placeholder="placeholder"
-      @clear="clearHandle"
-    >
-      <el-option :value="valueId" :label="valueTitle">
-        <el-tree
-          id="tree-option"
-          ref="selectTree"
-          :accordion="accordion"
-          :data="options"
-          :props="objMap as any"
-          :node-key="objMap.value"
-          :expand-on-click-node="false"
-          :default-expanded-keys="defaultExpandedKey"
-          :filter-node-method="filterNode"
-          @node-click="handleNodeClick"
-        ></el-tree>
-      </el-option>
-    </el-select>
-  </div>
-</template>
-
-<script setup lang="ts">
-interface ObjMap {
-  value: string;
-  label: string;
-  children: string;
-}
-interface Props {
-  objMap: ObjMap;
-  accordion: boolean;
-  value: string | number;
-  options: any[];
-  placeholder: string;
-}
-
-const props = withDefaults(defineProps<Props>(), {
-  objMap: () => {
-    return {
-      value: 'id',
-      label: 'label',
-      children: 'children'
-    };
-  },
-  accordion: false,
-  value: '',
-  options: () => [],
-  placeholder: ''
-});
-
-const selectTree = ref<ElTreeSelectInstance>();
-
-const emit = defineEmits(['update:value']);
-
-const valueId = computed({
-  get: () => props.value,
-  set: (val) => {
-    emit('update:value', val);
-  }
-});
-const valueTitle = ref('');
-const defaultExpandedKey = ref<any[]>([]);
-
-const initHandle = () => {
-  nextTick(() => {
-    const selectedValue = valueId.value;
-    if (selectedValue !== null && typeof selectedValue !== 'undefined') {
-      const node = selectTree.value?.getNode(selectedValue);
-      if (node) {
-        valueTitle.value = node.data[props.objMap.label];
-        selectTree.value?.setCurrentKey(selectedValue); // 设置默认选中
-        defaultExpandedKey.value = [selectedValue]; // 设置默认展开
-      }
-    } else {
-      clearHandle();
-    }
-  });
-};
-const handleNodeClick = (node: any) => {
-  valueTitle.value = node[props.objMap.label];
-  valueId.value = node[props.objMap.value];
-  defaultExpandedKey.value = [];
-  selectTree.value?.blur();
-  selectFilterData('');
-};
-const selectFilterData = (val: any) => {
-  selectTree.value?.filter(val);
-};
-const filterNode = (value: any, data: any) => {
-  if (!value) return true;
-  return data[props.objMap['label']].indexOf(value) !== -1;
-};
-const clearHandle = () => {
-  valueTitle.value = '';
-  valueId.value = '';
-  defaultExpandedKey.value = [];
-  clearSelected();
-};
-const clearSelected = () => {
-  const allNode = document.querySelectorAll('#tree-option .el-tree-node');
-  allNode.forEach((element) => element.classList.remove('is-current'));
-};
-
-onMounted(() => {
-  initHandle();
-});
-
-watch(valueId, () => {
-  initHandle();
-});
-</script>
-
-<style lang="scss" scoped>
-@import '@/assets/styles/variables.module.scss';
-
-.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
-  padding: 0;
-  background-color: #fff;
-  height: auto;
-}
-
-.el-select-dropdown__item.selected {
-  font-weight: normal;
-}
-
-ul li .el-tree .el-tree-node__content {
-  height: auto;
-  padding: 0 20px;
-  box-sizing: border-box;
-}
-
-:deep(.el-tree-node__content:hover),
-:deep(.el-tree-node__content:active),
-:deep(.is-current > div:first-child),
-:deep(.el-tree-node__content:focus) {
-  background-color: mix(#fff, $--color-primary, 90%);
-  color: $--color-primary;
-}
-</style>

+ 6 - 6
src/layout/components/AppMain.vue

@@ -40,16 +40,16 @@ watch(
 );
 
 onMounted(() => {
-  addIframe()
-})
+  addIframe();
+});
 
-watchEffect((route) => {
-  addIframe()
-})
+watchEffect(() => {
+  addIframe();
+});
 
 function addIframe() {
   if (route.meta.link) {
-    useTagsViewStore().addIframeView(route)
+    useTagsViewStore().addIframeView(route);
   }
 }
 </script>

+ 1 - 1
src/layout/components/IframeToggle/index.vue

@@ -18,7 +18,7 @@ const tagsViewStore = useTagsViewStore();
 
 function iframeUrl(url: string | undefined, query: any) {
   if (Object.keys(query).length > 0) {
-    let params = Object.keys(query)
+    const params = Object.keys(query)
       .map((key) => key + '=' + query[key])
       .join('&');
     return url + '?' + params;

+ 3 - 2
src/layout/components/Navbar.vue

@@ -33,7 +33,7 @@
             <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
               <template #reference>
                 <el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
-                  <svg-icon icon-class="message" />
+                  <div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
                 </el-badge>
               </template>
               <template #default>
@@ -98,7 +98,7 @@ import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
 import { TenantVO } from '@/api/types';
 import notice from './notice/index.vue';
 import router from '@/router';
-import {ElMessageBoxOptions} from "element-plus/es/components/message-box/src/message-box.type";
+import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
 
 const appStore = useAppStore();
 const userStore = useUserStore();
@@ -171,6 +171,7 @@ const logout = async () => {
         redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
       }
     });
+    proxy?.$tab.closeAllPage();
   });
 };
 

+ 8 - 0
src/layout/components/Settings/index.vue

@@ -59,6 +59,13 @@
       </span>
     </div>
 
+    <div class="drawer-item">
+      <span>显示页签图标</span>
+      <span class="comp-style">
+        <el-switch v-model="settingsStore.tagsIcon" :disabled="!settingsStore.tagsView" class="drawer-switch" />
+      </span>
+    </div>
+
     <div class="drawer-item">
       <span>固定 Header</span>
       <span class="comp-style">
@@ -153,6 +160,7 @@ const saveSetting = () => {
   const settings = useStorage<LayoutSetting>('layout-setting', defaultSettings);
   settings.value.topNav = storeSettings.value.topNav;
   settings.value.tagsView = storeSettings.value.tagsView;
+  settings.value.tagsIcon = storeSettings.value.tagsIcon;
   settings.value.fixedHeader = storeSettings.value.fixedHeader;
   settings.value.sidebarLogo = storeSettings.value.sidebarLogo;
   settings.value.dynamicTitle = storeSettings.value.dynamicTitle;

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

@@ -86,7 +86,7 @@ const resolvePath = (routePath: string, routeQuery?: string): any => {
     return props.basePath;
   }
   if (routeQuery) {
-    let query = JSON.parse(routeQuery);
+    const query = JSON.parse(routeQuery);
     return { path: getNormalPath(props.basePath + '/' + routePath), query: query };
   }
   return getNormalPath(props.basePath + '/' + routePath);

+ 2 - 2
src/layout/components/SocialCallback/index.vue

@@ -63,9 +63,9 @@ const loginByCode = async (data: LoginData) => {
 
 const init = async () => {
   // 如果域名不相等 则重定向处理
-  let host = window.location.host;
+  const host = window.location.host;
   if (domain !== host) {
-    let urlFull = new URL(window.location.href);
+    const urlFull = new URL(window.location.href);
     urlFull.host = domain;
     window.location.href = urlFull.toString();
     return;

+ 6 - 1
src/layout/components/TagsView/index.vue

@@ -5,13 +5,14 @@
         v-for="tag in visitedViews"
         :key="tag.path"
         :data-path="tag.path"
-        :class="isActive(tag) ? 'active' : ''"
+        :class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
         :to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
         class="tags-view-item"
         :style="activeStyle(tag)"
         @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
         @contextmenu.prevent="openMenu(tag, $event)"
       >
+        <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
         {{ tag.title }}
         <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
           <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
@@ -51,6 +52,7 @@ const router = useRouter();
 const visitedViews = computed(() => useTagsViewStore().getVisitedViews());
 const routes = computed(() => usePermissionStore().getRoutes());
 const theme = computed(() => useSettingsStore().theme);
+const tagsIcon = computed(() => useSettingsStore().tagsIcon)
 
 watch(route, () => {
   addTags();
@@ -285,6 +287,9 @@ onMounted(() => {
       }
     }
   }
+  .tags-view-item.active.has-icon::before {
+    content: none !important;
+  }
   .contextmenu {
     margin: 0;
     background: var(--el-bg-color);

+ 1 - 1
src/layout/components/TopBar/search.vue

@@ -67,7 +67,7 @@ const closeSearch = () => {
 };
 // 菜单搜索数据过滤
 const menuSearch = (queryString: string, cb: (options: any[]) => void) => {
-  let options = state.menuList.filter((item) => {
+  const options = state.menuList.filter((item) => {
     return item.title.indexOf(queryString) > -1;
   });
   cb(options);

+ 3 - 4
src/layout/components/notice/index.vue

@@ -24,10 +24,9 @@
 </template>
 
 <script setup lang="ts" name="layoutBreadcrumbUserNews">
-import { storeToRefs } from 'pinia';
 import { useNoticeStore } from '@/store/modules/notice';
 
-const noticeStore = storeToRefs(useNoticeStore());
+const noticeStore = useNoticeStore();
 const { readAll } = useNoticeStore();
 
 // 定义变量内容
@@ -42,7 +41,7 @@ const newsList = ref([]) as any;
  */
 const getTableData = async () => {
   state.loading = true;
-  newsList.value = noticeStore.state.value.notices;
+  newsList.value = noticeStore.state.notices;
   state.loading = false;
 };
 
@@ -50,7 +49,7 @@ const getTableData = async () => {
 const onNewsClick = (item: any) => {
   newsList.value[item].read = true;
   //并且写入pinia
-  noticeStore.state.value.notices = newsList.value;
+  noticeStore.state.notices = newsList.value;
 };
 
 // 前往通知中心点击

+ 1 - 1
src/main.ts

@@ -34,7 +34,7 @@ import i18n from '@/lang/index';
 // vxeTable
 import VXETable from 'vxe-table';
 import 'vxe-table/lib/style.css';
-VXETable.config({
+VXETable.setConfig({
   zIndex: 999999
 });
 

+ 2 - 2
src/permission.ts

@@ -14,8 +14,8 @@ NProgress.configure({ showSpinner: false });
 const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
 
 const isWhiteList = (path: string) => {
-  return whiteList.some(pattern => isPathMatch(pattern, path))
-}
+  return whiteList.some((pattern) => isPathMatch(pattern, path));
+};
 
 router.beforeEach(async (to, from, next) => {
   NProgress.start();

+ 1 - 98
src/router/index.ts

@@ -93,104 +93,7 @@ export const constantRoutes: RouteRecordRaw[] = [
 
 // 动态路由,基于用户权限动态去加载
 export const dynamicRoutes: RouteRecordRaw[] = [
-  {
-    path: '/system/user-auth',
-    component: Layout,
-    hidden: true,
-    permissions: ['system:user:edit'],
-    children: [
-      {
-        path: 'role/:userId(\\d+)',
-        component: () => import('@/views/system/user/authRole.vue'),
-        name: 'AuthRole',
-        meta: { title: '分配角色', activeMenu: '/system/user', icon: '', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/system/role-auth',
-    component: Layout,
-    hidden: true,
-    permissions: ['system:role:edit'],
-    children: [
-      {
-        path: 'user/:roleId(\\d+)',
-        component: () => import('@/views/system/role/authUser.vue'),
-        name: 'AuthUser',
-        meta: { title: '分配用户', activeMenu: '/system/role', icon: '', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/system/dict-data',
-    component: Layout,
-    hidden: true,
-    permissions: ['system:dict:list'],
-    children: [
-      {
-        path: 'index/:dictId(\\d+)',
-        component: () => import('@/views/system/dict/data.vue'),
-        name: 'Data',
-        meta: { title: '字典数据', activeMenu: '/system/dict', icon: '', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/system/oss-config',
-    component: Layout,
-    hidden: true,
-    permissions: ['system:ossConfig:list'],
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/system/oss/config.vue'),
-        name: 'OssConfig',
-        meta: { title: '配置管理', activeMenu: '/system/oss', icon: '', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/tool/gen-edit',
-    component: Layout,
-    hidden: true,
-    permissions: ['tool:gen:edit'],
-    children: [
-      {
-        path: 'index/:tableId(\\d+)',
-        component: () => import('@/views/tool/gen/editTable.vue'),
-        name: 'GenEdit',
-        meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/workflow/leaveEdit',
-    component: Layout,
-    hidden: true,
-    permissions: ['workflow:leave:edit'],
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/workflow/leave/leaveEdit.vue'),
-        name: 'leaveEdit',
-        meta: { title: '请假申请', activeMenu: '/workflow/leave', noCache: true }
-      }
-    ]
-  },
-  {
-    path: '/workflow/design',
-    component: Layout,
-    hidden: true,
-    permissions: ['workflow:leave:edit'],
-    children: [
-      {
-        path: 'index',
-        component: () => import('@/views/workflow/processDefinition/design.vue'),
-        name: 'design',
-        meta: { title: '流程设计', activeMenu: '/workflow/processDefinition', noCache: true }
-      }
-    ]
-  }
+
 ];
 
 /**

+ 5 - 8
src/settings.ts

@@ -27,6 +27,11 @@ const setting: DefaultSettings = {
    */
   tagsView: true,
 
+  /**
+   * 显示页签图标
+   */
+  tagsIcon: false,
+
   /**
    * 是否固定头部
    */
@@ -42,14 +47,6 @@ const setting: DefaultSettings = {
    */
   dynamicTitle: false,
 
-  /**
-   * @type {string | array} 'production' | ['production', 'development']
-   * @description Need show err logs component.
-   * The default is only used in the production env
-   * If you want to also use it in dev, you can pass ['production', 'development']
-   */
-  errorLog: 'production',
-
   /**
    * 是否开启动画 开启随机 关闭渐进渐出
    */

+ 1 - 1
src/store/index.ts

@@ -1,4 +1,4 @@
-import { createPinia } from "pinia";
+import { createPinia } from 'pinia';
 
 const store = createPinia();
 

+ 2 - 2
src/store/modules/permission.ts

@@ -99,14 +99,14 @@ export const usePermissionStore = defineStore('permission', () => {
   };
   const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
     let children: RouteRecordRaw[] = [];
-    childrenMap.forEach(el => {
+    childrenMap.forEach((el) => {
       el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
       if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
         children = children.concat(filterChildren(el.children, el));
       } else {
         children.push(el);
       }
-    })
+    });
     return children;
   };
   return {

+ 3 - 1
src/store/modules/settings.ts

@@ -5,10 +5,10 @@ import { useStorage } from '@vueuse/core';
 import { ref } from 'vue';
 
 export const useSettingsStore = defineStore('setting', () => {
-  // @ts-ignore
   const storageSetting = useStorage<LayoutSetting>('layout-setting', {
     topNav: defaultSettings.topNav,
     tagsView: defaultSettings.tagsView,
+    tagsIcon: defaultSettings.tagsIcon,
     fixedHeader: defaultSettings.fixedHeader,
     sidebarLogo: defaultSettings.sidebarLogo,
     dynamicTitle: defaultSettings.dynamicTitle,
@@ -21,6 +21,7 @@ export const useSettingsStore = defineStore('setting', () => {
   const showSettings = ref<boolean>(defaultSettings.showSettings);
   const topNav = ref<boolean>(storageSetting.value.topNav);
   const tagsView = ref<boolean>(storageSetting.value.tagsView);
+  const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
   const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
   const sidebarLogo = ref<boolean>(storageSetting.value.sidebarLogo);
   const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
@@ -38,6 +39,7 @@ export const useSettingsStore = defineStore('setting', () => {
     showSettings,
     topNav,
     tagsView,
+    tagsIcon,
     fixedHeader,
     sidebarLogo,
     dynamicTitle,

+ 4 - 2
src/types/global.d.ts

@@ -98,6 +98,10 @@ declare global {
      * 是否显示多标签导航
      */
     tagsView: boolean;
+    /**
+     * 显示页签图标
+     */
+    tagsIcon: boolean;
     /**
      * 是否固定头部
      */
@@ -157,8 +161,6 @@ declare global {
      * false: 明亮模式
      */
     dark: boolean;
-
-    errorLog: string;
   }
 }
 export {};

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

@@ -48,4 +48,3 @@ export type ObjKeysToUnion<T, P extends string = ''> = T extends object
       [K in keyof T]: ObjKeysToUnion<T[K], P extends '' ? `${K & string}` : `${P}.${K & string}`>;
     }[keyof T]
   : P;
-

+ 2 - 1
src/utils/request.ts

@@ -191,7 +191,8 @@ export function download(url: string, params: any, fileName: string) {
         const blob = new Blob([resp]);
         FileSaver.saveAs(blob, fileName);
       } else {
-        const resText = await resp.data.text();
+        const blob = new Blob([resp]);
+        const resText = await blob.text();
         const rspObj = JSON.parse(resText);
         const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
         ElMessage.error(errMsg);

+ 2 - 2
src/utils/ruoyi.ts

@@ -178,11 +178,11 @@ export const handleTree = <T>(data: any[], id?: string, parentId?: string, child
 
   for (const d of data) {
     const parentId = d[config.parentId];
-    const parentObj = childrenListMap[parentId]
+    const parentObj = childrenListMap[parentId];
     if (!parentObj) {
       tree.push(d);
     } else {
-      parentObj[config.childrenList].push(d)
+      parentObj[config.childrenList].push(d);
     }
   }
   return tree;

+ 1 - 1
src/views/demo/demo/index.vue

@@ -49,7 +49,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="true" label="主键" align="center" prop="id" />
         <el-table-column label="部门id" align="center" prop="deptId" />

+ 1 - 0
src/views/demo/tree/index.vue

@@ -33,6 +33,7 @@
         v-loading="loading"
         :data="treeList"
         row-key="id"
+        border
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
       >

+ 1 - 1
src/views/error/401.vue

@@ -22,7 +22,7 @@
 <script setup lang="ts">
 import errImage from '@/assets/401_images/401.gif';
 
-let { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const errGif = ref(errImage + '?' + +new Date());
 

+ 1 - 1
src/views/error/404.vue

@@ -22,7 +22,7 @@
 </template>
 
 <script setup lang="ts">
-let message = computed(() => {
+const message = computed(() => {
   return '找不到网页!';
 });
 </script>

+ 3 - 3
src/views/index.vue

@@ -26,14 +26,14 @@
           * 文件存储 七牛、阿里、腾讯 云存储<br />
           * 监控框架 SpringBoot-Admin 全方位服务监控<br />
           * 校验框架 Validation 增强接口安全性 严谨性<br />
-          * Excel框架 Alibaba EasyExcel 性能优异 扩展性强<br />
+          * Excel框架 FastExcel(原Alibaba EasyExcel) 性能优异 扩展性强<br />
           * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
           * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
           * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
           * 部署方式 Docker 容器编排 一键部署业务集群<br />
           * 国际化 SpringMessage Spring标准国际化方案<br />
         </p>
-        <p><b>当前版本:</b> <span>v5.3.1</span></p>
+        <p><b>当前版本:</b> <span>v5.4.0</span></p>
         <p>
           <el-tag type="danger">&yen;免费开源</el-tag>
         </p>
@@ -77,7 +77,7 @@
           * 分布式监控 Prometheus、Grafana 全方位性能监控<br />
           * 其余与 Vue 版本一致<br />
         </p>
-        <p><b>当前版本:</b> <span>v2.3.0</span></p>
+        <p><b>当前版本:</b> <span>v2.4.0</span></p>
         <p>
           <el-tag type="danger">&yen;免费开源</el-tag>
         </p>

+ 1 - 2
src/views/login.vue

@@ -260,10 +260,9 @@ onMounted(() => {
   background: #ffffff;
   width: 400px;
   padding: 25px 25px 5px 25px;
-
+  z-index: 1;
   .el-input {
     height: 40px;
-
     input {
       height: 40px;
     }

+ 1 - 0
src/views/monitor/logininfor/index.vue

@@ -63,6 +63,7 @@
         v-loading="loading"
         :data="loginInfoList"
         :default-sort="defaultSort"
+        border
         @selection-change="handleSelectionChange"
         @sort-change="handleSortChange"
       >

+ 1 - 0
src/views/monitor/online/index.vue

@@ -19,6 +19,7 @@
     <el-card shadow="hover">
       <el-table
         v-loading="loading"
+        border
         :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
         style="width: 100%"
       >

+ 1 - 0
src/views/monitor/operlog/index.vue

@@ -65,6 +65,7 @@
         ref="operLogTableRef"
         v-loading="loading"
         :data="operlogList"
+        border
         :default-sort="defaultSort"
         @selection-change="handleSelectionChange"
         @sort-change="handleSortChange"

+ 0 - 1
src/views/register.vue

@@ -203,7 +203,6 @@ onMounted(() => {
     line-height: 0;
     color: #7483a3;
   }
-
 }
 
 .register-form {

+ 2 - 2
src/views/system/client/index.vue

@@ -45,7 +45,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="clientList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" :data="clientList" border @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="true" label="id" align="center" prop="id" />
         <el-table-column label="客户端id" align="center" prop="clientId" />
@@ -300,7 +300,7 @@ const handleExport = () => {
 
 /** 状态修改  */
 const handleStatusChange = async (row: ClientVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '"吗?');
     await changeStatus(row.clientId, row.status);

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

@@ -60,7 +60,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="configList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
         <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />

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

@@ -42,6 +42,7 @@
         v-loading="loading"
         :data="deptList"
         row-key="deptId"
+        border
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
         :default-expand-all="isExpandAll"
       >

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

@@ -44,7 +44,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="dataList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
         <el-table-column label="字典标签" align="center" prop="dictLabel">

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

@@ -53,7 +53,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="typeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
         <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />

+ 74 - 7
src/views/system/menu/index.vue

@@ -30,6 +30,9 @@
           <el-col :span="1.5">
             <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
           </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
+          </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
@@ -39,6 +42,7 @@
         v-loading="loading"
         :data="menuList"
         row-key="menuId"
+        border
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
         :default-expand-all="isExpandAll"
       >
@@ -129,8 +133,8 @@
                 </span>
               </template>
               <el-radio-group v-model="form.isFrame">
-                <el-radio label="0">是</el-radio>
-                <el-radio label="1">否</el-radio>
+                <el-radio value="0">是</el-radio>
+                <el-radio value="1">否</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -207,8 +211,8 @@
                 </span>
               </template>
               <el-radio-group v-model="form.isCache">
-                <el-radio label="0">缓存</el-radio>
-                <el-radio label="1">不缓存</el-radio>
+                <el-radio value="0">缓存</el-radio>
+                <el-radio value="1">不缓存</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -225,7 +229,7 @@
                 </span>
               </template>
               <el-radio-group v-model="form.visible">
-                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :label="dict.value">{{ dict.label }} </el-radio>
+                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -242,7 +246,7 @@
                 </span>
               </template>
               <el-radio-group v-model="form.status">
-                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
+                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
                   {{ dict.label }}
                 </el-radio>
               </el-radio-group>
@@ -257,11 +261,31 @@
         </div>
       </template>
     </el-dialog>
+
+    <el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="750px">
+      <el-tree
+        ref="menuTreeRef"
+        class="tree-border"
+        :data="menuOptions"
+        show-checkbox
+        node-key="menuId"
+        :check-strictly="false"
+        empty-text="加载中,请稍候"
+        :default-expanded-keys="[0]"
+        :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
+      />
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading">确 定</el-button>
+          <el-button @click="cancelCascade">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup name="Menu" lang="ts">
-import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
+import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
 import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
 import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
 
@@ -404,7 +428,50 @@ const handleDelete = async (row: MenuVO) => {
   proxy?.$modal.msgSuccess('删除成功');
 };
 
+const deleteLoading = ref<boolean>(false);
+const menuTreeRef = ref<ElTreeInstance>();
+
+const deleteDialog = reactive<DialogOption>({
+  visible: false,
+  title: '级联删除菜单'
+});
+
+/** 级联删除按钮操作 */
+const handleCascadeDelete = () => {
+  menuTreeRef.value?.setCheckedKeys([]);
+  getTreeselect();
+  deleteDialog.visible = true;
+};
+
+/** 取消按钮 */
+const cancelCascade = () => {
+  menuTreeRef.value?.setCheckedKeys([]);
+  deleteDialog.visible = false;
+};
+
+/** 删除提交按钮 */
+const submitDeleteForm = async () => {
+  const menuIds = menuTreeRef.value?.getCheckedKeys();
+  if (menuIds.length < 0) {
+    proxy?.$modal.msgWarning('请选择要删除的菜单');
+    return;
+  }
+
+  deleteLoading.value = true;
+  await cascadeDelMenu(menuIds).finally(() => (deleteLoading.value = false));
+  await getList();
+  proxy?.$modal.msgSuccess('删除成功');
+  deleteDialog.visible = false;
+};
+
 onMounted(() => {
   getList();
 });
 </script>
+
+<style scoped lang="scss">
+.tree-border {
+  height: 300px;
+  overflow: auto;
+}
+</style>

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

@@ -44,7 +44,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="noticeList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
         <el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />

+ 14 - 4
src/views/system/oss/config.vue

@@ -45,7 +45,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="ossConfigList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
         <el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
@@ -87,10 +87,18 @@
           <el-input v-model="form.configKey" placeholder="请输入配置key" />
         </el-form-item>
         <el-form-item label="访问站点" prop="endpoint">
-          <el-input v-model="form.endpoint" placeholder="请输入访问站点" />
+          <el-input v-model="form.endpoint" placeholder="请输入访问站点">
+            <template #prefix>
+              <span style="color: #999">{{ protocol }}</span>
+            </template>
+          </el-input>
         </el-form-item>
         <el-form-item label="自定义域名" prop="domain">
-          <el-input v-model="form.domain" placeholder="请输入自定义域名" />
+          <el-input v-model="form.domain" placeholder="请输入自定义域名">
+            <template #prefix>
+              <span style="color: #999">{{ protocol }}</span>
+            </template>
+          </el-input>
         </el-form-item>
         <el-form-item label="accessKey" prop="accessKey">
           <el-input v-model="form.accessKey" placeholder="请输入accessKey" />
@@ -239,6 +247,8 @@ const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
 
 const { queryParams, form, rules } = toRefs(data);
 
+const protocol = computed(() => (form.value.isHttps === 'Y' ? 'https://' : 'http://'));
+
 /** 查询对象存储配置列表 */
 const getList = async () => {
   loading.value = true;
@@ -306,7 +316,7 @@ const submitForm = () => {
 };
 /** 状态修改  */
 const handleStatusChange = async (row: OssConfigVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
     await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);

+ 5 - 4
src/views/system/oss/index.vue

@@ -70,6 +70,7 @@
         v-if="showTable"
         v-loading="loading"
         :data="ossList"
+        border
         :header-cell-class-name="handleHeaderClass"
         @selection-change="handleSelectionChange"
         @header-click="handleHeaderCLick"
@@ -255,9 +256,9 @@ const handleHeaderCLick = (column: any) => {
   handleOrderChange(column.property, column.multiOrder);
 };
 const handleOrderChange = (prop: string, order: string) => {
-  let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
-  let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
-  let propIndex = orderByArr.indexOf(prop);
+  const orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
+  const isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
+  const propIndex = orderByArr.indexOf(prop);
   if (propIndex !== -1) {
     if (order) {
       //排序里已存在 只修改排序
@@ -306,7 +307,7 @@ const handleDownload = (row: OssVO) => {
 };
 /** 预览开关按钮  */
 const handlePreviewListResource = async (preview: boolean) => {
-  let text = preview ? '启用' : '停用';
+  const text = preview ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
     await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);

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

@@ -84,7 +84,7 @@
               <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
             </el-row>
           </template>
-          <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
+          <el-table v-loading="loading" border :data="postList" @selection-change="handleSelectionChange">
             <el-table-column type="selection" width="55" align="center" />
             <el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
             <el-table-column label="岗位编码" align="center" prop="postCode" />

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

@@ -33,7 +33,7 @@
           <right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar>
         </el-row>
       </template>
-      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
         <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />

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

@@ -55,7 +55,7 @@
         </el-row>
       </template>
 
-      <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
+      <el-table ref="roleTableRef" border v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
@@ -323,7 +323,7 @@ const handleSelectionChange = (selection: RoleVO[]) => {
 
 /** 角色状态修改 */
 const handleStatusChange = async (row: RoleVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
     await changeRoleStatus(row.roleId, row.status);
@@ -346,11 +346,11 @@ const getMenuTreeselect = async () => {
 /** 所有部门节点数据 */
 const getDeptAllCheckedKeys = (): any => {
   // 目前被选中的部门节点
-  let checkedKeys = deptRef.value?.getCheckedKeys();
+  const checkedKeys = deptRef.value?.getCheckedKeys();
   // 半选中的部门节点
-  let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
+  const halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
   if (halfCheckedKeys) {
-    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
+    checkedKeys?.unshift(...halfCheckedKeys);
   }
   return checkedKeys;
 };
@@ -404,14 +404,14 @@ const getRoleDeptTreeSelect = async (roleId: string | number) => {
 /** 树权限(展开/折叠)*/
 const handleCheckedTreeExpand = (value: boolean, type: string) => {
   if (type == 'menu') {
-    let treeList = menuOptions.value;
+    const treeList = menuOptions.value;
     for (let i = 0; i < treeList.length; i++) {
       if (menuRef.value) {
         menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
       }
     }
   } else if (type == 'dept') {
-    let treeList = deptOptions.value;
+    const treeList = deptOptions.value;
     for (let i = 0; i < treeList.length; i++) {
       if (deptRef.value) {
         deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
@@ -438,11 +438,11 @@ const handleCheckedTreeConnect = (value: any, type: string) => {
 /** 所有菜单节点数据 */
 const getMenuAllCheckedKeys = (): any => {
   // 目前被选中的菜单节点
-  let checkedKeys = menuRef.value?.getCheckedKeys();
+  const checkedKeys = menuRef.value?.getCheckedKeys();
   // 半选中的菜单节点
-  let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
+  const halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
   if (halfCheckedKeys) {
-    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
+    checkedKeys?.unshift(...halfCheckedKeys);
   }
   return checkedKeys;
 };

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

@@ -14,7 +14,7 @@
         </el-form-item>
       </el-form>
       <el-row>
-        <el-table ref="tableRef" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
+        <el-table ref="tableRef" border :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
           <el-table-column type="selection" width="55"></el-table-column>
           <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
           <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />

+ 3 - 3
src/views/system/tenant/index.vue

@@ -51,7 +51,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="tenantList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="id" align="center" prop="id" />
         <el-table-column label="租户编号" align="center" prop="tenantId" />
@@ -245,7 +245,7 @@ const getList = async () => {
 
 // 租户套餐状态修改
 const handleStatusChange = async (row: TenantVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
     await changeTenantStatus(row.id, row.tenantId, row.status);
@@ -361,7 +361,7 @@ const handleExport = () => {
 /**同步租户字典*/
 const handleSyncTenantDict = async () => {
   await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
-  let res = await syncTenantDict();
+  const res = await syncTenantDict();
   proxy?.$modal.msgSuccess(res.msg);
 };
 

+ 6 - 6
src/views/system/tenantPackage/index.vue

@@ -39,7 +39,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="tenantPackageList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="tenantPackageList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column v-if="false" label="租户套餐id" align="center" prop="packageId" />
         <el-table-column label="套餐名称" align="center" prop="packageName" />
@@ -167,11 +167,11 @@ const getMenuTreeselect = async () => {
 // 所有菜单节点数据
 const getMenuAllCheckedKeys = (): any => {
   // 目前被选中的菜单节点
-  let checkedKeys = menuTreeRef.value?.getCheckedKeys();
+  const checkedKeys = menuTreeRef.value?.getCheckedKeys();
   // 半选中的菜单节点
-  let halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
+  const halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
   if (halfCheckedKeys) {
-    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
+    checkedKeys?.unshift(...halfCheckedKeys);
   }
   return checkedKeys;
 };
@@ -194,7 +194,7 @@ const getList = async () => {
 
 // 租户套餐状态修改
 const handleStatusChange = async (row: TenantPkgVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   const [err] = await to(proxy?.$modal.confirm('确认要"' + text + '""' + row.packageName + '"套餐吗?') as Promise<any>);
   if (err) {
     row.status = row.status === '0' ? '1' : '0';
@@ -241,7 +241,7 @@ const handleSelectionChange = (selection: TenantPkgVO[]) => {
 // 树权限(展开/折叠)
 const handleCheckedTreeExpand = (value: CheckboxValueType, type: string) => {
   if (type == 'menu') {
-    let treeList = menuOptions.value;
+    const treeList = menuOptions.value;
     for (let i = 0; i < treeList.length; i++) {
       if (menuTreeRef.value) {
         menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value as boolean;

+ 10 - 3
src/views/system/user/authRole.vue

@@ -23,6 +23,7 @@
         <el-table
           ref="tableRef"
           v-loading="loading"
+          border
           :row-key="getRowKey"
           :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
           @row-click="clickRow"
@@ -33,7 +34,7 @@
               <span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
             </template>
           </el-table-column>
-          <el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
+          <el-table-column type="selection" :reserve-selection="true" :selectable="checkSelectable" width="55"></el-table-column>
           <el-table-column label="角色编号" align="center" prop="roleId" />
           <el-table-column label="角色名称" align="center" prop="roleName" />
           <el-table-column label="权限字符" align="center" prop="roleKey" />
@@ -80,8 +81,10 @@ const tableRef = ref<ElTableInstance>();
 
 /** 单击选中行数据 */
 const clickRow = (row: RoleVO) => {
-  row.flag = !row.flag;
-  tableRef.value?.toggleRowSelection(row, row.flag);
+  if (checkSelectable(row)) {
+    row.flag = !row.flag;
+    tableRef.value?.toggleRowSelection(row, row.flag);
+  }
 };
 /** 多选框选中数据 */
 const handleSelectionChange = (selection: RoleVO[]) => {
@@ -91,6 +94,10 @@ const handleSelectionChange = (selection: RoleVO[]) => {
 const getRowKey = (row: RoleVO): string => {
   return String(row.roleId);
 };
+/** 检查角色状态 */
+const checkSelectable = (row: RoleVO): boolean => {
+  return row.status === '0';
+};
 /** 关闭按钮 */
 const close = () => {
   const obj: RouteLocationNormalized = {

+ 5 - 2
src/views/system/user/index.vue

@@ -27,6 +27,9 @@
                 <el-form-item label="用户名称" prop="userName">
                   <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
                 </el-form-item>
+                <el-form-item label="用户昵称" prop="nickName">
+                  <el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" />
+                </el-form-item>
                 <el-form-item label="手机号码" prop="phonenumber">
                   <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
                 </el-form-item>
@@ -92,7 +95,7 @@
             </el-row>
           </template>
 
-          <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+          <el-table v-loading="loading" border :data="userList" @selection-change="handleSelectionChange">
             <el-table-column type="selection" width="50" align="center" />
             <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
             <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
@@ -498,7 +501,7 @@ const handleDelete = async (row?: UserVO) => {
 
 /** 用户状态修改  */
 const handleStatusChange = async (row: UserVO) => {
-  let text = row.status === '0' ? '启用' : '停用';
+  const text = row.status === '0' ? '启用' : '停用';
   try {
     await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
     await api.changeUserStatus(row.userId, row.status);

+ 1 - 1
src/views/system/user/profile/onlineDevice.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <el-table :data="devices" style="width: 100%; height: 100%; font-size: 14px">
+    <el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px">
       <el-table-column label="设备类型" align="center">
         <template #default="scope">
           <dict-tag :options="sys_device_type" :value="scope.row.deviceType" />

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

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <el-table :data="auths" style="width: 100%; height: 100%; font-size: 14px">
+    <el-table :data="auths" border style="width: 100%; height: 100%; font-size: 14px">
       <el-table-column label="序号" width="50" type="index" />
       <el-table-column label="绑定账号平台" width="140" align="center" prop="source" show-overflow-tooltip />
       <el-table-column label="头像" width="120" align="center" prop="avatar">

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

@@ -134,7 +134,7 @@ const beforeUpload = (file: UploadRawFile): any => {
 /** 上传图片 */
 const uploadImg = async () => {
   cropper.value.getCropBlob(async (data: any) => {
-    let formData = new FormData();
+    const formData = new FormData();
     formData.append('avatarfile', data, options.fileName);
     const res = await uploadAvatar(formData);
     open.value = false;

+ 1 - 1
src/views/tool/gen/editTable.vue

@@ -5,7 +5,7 @@
         <basic-info-form ref="basicInfo" :info="info" />
       </el-tab-pane>
       <el-tab-pane label="字段信息" name="columnInfo">
-        <el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
+        <el-table ref="dragTable" border :data="columns" row-key="columnId" :max-height="tableHeight">
           <el-table-column label="序号" type="index" min-width="5%" />
           <el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" />
           <el-table-column label="字段描述" min-width="10%">

+ 1 - 1
src/views/tool/gen/importTable.vue

@@ -19,7 +19,7 @@
       </el-form-item>
     </el-form>
     <el-row>
-      <el-table ref="tableRef" :data="dbTableList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
+      <el-table ref="tableRef" border :data="dbTableList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55"></el-table-column>
         <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
         <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>

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

@@ -56,7 +56,7 @@
         </el-row>
       </template>
 
-      <el-table v-loading="loading" :data="tableList" @selection-change="handleSelectionChange">
+      <el-table v-loading="loading" border :data="tableList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" align="center" width="55"></el-table-column>
         <el-table-column label="序号" type="index" width="50" align="center">
           <template #default="scope">
@@ -113,8 +113,8 @@
 </template>
 
 <script setup name="Gen" lang="ts">
-import {delTable, genCode, getDataNames, listTable, previewTable, synchDb} from '@/api/tool/gen';
-import {TableQuery, TableVO} from '@/api/tool/gen/types';
+import { delTable, genCode, getDataNames, listTable, previewTable, synchDb } from '@/api/tool/gen';
+import { TableQuery, TableVO } from '@/api/tool/gen/types';
 import router from '@/router';
 import ImportTable from './importTable.vue';
 

+ 40 - 43
src/views/workflow/category/index.vue

@@ -31,10 +31,11 @@
         v-loading="loading"
         :data="categoryList"
         row-key="categoryId"
+        border
         :default-expand-all="isExpandAll"
         :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
       >
-        <el-table-column label="分类名称" prop="categoryName" width="260"/>
+        <el-table-column label="分类名称" prop="categoryName" width="260" />
         <el-table-column label="显示顺序" align="center" prop="orderNum" width="200" />
         <el-table-column label="创建时间" align="center" prop="createTime" width="180" />
         <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
@@ -77,7 +78,7 @@
           </el-col>
         </el-row>
       </el-form>
-    <template #footer>
+      <template #footer>
         <div class="dialog-footer">
           <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
           <el-button @click="cancel">取 消</el-button>
@@ -88,17 +89,16 @@
 </template>
 
 <script setup name="Category" lang="ts">
-import { listCategory, getCategory, delCategory, addCategory, updateCategory } from "@/api/workflow/category";
+import { listCategory, getCategory, delCategory, addCategory, updateCategory } from '@/api/workflow/category';
 import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/workflow/category/types';
 
 type CategoryOption = {
   categoryId: number;
   categoryName: string;
   children?: CategoryOption[];
-}
-
-const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
+};
 
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const categoryList = ref<CategoryVO[]>([]);
 const categoryOptions = ref<CategoryOption[]>([]);
@@ -109,32 +109,29 @@ const loading = ref(false);
 
 const queryFormRef = ref<ElFormInstance>();
 const categoryFormRef = ref<ElFormInstance>();
-const categoryTableRef = ref<ElTableInstance>()
+const categoryTableRef = ref<ElTableInstance>();
 
 const dialog = reactive<DialogOption>({
   visible: false,
   title: ''
 });
 
-
 const initFormData: CategoryForm = {
   categoryId: undefined,
-  categoryName: "",
+  categoryName: '',
   parentId: undefined,
-  orderNum: 0,
-}
+  orderNum: 0
+};
 
 const data = reactive<PageData<CategoryForm, CategoryQuery>>({
-  form: {...initFormData},
+  form: { ...initFormData },
   queryParams: {
-    categoryName: undefined,
+    categoryName: undefined
   },
   rules: {
-    categoryId: [
-      { required: true, message: "流程分类ID不能为空", trigger: "blur" }
-    ],
-    parentId: [{ required: true, message: "请选择上级分类", trigger: "change" }],
-    categoryName: [{ required: true, message: "请输入分类名称", trigger: "blur" }]
+    categoryId: [{ required: true, message: '流程分类ID不能为空', trigger: 'blur' }],
+    parentId: [{ required: true, message: '请选择上级分类', trigger: 'change' }],
+    categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
   }
 });
 
@@ -144,19 +141,19 @@ const { queryParams, form, rules } = toRefs(data);
 const getList = async () => {
   loading.value = true;
   const res = await listCategory(queryParams.value);
-  const data = proxy?.handleTree<CategoryVO>(res.data, "categoryId", "parentId");
+  const data = proxy?.handleTree<CategoryVO>(res.data, 'categoryId', 'parentId');
   if (data) {
     categoryList.value = data;
     loading.value = false;
   }
-}
+};
 
 /** 查询流程分类下拉树结构 */
 const getTreeselect = async () => {
   const res = await listCategory();
   categoryOptions.value = [];
   // 处理树形数据
-  const data = proxy?.handleTree<CategoryOption>(res.data, "categoryId", "parentId");
+  const data = proxy?.handleTree<CategoryOption>(res.data, 'categoryId', 'parentId');
   if (data) {
     categoryOptions.value = data; // 将处理后的树形数据赋值
   }
@@ -166,24 +163,24 @@ const getTreeselect = async () => {
 const cancel = () => {
   reset();
   dialog.visible = false;
-}
+};
 
 // 表单重置
 const reset = () => {
-  form.value = {...initFormData}
+  form.value = { ...initFormData };
   categoryFormRef.value?.resetFields();
-}
+};
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
   getList();
-}
+};
 
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value?.resetFields();
   handleQuery();
-}
+};
 
 /** 新增按钮操作 */
 const handleAdd = (row?: CategoryVO) => {
@@ -195,22 +192,22 @@ const handleAdd = (row?: CategoryVO) => {
     form.value.parentId = undefined;
   }
   dialog.visible = true;
-  dialog.title = "添加流程分类";
-}
+  dialog.title = '添加流程分类';
+};
 
 /** 展开/折叠操作 */
 const handleToggleExpandAll = () => {
   isExpandAll.value = !isExpandAll.value;
-  toggleExpandAll(categoryList.value, isExpandAll.value)
-}
+  toggleExpandAll(categoryList.value, isExpandAll.value);
+};
 
 /** 展开/折叠操作 */
 const toggleExpandAll = (data: CategoryVO[], status: boolean) => {
   data.forEach((item) => {
-    categoryTableRef.value?.toggleRowExpansion(item, status)
-    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
-  })
-}
+    categoryTableRef.value?.toggleRowExpansion(item, status);
+    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
+  });
+};
 
 /** 修改按钮操作 */
 const handleUpdate = async (row: CategoryVO) => {
@@ -222,8 +219,8 @@ const handleUpdate = async (row: CategoryVO) => {
   const res = await getCategory(row.categoryId);
   Object.assign(form.value, res.data);
   dialog.visible = true;
-  dialog.title = "修改流程分类";
-}
+  dialog.title = '修改流程分类';
+};
 
 /** 提交按钮 */
 const submitForm = () => {
@@ -231,25 +228,25 @@ const submitForm = () => {
     if (valid) {
       buttonLoading.value = true;
       if (form.value.categoryId) {
-        await updateCategory(form.value).finally(() => buttonLoading.value = false);
+        await updateCategory(form.value).finally(() => (buttonLoading.value = false));
       } else {
-        await addCategory(form.value).finally(() => buttonLoading.value = false);
+        await addCategory(form.value).finally(() => (buttonLoading.value = false));
       }
-      proxy?.$modal.msgSuccess("操作成功");
+      proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
       getList();
     }
   });
-}
+};
 
 /** 删除按钮操作 */
 const handleDelete = async (row: CategoryVO) => {
   await proxy?.$modal.confirm('是否确认删除"' + row.categoryName + '"的分类?');
   loading.value = true;
-  await delCategory(row.categoryId).finally(() => loading.value = false);
+  await delCategory(row.categoryId).finally(() => (loading.value = false));
   await getList();
-  proxy?.$modal.msgSuccess("删除成功");
-}
+  proxy?.$modal.msgSuccess('删除成功');
+};
 
 onMounted(() => {
   getList();

+ 1 - 1
src/views/workflow/leave/index.vue

@@ -222,7 +222,7 @@ const handleExport = () => {
 const handleCancelProcessApply = async (id: string) => {
   await proxy?.$modal.confirm('是否确认撤销当前单据?');
   loading.value = true;
-  let data = {
+  const data = {
     businessId: id,
     message: '申请人撤销流程!'
   };

+ 17 - 34
src/views/workflow/leave/leaveEdit.vue

@@ -1,17 +1,15 @@
 <template>
   <div class="p-2">
     <el-card shadow="never">
-      <div style="display: flex; justify-content: space-between">
-        <div>
-          <el-button v-if="submitButtonShow" :loading="buttonLoading" type="info" @click="submitForm('draft')">暂存</el-button>
-          <el-button v-if="submitButtonShow" :loading="buttonLoading" type="primary" @click="submitForm('submit')">提 交</el-button>
-          <el-button v-if="approvalButtonShow" :loading="buttonLoading" type="primary" @click="approvalVerifyOpen">审批</el-button>
-          <el-button v-if="form && form.id && form.status !== 'draft'" type="primary" @click="handleApprovalRecord">流程进度</el-button>
-        </div>
-        <div>
-          <el-button style="float: right" @click="goBack()">返回</el-button>
-        </div>
-      </div>
+      <approvalButton
+        @submitForm="submitForm"
+        @approvalVerifyOpen="approvalVerifyOpen"
+        @handleApprovalRecord="handleApprovalRecord"
+        :buttonLoading="buttonLoading"
+        :id="form.id"
+        :status="form.status"
+        :pageType="routeParams.type"
+      />
     </el-card>
     <el-card shadow="never" style="height: 78vh; overflow-y: auto">
       <el-form ref="leaveFormRef" v-loading="loading" :disabled="routeParams.type === 'view'" :model="form" :rules="rules" label-width="80px">
@@ -20,7 +18,7 @@
             <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
-        <el-form-item label="请假时间">
+        <el-form-item label="请假时间" required>
           <el-date-picker
             v-model="leaveTime"
             value-format="YYYY-MM-DD HH:mm:ss"
@@ -64,6 +62,7 @@ import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
 import { startWorkFlow } from '@/api/workflow/task';
 import SubmitVerify from '@/components/Process/submitVerify.vue';
 import ApprovalRecord from '@/components/Process/approvalRecord.vue';
+import ApprovalButton from '@/components/Process/approvalButton.vue';
 import { AxiosResponse } from 'axios';
 import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -128,6 +127,8 @@ const dialogVisible = reactive<DialogOption>({
 const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
 //审批记录组件
 const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
+//按钮组件
+const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
 
 const leaveFormRef = ref<ElFormInstance>();
 
@@ -212,9 +213,9 @@ const submitForm = (status: string) => {
         buttonLoading.value = true;
         let res: AxiosResponse<LeaveVO>;
         if (form.value.id) {
-          res = await updateLeave(form.value);
+          res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
         } else {
-          res = await addLeave(form.value);
+          res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
         }
         form.value = res.data;
         if (status === 'draft') {
@@ -252,7 +253,9 @@ const handleStartWorkFlow = async (data: LeaveForm) => {
     submitFormData.value.businessId = data.id;
     //流程变量
     taskVariables.value = {
+      // leave2/6 使用的流程变量
       leaveDays: data.leaveDays,
+      // leave4/5 使用的流程变量
       userList: ['1', '3', '4']
     };
     submitFormData.value.variables = taskVariables.value;
@@ -274,30 +277,10 @@ const submitCallback = async () => {
   await proxy.$tab.closePage(proxy.$route);
   proxy.$router.go(-1);
 };
-
-//返回
-const goBack = () => {
-  proxy.$tab.closePage(proxy.$route);
-  proxy.$router.go(-1);
-};
 //审批
 const approvalVerifyOpen = async () => {
   submitVerifyRef.value.openDialog(routeParams.value.taskId);
 };
-//校验提交按钮是否显示
-const submitButtonShow = computed(() => {
-  return (
-    routeParams.value.type === 'add' ||
-    (routeParams.value.type === 'update' &&
-      form.value.status &&
-      (form.value.status === 'draft' || form.value.status === 'cancel' || form.value.status === 'back'))
-  );
-});
-
-//校验审批按钮是否显示
-const approvalButtonShow = computed(() => {
-  return routeParams.value.type === 'approval' && form.value.status && form.value.status === 'waiting';
-});
 
 onMounted(() => {
   nextTick(async () => {

+ 3 - 3
src/views/workflow/processDefinition/design.vue

@@ -4,7 +4,7 @@
   </div>
 </template>
 
-<script setup name="WarmFlow">
+<script setup name="WarmFlow" lang="ts">
 const { proxy } = getCurrentInstance();
 import { onMounted } from 'vue';
 import { getToken } from '@/utils/auth';
@@ -24,12 +24,12 @@ const iframeLoaded = () => {
   };
 };
 const open = async (definitionId, disabled) => {
-  let url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&disabled=${disabled}`;
+  const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&disabled=${disabled}`;
   iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
 };
 /** 关闭按钮 */
 function close() {
-  const obj = { path: '/workflow/processDefinition', query: {activeName: proxy.$route.query.activeName}};
+  const obj = { path: '/workflow/processDefinition', query: { activeName: proxy.$route.query.activeName } };
   proxy.$tab.closeOpenPage(obj);
 }
 

+ 6 - 3
src/views/workflow/processDefinition/index.vue

@@ -197,7 +197,7 @@ import { categoryTree } from '@/api/workflow/category';
 import { CategoryTreeVO } from '@/api/workflow/category/types';
 import { FlowDefinitionQuery, FlowDefinitionVo, FlowDefinitionForm } from '@/api/workflow/definition/types';
 import { UploadRequestOptions, TabsPaneContext } from 'element-plus';
-import { ElMessageBoxOptions } from "element-plus/es/components/message-box/src/message-box.type";
+import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -326,7 +326,7 @@ const handleSelectionChange = (selection: any) => {
 };
 //分页
 const getPageList = async () => {
-  let query = proxy.$route.query;
+  const query = proxy.$route.query;
   if (query.activeName) {
     activeName.value = query.activeName;
   }
@@ -411,7 +411,7 @@ const handlerBeforeUpload = () => {
 };
 //部署文件
 const handlerImportDefinition = (data: UploadRequestOptions): XMLHttpRequest => {
-  let formData = new FormData();
+  const formData = new FormData();
   uploadDialogLoading.value = true;
   formData.append('file', data.file);
   formData.append('category', selectCategory.value);
@@ -466,6 +466,9 @@ const reset = () => {
  */
 const handleAdd = async () => {
   reset();
+  if (queryParams.value.category != '') {
+    form.value.category = queryParams.value.category;
+  }
   modelDialog.visible = true;
   modelDialog.title = '新增流程';
 };

+ 2 - 2
src/views/workflow/processInstance/index.vue

@@ -352,7 +352,7 @@ const handleInvalid = async (row: FlowInstanceVO) => {
   await proxy?.$modal.confirm('是否确认作废?');
   loading.value = true;
   if ('running' === tab.value) {
-    let param = {
+    const param = {
       id: row.id,
       comment: deleteReason.value
     };
@@ -381,7 +381,7 @@ const handleInstanceVariable = async (row: FlowInstanceVO) => {
   variableLoading.value = true;
   variableVisible.value = true;
   processDefinitionName.value = row.flowName;
-  let data = await instanceVariable(row.id);
+  const data = await instanceVariable(row.id);
   variables.value = data.data.variable;
   variableLoading.value = false;
 };

+ 2 - 1
src/views/workflow/task/allTaskWaiting.vue

@@ -227,7 +227,8 @@ const handleView = (row) => {
     taskId: row.id,
     type: 'view',
     formCustom: row.formCustom,
-    formPath: row.formPath
+    formPath: row.formPath,
+    instanceId: row.instanceId
   });
   workflowCommon.routerJump(routerJumpVo, proxy);
 };

+ 1 - 1
src/views/workflow/task/myDocument.vue

@@ -222,7 +222,7 @@ const handleCancelProcessApply = async (businessId: string) => {
   await proxy?.$modal.confirm('是否确认撤销当前单据?');
   loading.value = true;
   if ('running' === tab.value) {
-    let data = {
+    const data = {
       businessId: businessId,
       message: '申请人撤销流程!'
     };