Explorar o código

!241 发布 5.5.0-2.5.0 喜迎国庆
Merge pull request !241 from 疯狂的狮子Li/dev

疯狂的狮子Li hai 6 meses
pai
achega
1b46739799
Modificáronse 42 ficheiros con 1111 adicións e 177 borrados
  1. 1 1
      package.json
  2. 0 8
      src/api/system/dept/index.ts
  3. 11 0
      src/api/system/post/index.ts
  4. 1 1
      src/api/system/user/index.ts
  5. 1 1
      src/api/system/user/types.ts
  6. 3 0
      src/api/workflow/definition/types.ts
  7. 24 0
      src/api/workflow/instance/index.ts
  8. 2 0
      src/api/workflow/instance/types.ts
  9. 12 0
      src/api/workflow/leave/index.ts
  10. 2 0
      src/api/workflow/leave/types.ts
  11. 63 0
      src/api/workflow/spel/index.ts
  12. 111 0
      src/api/workflow/spel/types.ts
  13. 15 2
      src/api/workflow/task/index.ts
  14. 9 5
      src/api/workflow/task/types.ts
  15. 1 0
      src/api/workflow/workflowCommon/types.ts
  16. 77 15
      src/assets/styles/sidebar.scss
  17. 3 3
      src/assets/styles/transition.scss
  18. 6 1
      src/assets/styles/variables.module.scss
  19. 100 0
      src/components/Process/MessageType.vue
  20. 6 5
      src/components/Process/approvalButton.vue
  21. 29 17
      src/components/Process/submitVerify.vue
  22. 4 1
      src/components/UserSelect/index.vue
  23. 1 0
      src/layout/components/Sidebar/index.vue
  24. 7 2
      src/layout/components/TagsView/index.vue
  25. 1 1
      src/main.ts
  26. 3 3
      src/utils/sse.ts
  27. 2 2
      src/views/index.vue
  28. 1 1
      src/views/system/dept/index.vue
  29. 15 3
      src/views/system/menu/index.vue
  30. 6 5
      src/views/system/post/index.vue
  31. 20 9
      src/views/system/user/index.vue
  32. 1 1
      src/views/system/user/profile/userInfo.vue
  33. 10 1
      src/views/tool/gen/index.vue
  34. 39 48
      src/views/workflow/leave/leaveEdit.vue
  35. 1 1
      src/views/workflow/processDefinition/design.vue
  36. 41 3
      src/views/workflow/processDefinition/index.vue
  37. 76 19
      src/views/workflow/processInstance/index.vue
  38. 360 0
      src/views/workflow/spel/index.vue
  39. 33 11
      src/views/workflow/task/allTaskWaiting.vue
  40. 2 0
      src/views/workflow/task/taskCopyList.vue
  41. 9 7
      src/views/workflow/task/taskFinish.vue
  42. 2 0
      src/views/workflow/task/taskWaiting.vue

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package",
   "name": "ruoyi-vue-plus",
-  "version": "5.4.1-2.4.1",
+  "version": "5.5.0-2.5.0",
   "description": "RuoYi-Vue-Plus多租户管理系统",
   "author": "LionLi",
   "license": "MIT",

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

@@ -38,14 +38,6 @@ export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
   });
 };
 
-// 查询部门下拉树结构
-export const treeselect = (): AxiosPromise<DeptTreeVO[]> => {
-  return request({
-    url: '/system/dept/treeselect',
-    method: 'get'
-  });
-};
-
 // 新增部门
 export const addDept = (data: DeptForm) => {
   return request({

+ 11 - 0
src/api/system/post/index.ts

@@ -1,6 +1,7 @@
 import request from '@/utils/request';
 import { PostForm, PostQuery, PostVO } from './types';
 import { AxiosPromise } from 'axios';
+import { DeptTreeVO } from '../dept/types';
 
 // 查询岗位列表
 export function listPost(query: PostQuery): AxiosPromise<PostVO[]> {
@@ -56,3 +57,13 @@ export function delPost(postId: string | number | (string | number)[]) {
     method: 'delete'
   });
 }
+
+/**
+ * 查询部门下拉树结构
+ */
+export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
+  return request({
+    url: '/system/post/deptTree',
+    method: 'get'
+  });
+};

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

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

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

@@ -20,7 +20,7 @@ export interface UserQuery extends PageQuery {
   status?: string;
   deptId?: string | number;
   roleId?: string | number;
-  userIds?: string;
+  userIds?:  string | number | (string | number)[] | undefined;
 }
 
 /**

+ 3 - 0
src/api/workflow/definition/types.ts

@@ -22,7 +22,10 @@ export interface FlowDefinitionForm {
   flowName: string;
   flowCode: string;
   category: string;
+  ext: string;
   formPath: string;
+  formCustom: string;
+  modelValue: string;
 }
 
 export interface definitionXmlVO {

+ 24 - 0
src/api/workflow/instance/index.ts

@@ -87,6 +87,18 @@ export const deleteByInstanceIds = (instanceIds: Array<string | number> | string
     method: 'delete'
   });
 };
+
+/**
+ * 删除历史流程实例
+ * @param instanceIds
+ */
+export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
+  return request({
+    url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
+    method: 'delete'
+  });
+};
+
 /**
  * 作废流程
  * @param data 参数
@@ -99,3 +111,15 @@ export const invalid = (data: any) => {
     data: data
   });
 };
+/**
+ * 修改流程变量
+ * @param data 参数
+ * @returns
+ */
+export const updateVariable = (data: any) => {
+  return request({
+    url: `/workflow/instance/updateVariable`,
+    method: 'put',
+    data: data
+  });
+};

+ 2 - 0
src/api/workflow/instance/types.ts

@@ -23,4 +23,6 @@ export interface FlowInstanceVO extends BaseEntity {
   flowStatus: string;
   flowStatusName: string;
   flowTaskList: FlowTaskVO[];
+  businessCode: string;
+  businessTitle: string;
 }

+ 12 - 0
src/api/workflow/leave/index.ts

@@ -39,6 +39,18 @@ export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
   });
 };
 
+/**
+ * 提交请假并发起流程
+ * @param data
+ */
+export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
+  return request({
+    url: '/workflow/leave/submitAndFlowStart',
+    method: 'post',
+    data: data
+  });
+};
+
 /**
  * 修改请假
  * @param data

+ 2 - 0
src/api/workflow/leave/types.ts

@@ -1,5 +1,6 @@
 export interface LeaveVO {
   id: string | number;
+  applyCode?: string;
   leaveType: string;
   startDate: string;
   endDate: string;
@@ -10,6 +11,7 @@ export interface LeaveVO {
 
 export interface LeaveForm extends BaseEntity {
   id?: string | number;
+  applyCode?: string;
   leaveType?: string;
   startDate?: string;
   endDate?: string;

+ 63 - 0
src/api/workflow/spel/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { SpelVO, SpelForm, SpelQuery } from '@/api/workflow/spel/types';
+
+/**
+ * 查询流程spel表达式定义列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listSpel = (query?: SpelQuery): AxiosPromise<SpelVO[]> => {
+  return request({
+    url: '/workflow/spel/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询流程spel表达式定义详细
+ * @param id
+ */
+export const getSpel = (id: string | number): AxiosPromise<SpelVO> => {
+  return request({
+    url: '/workflow/spel/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增流程spel表达式定义
+ * @param data
+ */
+export const addSpel = (data: SpelForm) => {
+  return request({
+    url: '/workflow/spel',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改流程spel表达式定义
+ * @param data
+ */
+export const updateSpel = (data: SpelForm) => {
+  return request({
+    url: '/workflow/spel',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除流程spel表达式定义
+ * @param id
+ */
+export const delSpel = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/workflow/spel/' + id,
+    method: 'delete'
+  });
+};

+ 111 - 0
src/api/workflow/spel/types.ts

@@ -0,0 +1,111 @@
+export interface SpelVO {
+  /**
+   * 主键id
+   */
+  id: string | number;
+
+  /**
+   * 组件名称
+   */
+  componentName: string;
+
+  /**
+   * 方法名
+   */
+  methodName: string;
+
+  /**
+   * 参数
+   */
+  methodParams: string;
+
+  /**
+   * 预览spel值
+   */
+  viewSpel: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SpelForm extends BaseEntity {
+  /**
+   * 主键id
+   */
+  id?: string | number;
+
+  /**
+   * 组件名称
+   */
+  componentName?: string;
+
+  /**
+   * 方法名
+   */
+  methodName?: string;
+
+  /**
+   * 参数
+   */
+  methodParams?: string;
+
+  /**
+   * 预览spel值
+   */
+  viewSpel?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+}
+
+export interface SpelQuery extends PageQuery {
+
+  /**
+   * 组件名称
+   */
+  componentName?: string;
+
+  /**
+   * 方法名
+   */
+  methodName?: string;
+
+  /**
+   * 参数
+   */
+  methodParams?: string;
+
+  /**
+   * 预览spel值
+   */
+  viewSpel?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 15 - 2
src/api/workflow/task/index.ts

@@ -148,9 +148,9 @@ export const terminationTask = (data: any) => {
  * 获取可驳回得任务节点
  * @returns
  */
-export const getBackTaskNode = (definitionId: string, nodeCode: string) => {
+export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
   return request({
-    url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
+    url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
     method: 'get'
   });
 };
@@ -191,3 +191,16 @@ export const getNextNodeList = (data: any): any => {
     data: data
   });
 };
+
+/**
+ * 催办任务
+ * @param data参数
+ * @returns
+ */
+export const urgeTask = (data: any): any => {
+  return request({
+    url: '/workflow/task/urgeTask',
+    method: 'post',
+    data: data
+  });
+};

+ 9 - 5
src/api/workflow/task/types.ts

@@ -30,16 +30,20 @@ export interface FlowTaskVO {
   nodeRatio: string | number;
   version?: string;
   applyNode?: boolean;
-  buttonList?: buttonList[];
+  buttonList?: ButtonList[];
+  copyList?: FlowCopyVo[];
+  varList?: Map<string, string>;
+  businessCode: string;
+  businessTitle: string;
 }
 
-export interface buttonList {
+export interface ButtonList {
   code: string;
   show: boolean;
 }
-export interface VariableVo {
-  key: string;
-  value: string;
+export interface FlowCopyVo {
+  userId: string | number;
+  userName: string;
 }
 
 export interface TaskOperationBo {

+ 1 - 0
src/api/workflow/workflowCommon/types.ts

@@ -10,4 +10,5 @@ export interface StartProcessBo {
   businessId: string | number;
   flowCode: string;
   variables: any;
+  flowInstanceBizExtBo: any;
 }

+ 77 - 15
src/assets/styles/sidebar.scss

@@ -65,7 +65,7 @@
     }
 
     .svg-icon {
-      margin-right: 16px;
+      margin-right: 10px;
     }
 
     .el-menu {
@@ -88,12 +88,16 @@
     // menu hover
     .theme-dark .sub-menu-title-noDropdown,
     .theme-dark .el-sub-menu__title {
+      border-radius: 8px;
+      margin: 1px 5px 1px 5px;
       &:hover {
         background-color: $base-sub-menu-title-hover !important;
       }
     }
     .sub-menu-title-noDropdown,
     .el-sub-menu__title {
+      border-radius: 8px;
+      margin: 1px 5px 1px 5px;
       &:hover {
         background-color: rgba(0, 0, 0, 0.05) !important;
       }
@@ -105,8 +109,11 @@
 
     & .nest-menu .el-sub-menu > .el-sub-menu__title,
     & .el-sub-menu .el-menu-item {
-      min-width: $base-sidebar-width !important;
-      &:hover {
+      min-width: calc($base-sidebar-width - 20px) !important;
+      border-radius: 8px;
+      height: 45px;
+      margin: 1px 5px 1px 5px;
+      &:not(.is-active):hover {
         background-color: rgba(0, 0, 0, 0.1) !important;
       }
     }
@@ -114,28 +121,54 @@
     & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
     & .theme-dark .el-sub-menu .el-menu-item {
       background-color: $base-sub-menu-background !important;
+      border-radius: 8px;
+      height: 45px;
+      margin: 1px 5px 1px 5px;
 
-      &:hover {
+      &.is-active {
+        background-color: var(--el-menu-active-color) !important;
+        color: #fff;
+      }
+      &:not(.is-active):hover {
+        // you can use $sub-menuHover
         background-color: $base-sub-menu-hover !important;
       }
     }
 
     & .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
     & .theme-dark .el-menu-item {
-      &:hover {
+      border-radius: 8px;
+      height: 45px;
+      margin: 1px 5px 1px 5px;
+
+      &.is-active {
+        background-color: var(--el-menu-active-color) !important;
+        color: #fff;
+      }
+      &:not(.is-active):hover {
         // you can use $sub-menuHover
         background-color: $base-menu-hover !important;
       }
     }
+
     & .nest-menu .el-sub-menu > .el-sub-menu__title,
     & .el-menu-item {
-      &:hover {
+      border-radius: 8px;
+      height: 45px;
+      margin: 1px 5px 1px 5px;
+
+      &.is-active {
+        background-color: var(--el-menu-active-color) !important;
+        color: #fff;
+      }
+      &:not(.is-active):hover {
         // you can use $sub-menuHover
         background-color: rgba(0, 0, 0, 0.04) !important;
       }
     }
   }
 
+  // 收起菜单后的样式
   .hideSidebar {
     .sidebar-container {
       width: 54px !important;
@@ -148,29 +181,48 @@
     .sub-menu-title-noDropdown {
       padding: 0 !important;
       position: relative;
+      height: 45px;
+      // 选中状态的菜单
+      &.is-active {
+        background-color: var(--el-menu-active-color) !important;
+        color: #fff !important;
+      }
 
       .el-tooltip {
         padding: 0 !important;
-
-        .svg-icon {
-          margin-left: 20px;
-        }
       }
     }
 
-    .el-sub-menu {
+
+    & .el-sub-menu {
       overflow: hidden;
+      border-radius: 8px;
+      .el-sub-menu__title.el-tooltip__trigger {
+        border-radius: 8px;
+        height: 45px;
+      }
+
+      // 选中状态的菜单
+      &.is-active .el-sub-menu__title.el-tooltip__trigger {
+        background-color: var(--el-menu-active-color) !important;
+      }
+
 
       & > .el-sub-menu__title {
         padding: 0 !important;
-
-        .svg-icon {
-          margin-left: 20px;
-        }
       }
     }
 
     .el-menu--collapse {
+      .is-active .svg-icon {
+        fill: #fff;
+      }
+      .svg-icon {
+        display: flex;
+        margin: auto;
+        height: 100%;
+        // 这里设置width会跟随sidebar-container的transition 不符合预期
+      }
       .el-sub-menu {
         & > .el-sub-menu__title {
           & > span {
@@ -232,3 +284,13 @@
     }
   }
 }
+// 收起菜单后悬浮的菜单样式
+.el-popper.is-pure{
+  border-radius: 8px;
+  .el-menu--popup{
+    border-radius: 8px;
+  }
+  .el-menu-item{
+    border-radius: 4px;
+  }
+}

+ 3 - 3
src/assets/styles/transition.scss

@@ -6,7 +6,7 @@
   transition: opacity 0.28s;
 }
 
-.fade-enter,
+.fade-enter-from,
 .fade-leave-active {
   opacity: 0;
 }
@@ -18,7 +18,7 @@
   transition: all 0.5s;
 }
 
-.fade-transform-enter {
+.fade-transform-enter-from {
   opacity: 0;
   transform: translateX(-30px);
 }
@@ -34,7 +34,7 @@
   transition: all 0.5s;
 }
 
-.breadcrumb-enter,
+.breadcrumb-enter-from,
 .breadcrumb-leave-active {
   opacity: 0;
   transform: translateX(20px);

+ 6 - 1
src/assets/styles/variables.module.scss

@@ -1,6 +1,6 @@
 // 全局SCSS变量
 :root {
-  --menuBg: #304156;
+  --menuBg: #1f2d3d;
   --menuColor: #bfcbd9;
   --menuActiveText: #f4f4f5;
   --menuHover: #263445;
@@ -10,6 +10,11 @@
   --subMenuHover: #001528;
   --subMenuTitleHover: #293444;
 
+  // 菜单栏缩进
+  --el-menu-base-level-padding: 12px;
+  --el-menu-level-padding: 8px;
+  --el-menu-item-height: 50px;
+
   --fixedHeaderBg: #ffffff;
   --tableHeaderBg: #f8f8f9;
   --tableHeaderTextColor: #515a6e;

+ 100 - 0
src/components/Process/MessageType.vue

@@ -0,0 +1,100 @@
+<template>
+  <el-dialog v-model="visible" :title="props.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false">
+    <el-form v-loading="loading" ref="ruleFormRef" :model="form" :rules="rules" label-width="120px">
+      <el-form-item label="消息提醒" prop="messageType">
+        <el-checkbox-group v-model="form.messageType">
+          <el-checkbox value="1" name="type" disabled>站内信</el-checkbox>
+          <el-checkbox value="2" name="type">邮件</el-checkbox>
+          <el-checkbox value="3" name="type">短信</el-checkbox>
+        </el-checkbox-group>
+      </el-form-item>
+      <el-form-item label="消息内容" prop="message">
+        <el-input v-model="form.message" type="textarea" resize="none" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer" style="float: right; padding-bottom: 20px">
+        <el-button :disabled="buttonDisabled" type="primary" @click="submit(ruleFormRef)">确认</el-button>
+        <el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import { ref } from 'vue';
+import { ComponentInternalInstance } from 'vue';
+import { ElForm, FormInstance } from 'element-plus';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const emits = defineEmits(['submitCallback', 'cancelCallback']);
+const props = defineProps({
+  title: {
+    type: String,
+    default: '提示'
+  }
+});
+const ruleFormRef = ref<FormInstance>();
+//遮罩层
+const loading = ref(true);
+const visible = ref(false);
+const buttonDisabled = ref(true);
+const form = ref<Record<string, any>>({
+  message: undefined,
+  messageType: ['1']
+});
+const rules = reactive<Record<string, any>>({
+  messageType: [
+    {
+      required: true,
+      message: '请选择消息提醒',
+      trigger: 'change'
+    }
+  ],
+  message: [
+    {
+      required: true,
+      message: '请输入消息内容',
+      trigger: 'blur'
+    }
+  ]
+});
+//确认
+//打开弹窗
+const open = async () => {
+  reset();
+  visible.value = true;
+  loading.value = false;
+  buttonDisabled.value = false;
+};
+//关闭弹窗
+const close = async () => {
+  reset();
+  visible.value = false;
+};
+const submit = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      emits('submitCallback', form.value);
+    }
+  });
+};
+//取消
+const cancel = async () => {
+  visible.value = false;
+  buttonDisabled.value = false;
+  emits('cancelCallback');
+};
+//重置
+const reset = async () => {
+  form.value.taskIdList = [];
+  form.value.message = '';
+  form.value.messageType = ['1'];
+};
+/**
+ * 对外暴露子组件方法
+ */
+defineExpose({
+  open,
+  close
+});
+</script>

+ 6 - 5
src/components/Process/approvalButton.vue

@@ -1,8 +1,8 @@
 <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="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft', mode)">暂存</el-button>
+      <el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit', mode)">提 交</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 />
@@ -19,12 +19,13 @@ const props = defineProps({
   status: propTypes.string.def(''),
   pageType: propTypes.string.def(''),
   buttonLoading: propTypes.bool.def(false),
-  id: propTypes.string.def('') || propTypes.number.def()
+  id: propTypes.string.def('') || propTypes.number.def(),
+  mode: propTypes.bool.def(false)
 });
 const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
 //暂存,提交
-const submitForm = async (type) => {
-  emits('submitForm', type);
+const submitForm = async (type, mode) => {
+  emits('submitForm', type, mode);
 };
 //审批
 const approvalVerifyOpen = async () => {

+ 29 - 17
src/components/Process/submitVerify.vue

@@ -14,7 +14,7 @@
       <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 }}
+          {{ user.userName }}
         </el-tag>
       </el-form-item>
       <el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap">
@@ -80,7 +80,7 @@
     <!-- 加签组件 -->
     <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
     <!-- 弹窗选人 -->
-    <UserSelect ref="porUserRef" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
+    <UserSelect ref="porUserRef" :data="form.assigneeMap[nodeCode]" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
 
     <!-- 驳回开始 -->
     <el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
@@ -149,8 +149,7 @@ import {
 import UserSelect from '@/components/UserSelect';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-import { UserVO } from '@/api/system/user/types';
-import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
+import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
 
 const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
 const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
@@ -171,9 +170,11 @@ const buttonDisabled = ref(true);
 //任务id
 const taskId = ref<string>('');
 //抄送人
-const selectCopyUserList = ref<UserVO[]>([]);
+const selectCopyUserList = ref<FlowCopyVo[]>([]);
 //抄送人id
 const selectCopyUserIds = ref<string>(undefined);
+//自定义节点变量
+const varNodeList = ref<Map<string, string>>(undefined);
 //可减签的人员
 const deleteUserList = ref<any>([]);
 //弹窗可选择的人员id
@@ -217,7 +218,11 @@ const task = ref<FlowTaskVO>({
   nodeType: undefined,
   nodeRatio: undefined,
   applyNode: false,
-  buttonList: []
+  buttonList: [],
+  copyList: [],
+  varList: undefined,
+  businessCode: undefined,
+  businessTitle: undefined
 });
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -257,14 +262,21 @@ const openDialog = async (id?: string) => {
   task.value.buttonList.forEach((e) => {
     buttonObj.value[e.code] = e.show;
   });
+  selectCopyUserList.value = task.value.copyList;
+  selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
+  varNodeList.value = task.value.varList;
+  console.log('varNodeList', varNodeList.value)
   buttonDisabled.value = false;
-  const data = {
-    taskId: taskId.value,
-    variables: props.taskVariables
-  };
-  const nextData = await getNextNodeList(data);
-  nestNodeList.value = nextData.data;
-  loading.value = false;
+  try {
+    const data = {
+      taskId: taskId.value,
+      variables: props.taskVariables
+    };
+    const nextData = await getNextNodeList(data);
+    nestNodeList.value = nextData.data;
+  } finally {
+    loading.value = false;
+  }
 };
 
 onMounted(() => {});
@@ -298,7 +310,7 @@ const handleCompleteTask = async () => {
     selectCopyUserList.value.forEach((e) => {
       const copyUser = {
         userId: e.userId,
-        userName: e.nickName
+        userName: e.userName
       };
       flowCopyList.push(copyUser);
     });
@@ -325,7 +337,7 @@ const handleBackProcessOpen = async () => {
   backVisible.value = true;
   backLoading.value = true;
   backButtonDisabled.value = true;
-  const data = await getBackTaskNode(task.value.definitionId, task.value.nodeCode);
+  const data = await getBackTaskNode(task.value.id, task.value.nodeCode);
   taskNodeList.value = data.data;
   backLoading.value = false;
   backButtonDisabled.value = false;
@@ -361,14 +373,14 @@ const openUserSelectCopy = () => {
   userSelectCopyRef.value.open();
 };
 //确认抄送人员
-const userSelectCopyCallBack = (data: UserVO[]) => {
+const userSelectCopyCallBack = (data: FlowCopyVo[]) => {
   if (data && data.length > 0) {
     selectCopyUserList.value = data;
     selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
   }
 };
 //删除抄送人员
-const handleCopyCloseTag = (user: UserVO) => {
+const handleCopyCloseTag = (user: FlowCopyVo) => {
   const userId = user.userId;
   // 使用split删除用户
   const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);

+ 4 - 1
src/components/UserSelect/index.vue

@@ -168,12 +168,15 @@ const confirm = () => {
 };
 
 const computedIds = (data) => {
+  if (data === '' || data === null || data === undefined) {
+    return [];
+  }
   if (data instanceof Array) {
     return data.map((item) => String(item));
   } else if (typeof data === 'string') {
     return data.split(',');
   } else if (typeof data === 'number') {
-    return [data];
+    return [String(data)];
   } else {
     console.warn('<UserSelect> The data type of data should be array or string or number, but I received other');
     return [];

+ 1 - 0
src/layout/components/Sidebar/index.vue

@@ -11,6 +11,7 @@
           :unique-opened="true"
           :active-text-color="theme"
           :collapse-transition="false"
+          :popper-offset="12"
           mode="vertical"
         >
           <sidebar-item v-for="(r, index) in sidebarRouters" :key="r.path + index" :item="r" :base-path="r.path" />

+ 7 - 2
src/layout/components/TagsView/index.vue

@@ -13,7 +13,7 @@
         @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 class="tags-view-item-title">{{ tag.title }}</span>
         <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
           <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
         </span>
@@ -253,7 +253,7 @@ onMounted(() => {
       position: relative;
       cursor: pointer;
       height: 26px;
-      line-height: 23px;
+      line-height: 25px;
       background-color: var(--el-bg-color);
       border: 1px solid var(--el-border-color-light);
       color: #495060;
@@ -261,6 +261,7 @@ onMounted(() => {
       font-size: 12px;
       margin-left: 5px;
       margin-top: 4px;
+      border-radius: 4px;
       &:hover {
         color: var(--el-color-primary);
       }
@@ -290,6 +291,10 @@ onMounted(() => {
   .tags-view-item.active.has-icon::before {
     content: none !important;
   }
+  .tags-view-item-title {
+    margin-left: 4px;
+    margin-right: 3px;
+  }
   .contextmenu {
     margin: 0;
     background: var(--el-bg-color);

+ 1 - 1
src/main.ts

@@ -1,8 +1,8 @@
 import { createApp } from 'vue';
 // global css
 import 'virtual:uno.css';
-import '@/assets/styles/index.scss';
 import 'element-plus/theme-chalk/dark/css-vars.css';
+import '@/assets/styles/index.scss';
 
 // App、router、store
 import App from './App.vue';

+ 3 - 3
src/utils/sse.ts

@@ -11,10 +11,10 @@ export const initSSE = (url: any) => {
   url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
   const { data, error } = useEventSource(url, [], {
     autoReconnect: {
-      retries: 10,
-      delay: 3000,
+      retries: 5,
+      delay: 5000,
       onFailed() {
-        console.log('Failed to connect after 10 retries');
+        console.log('Failed to connect after 5 retries');
       }
     }
   });

+ 2 - 2
src/views/index.vue

@@ -33,7 +33,7 @@
           * 部署方式 Docker 容器编排 一键部署业务集群<br />
           * 国际化 SpringMessage Spring标准国际化方案<br />
         </p>
-        <p><b>当前版本:</b> <span>v5.4.1</span></p>
+        <p><b>当前版本:</b> <span>v5.5.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.4.1</span></p>
+        <p><b>当前版本:</b> <span>v2.5.0</span></p>
         <p>
           <el-tag type="danger">&yen;免费开源</el-tag>
         </p>

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

@@ -197,7 +197,7 @@ const initData: PageData<DeptForm, DeptQuery> = {
     deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
     orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
     email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
-    phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
+    phone: [{ pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
   }
 };
 const data = reactive<PageData<DeptForm, DeptQuery>>(initData);

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

@@ -25,10 +25,10 @@
       <template #header>
         <el-row :gutter="10">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
+            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
+            <el-button v-hasPermi="['system:menu:remove']" 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>
@@ -44,6 +44,7 @@
         :default-expand-all="false"
         lazy
         :load="getChildrenList"
+        :expand-change="expandMenuHandle"
       >
         <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
         <el-table-column prop="icon" label="图标" align="center" width="100">
@@ -353,6 +354,13 @@ const getChildrenList = async (row: any, treeNode: unknown, resolve: (data: any[
   resolve(children);
 };
 
+/** 收起菜单时从menuExpandMap中删除对应菜单id数据 */
+const expandMenuHandle = async (row: any, expanded: boolean) => {
+  if (!expanded) {
+    menuExpandMap.value[row.menuId] = undefined;
+  }
+};
+
 /** 刷新展开的菜单数据 */
 const refreshLoadTree = (parentId: string | number) => {
   if (menuExpandMap.value[parentId]) {
@@ -388,12 +396,16 @@ const getList = async () => {
     }
     tempMap[parentId].push(menu);
   }
+  // 创建一个当前所有 menuId 的 Set,用于查找父菜单是否存在于当前数据中
+  const menuIdSet = new Set();
   // 设置有没有子菜单
   for (const menu of res.data) {
     menu['hasChildren'] = tempMap[menu.menuId]?.length > 0;
+    menuIdSet.add(menu.menuId);
   }
   menuChildrenListMap.value = tempMap;
-  menuList.value = tempMap[0] || [];
+  // 找出所有父ID不在当前菜单ID集合中的菜单项,作为新的顶层菜单
+  menuList.value = res.data.filter((menu) => !menuIdSet.has(menu.parentId));
   // 根据新数据重新加载子菜单数据
   refreshAllExpandMenuData();
   loading.value = false;

+ 6 - 5
src/views/system/post/index.vue

@@ -170,10 +170,9 @@
 </template>
 
 <script setup name="Post" lang="ts">
-import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
+import { listPost, addPost, delPost, getPost, updatePost, deptTreeSelect } from '@/api/system/post';
 import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
-import { DeptVO } from '@/api/system/dept/types';
-import api from '@/api/system/user';
+import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
@@ -186,7 +185,7 @@ const single = ref(true);
 const multiple = ref(true);
 const total = ref(0);
 const deptName = ref('');
-const deptOptions = ref<DeptVO[]>([]);
+const deptOptions = ref<DeptTreeVO[]>([]);
 const deptTreeRef = ref<ElTreeInstance>();
 const postFormRef = ref<ElFormInstance>();
 const queryFormRef = ref<ElFormInstance>();
@@ -212,6 +211,8 @@ const data = reactive<PageData<PostForm, PostQuery>>({
   queryParams: {
     pageNum: 1,
     pageSize: 10,
+    deptId: undefined,
+    belongDeptId: undefined,
     postCode: '',
     postName: '',
     postCategory: '',
@@ -245,7 +246,7 @@ watchEffect(
 
 /** 查询部门下拉树结构 */
 const getTreeSelect = async () => {
-  const res = await api.deptTreeSelect();
+  const res = await deptTreeSelect();
   deptOptions.value = res.data;
 };
 

+ 20 - 9
src/views/system/user/index.vue

@@ -154,7 +154,7 @@
               <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
             <el-form-item label="归属部门" prop="deptId">
               <el-tree-select
                 v-model="form.deptId"
@@ -209,7 +209,7 @@
           </el-col>
         </el-row>
         <el-row>
-          <el-col :span="12">
+          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
             <el-form-item label="岗位">
               <el-select v-model="form.postIds" multiple placeholder="请选择">
                 <el-option
@@ -222,7 +222,7 @@
               </el-select>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
             <el-form-item label="角色" prop="roleIds">
               <el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
                 <el-option
@@ -293,13 +293,12 @@ import api from '@/api/system/user';
 import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
 import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
 import { RoleVO } from '@/api/system/role/types';
-import { PostQuery, PostVO } from '@/api/system/post/types';
-import { treeselect } from '@/api/system/dept';
+import { PostVO } from '@/api/system/post/types';
 import { globalHeaders } from '@/utils/request';
 import { to } from 'await-to-js';
 import { optionselect } from '@/api/system/post';
-import { hasPermi } from '@/directive/permission';
 import { checkPermi } from '@/utils/permission';
+import { useUserStore } from '@/store/modules/user';
 
 const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -411,7 +410,7 @@ const initData: PageData<UserForm, UserQuery> = {
     ],
     phonenumber: [
       {
-        pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
+        pattern: /^1[3456789][0-9]\d{8}$/,
         message: '请输入正确的手机号码',
         trigger: 'blur'
       }
@@ -616,7 +615,9 @@ const handleUpdate = async (row?: UserForm) => {
   dialog.title = '修改用户';
   Object.assign(form.value, data.user);
   postOptions.value = data.posts;
-  roleOptions.value = data.roles;
+  roleOptions.value = Array.from(
+    new Map([...data.roles, ...data.user.roles].map(role => [role.roleId, role])).values()
+  );
   form.value.postIds = data.postIds;
   form.value.roleIds = data.roleIds;
   form.value.password = '';
@@ -626,7 +627,17 @@ const handleUpdate = async (row?: UserForm) => {
 const submitForm = () => {
   userFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
+      if (form.value.userId) {
+        // 自己编辑自己的情况下 不允许编辑角色部门岗位
+        if (form.value.userId == useUserStore().userId) {
+          form.value.roleIds = null;
+          form.value.deptId = null;
+          form.value.postIds = null;
+        }
+        await api.updateUser(form.value);
+      } else {
+        await api.addUser(form.value);
+      }
       proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
       await getList();

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

@@ -48,7 +48,7 @@ const rule: ElFormRules = {
       message: '手机号码不能为空',
       trigger: 'blur'
     },
-    { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+    { pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
   ]
 };
 const rules = ref<ElFormRules>(rule);

+ 10 - 1
src/views/tool/gen/index.vue

@@ -104,7 +104,7 @@
           <el-link v-copyText="value" v-copyText:callback="copyTextSuccess" :underline="false" icon="DocumentCopy" style="float: right">
             &nbsp;复制
           </el-link>
-          <pre>{{ value }}</pre>
+          <highlightjs :code="value" />
         </el-tab-pane>
       </el-tabs>
     </el-dialog>
@@ -248,3 +248,12 @@ onMounted(() => {
   getDataNameList();
 });
 </script>
+
+<style lang="scss" scoped>
+.el-tab-pane {
+  background-color: #282c34;
+  .el-link {
+    color: #fff;
+  }
+}
+</style>

+ 39 - 48
src/views/workflow/leave/leaveEdit.vue

@@ -1,6 +1,8 @@
 <template>
   <div class="p-2">
     <el-card shadow="never">
+      <!-- mode用于直接后端发起流程 不同接口实现方式可查看具体后端代码 -->
+      <!-- 默认前端发起 前端发起更多样性 比如可以选审批人 选抄送人 上传附件等等 后端发起需要用户自行编写代码传这些参数 -->
       <approvalButton
         @submitForm="submitForm"
         @approvalVerifyOpen="approvalVerifyOpen"
@@ -9,10 +11,16 @@
         :id="form.id"
         :status="form.status"
         :pageType="routeParams.type"
+        :mode="false"
       />
     </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">
+        <el-form-item label="流程定义" v-if="routeParams.type === 'add'">
+          <el-select v-model="flowCode" placeholder="选择流程定义" style="width: 100%">
+            <el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </el-form-item>
         <el-form-item label="请假类型" prop="leaveType">
           <el-select v-model="form.leaveType" placeholder="请选择请假类型" style="width: 100%">
             <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
@@ -42,22 +50,11 @@
     <submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
     <!-- 审批记录 -->
     <approvalRecord ref="approvalRecordRef" />
-    <el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" :before-close="handleClose" width="500">
-      <el-select v-model="flowCode" placeholder="Select" style="width: 240px">
-        <el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
-      </el-select>
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="handleClose">取消</el-button>
-          <el-button type="primary" @click="submitFlow()"> 确认 </el-button>
-        </div>
-      </template>
-    </el-dialog>
   </div>
 </template>
 
 <script setup name="Leave" lang="ts">
-import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
+import { addLeave, getLeave, submitAndFlowStart, updateLeave } from '@/api/workflow/leave';
 import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
 import { startWorkFlow } from '@/api/workflow/task';
 import SubmitVerify from '@/components/Process/submitVerify.vue';
@@ -116,28 +113,24 @@ const flowCodeOptions = [
     label: '请假申请-排他并行会签'
   }
 ];
+// 自定义流程可不选择 直接填写flowCode 例如 'leave1'
+const flowCode = ref<string>('leave1');
 
-const flowCode = ref<string>('');
-
-const dialogVisible = reactive<DialogOption>({
-  visible: false,
-  title: '流程定义'
-});
 //提交组件
 const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
 //审批记录组件
 const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
-//按钮组件
-const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
 
 const leaveFormRef = ref<ElFormInstance>();
 
 const submitFormData = ref<StartProcessBo>({
   businessId: '',
   flowCode: '',
-  variables: {}
+  variables: {},
+  flowInstanceBizExtBo: {}
 });
 const taskVariables = ref<Record<string, any>>({});
+const flowInstanceBizExtBo = ref<Record<string, any>>({});
 
 const initFormData: LeaveForm = {
   id: undefined,
@@ -164,11 +157,6 @@ const data = reactive<PageData<LeaveForm, LeaveQuery>>({
   }
 });
 
-const handleClose = () => {
-  dialogVisible.visible = false;
-  flowCode.value = '';
-  buttonLoading.value = false;
-};
 const { form, rules } = toRefs(data);
 
 /** 表单重置 */
@@ -200,7 +188,7 @@ const getInfo = () => {
 };
 
 /** 提交按钮 */
-const submitForm = (status: string) => {
+const submitForm = (status: string, mode: boolean) => {
   if (leaveTime.value.length === 0) {
     proxy?.$modal.msgError('请假时间不能为空');
     return;
@@ -211,29 +199,30 @@ const submitForm = (status: string) => {
       form.value.endDate = leaveTime.value[1];
       if (valid) {
         buttonLoading.value = true;
-        let res: AxiosResponse<LeaveVO>;
-        if (form.value.id) {
-          res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
-        } else {
-          res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
-        }
-        form.value = res.data;
-        if (status === 'draft') {
+        // 设置后端发起和不等于草稿状态 直接走流程发起
+        if (mode && status != 'draft') {
+          const res = await submitAndFlowStart(form.value).finally(() => (buttonLoading.value = false));
+          form.value = res.data;
           buttonLoading.value = false;
-          proxy?.$modal.msgSuccess('暂存成功');
+          proxy?.$modal.msgSuccess('操作成功');
           proxy.$tab.closePage(proxy.$route);
           proxy.$router.go(-1);
         } else {
-          if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
-            flowCode.value = flowCodeOptions[0].value;
-            dialogVisible.visible = true;
-            return;
+          let res;
+          if (form.value.id) {
+            res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
+          } else {
+            res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
           }
-          //说明启动过先随意穿个参数
-          if (flowCode.value === '' || flowCode.value === null) {
-            flowCode.value = 'xx';
+          form.value = res.data;
+          if (status === 'draft') {
+            buttonLoading.value = false;
+            proxy?.$modal.msgSuccess('暂存成功');
+            proxy.$tab.closePage(proxy.$route);
+            proxy.$router.go(-1);
+          } else {
+            await handleStartWorkFlow(res.data);
           }
-          await handleStartWorkFlow(res.data);
         }
       }
     });
@@ -242,10 +231,6 @@ const submitForm = (status: string) => {
   }
 };
 
-const submitFlow = async () => {
-  handleStartWorkFlow(form.value);
-  dialogVisible.visible = false;
-};
 //提交申请
 const handleStartWorkFlow = async (data: LeaveForm) => {
   try {
@@ -258,7 +243,13 @@ const handleStartWorkFlow = async (data: LeaveForm) => {
       // leave4/5 使用的流程变量
       userList: ['1', '3', '4']
     };
+    //流程实例业务扩展字段
+    flowInstanceBizExtBo.value = {
+      businessTitle: '请假申请',
+      businessCode: data.applyCode
+    };
     submitFormData.value.variables = taskVariables.value;
+    submitFormData.value.flowInstanceBizExtBo = flowInstanceBizExtBo.value;
     const resp = await startWorkFlow(submitFormData.value);
     if (submitVerifyRef.value) {
       buttonLoading.value = false;

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

@@ -24,7 +24,7 @@ const iframeLoaded = () => {
   };
 };
 const open = async (definitionId, disabled) => {
-  const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&disabled=${disabled}`;
+  const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&onlyDesignShow=true`;
   iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
 };
 /** 关闭按钮 */

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

@@ -159,7 +159,7 @@
     <!-- 新增/编辑流程定义 -->
     <el-dialog v-model="modelDialog.visible" :title="modelDialog.title" width="650px" append-to-body :close-on-click-modal="false">
       <template #footer>
-        <el-form ref="defFormRef" :model="form" :rules="rules" label-width="110px">
+        <el-form ref="defFormRef" :model="form" :rules="rules" label-width="120px">
           <el-form-item label="流程类别" prop="category">
             <el-tree-select
               v-model="form.category"
@@ -178,6 +178,21 @@
           <el-form-item label="流程名称" prop="flowName">
             <el-input v-model="form.flowName" placeholder="请输入流程名称" maxlength="100" show-word-limit />
           </el-form-item>
+          <el-form-item label="设计器模式" prop="modelValue">
+            <el-radio-group v-model="form.modelValue" :disabled="!!form.id">
+              <el-radio value="CLASSICS" size="large" border>经典模式</el-radio>
+              <el-radio value="MIMIC" size="large" border>仿钉钉模式</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="流程配置">
+            <el-checkbox v-model="autoPass" label="下一节点执行人是当前任务处理人自动审批" />
+          </el-form-item>
+          <el-form-item label="是否动态表单" prop="formCustom">
+            <el-radio-group v-model="form.formCustom">
+              <el-radio value="Y" size="large" border>是</el-radio>
+              <el-radio value="N" size="large" border>否</el-radio>
+            </el-radio-group>
+          </el-form-item>
           <el-form-item label="表单路径" prop="formPath">
             <el-input v-model="form.formPath" placeholder="请输入表单路径" maxlength="100" show-word-limit />
           </el-form-item>
@@ -215,6 +230,7 @@ const uploadDialogLoading = ref(false);
 const processDefinitionList = ref<FlowDefinitionVo[]>([]);
 const categoryOptions = ref<CategoryTreeVO[]>([]);
 const categoryName = ref('');
+const autoPass = ref(false);
 /** 部署文件分类选择 */
 const selectCategory = ref();
 const defFormRef = ref<ElFormInstance>();
@@ -245,6 +261,8 @@ const queryParams = ref<FlowDefinitionQuery>({
 const rules = {
   category: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
   flowName: [{ required: true, message: '流程定义名称不能为空', trigger: 'blur' }],
+  formCustom: [{ required: true, message: '请选择是否动态表单', trigger: 'change' }],
+  modelValue: [{ required: true, message: '设计器模式不能为空', trigger: 'change' }],
   flowCode: [{ required: true, message: '流程定义编码不能为空', trigger: 'blur' }]
 };
 const initFormData: FlowDefinitionForm = {
@@ -252,7 +270,10 @@ const initFormData: FlowDefinitionForm = {
   flowName: '',
   flowCode: '',
   category: '',
-  formPath: ''
+  ext: '',
+  formPath: '',
+  formCustom: '',
+  modelValue: ''
 };
 //流程定义参数
 const form = ref<FlowDefinitionForm>({
@@ -260,7 +281,10 @@ const form = ref<FlowDefinitionForm>({
   flowName: '',
   flowCode: '',
   category: '',
-  formPath: ''
+  ext: '',
+  formPath: '',
+  formCustom: '',
+  modelValue: ''
 });
 onMounted(() => {
   getPageList();
@@ -329,6 +353,7 @@ const getPageList = async () => {
   const query = proxy.$route.query;
   if (query.activeName) {
     activeName.value = query.activeName;
+    proxy.$route.query.activeName = '';
   }
   if (activeName.value === '0') {
     getList();
@@ -469,6 +494,8 @@ const handleAdd = async () => {
   if (queryParams.value.category != '') {
     form.value.category = queryParams.value.category;
   }
+  form.value.modelValue = 'CLASSICS';
+  form.value.formCustom = 'N';
   modelDialog.visible = true;
   modelDialog.title = '新增流程';
 };
@@ -478,6 +505,13 @@ const handleUpdate = async (row?: FlowDefinitionVo) => {
   const id = row?.id || ids.value[0];
   const res = await getInfo(id);
   Object.assign(form.value, res.data);
+  autoPass.value = false;
+  if (form.value.ext != null && form.value.ext != '') {
+    const extJson = JSON.parse(form.value.ext);
+    if (extJson.autoPass != null && extJson.autoPass != '') {
+      autoPass.value = extJson.autoPass;
+    }
+  }
   modelDialog.visible = true;
   modelDialog.title = '修改流程';
 };
@@ -486,10 +520,14 @@ const handleSubmit = async () => {
   defFormRef.value.validate(async (valid: boolean) => {
     if (valid) {
       loading.value = true;
+      const ext = {};
+      ext.autoPass = autoPass.value;
+      form.value.ext = JSON.stringify(ext);
       if (form.value.id) {
         await edit(form.value).finally(() => (loading.value = false));
       } else {
         await add(form.value).finally(() => (loading.value = false));
+        activeName.value = '1';
       }
       proxy?.$modal.msgSuccess('操作成功');
       modelDialog.visible = false;

+ 76 - 19
src/views/workflow/processInstance/index.vue

@@ -20,14 +20,6 @@
         </el-card>
       </el-col>
       <el-col :lg="20" :xs="24">
-        <!--        <div class="mb-[10px]">
-                  <el-card shadow="hover" class="text-center">
-                    <el-radio-group v-model="tab" @change="changeTab(tab)">
-                      <el-radio-button value="running">运行中</el-radio-button>
-                      <el-radio-button value="finish">已完成</el-radio-button>
-                    </el-radio-group>
-                  </el-card>
-                </div>-->
         <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
           <div v-show="showSearch" class="mb-[10px]">
             <el-card shadow="hover">
@@ -69,15 +61,17 @@
             <el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
               <el-table-column type="selection" width="55" align="center" />
               <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
-              <el-table-column :show-overflow-tooltip="true" align="center" label="流程定义名称">
+              <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+              <el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
+              <el-table-column :show-overflow-tooltip="true" align="center" width="120" label="流程定义名称">
                 <template #default="scope">
                   <span>{{ scope.row.flowName }}v{{ scope.row.version }}</span>
                 </template>
               </el-table-column>
-              <el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
-              <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
+              <el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
               <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
-              <el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
+              <el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
+              <el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
               <el-table-column align="center" prop="version" label="版本号" width="90">
                 <template #default="scope"> v{{ scope.row.version }}.0</template>
               </el-table-column>
@@ -87,14 +81,14 @@
                   <el-tag v-else type="danger">挂起</el-tag>
                 </template>
               </el-table-column>
-              <el-table-column align="center" label="流程状态" min-width="70">
+              <el-table-column align="center" label="流程状态" min-width="80">
                 <template #default="scope">
                   <dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
                 </template>
               </el-table-column>
               <el-table-column align="center" prop="createTime" label="启动时间" width="160"></el-table-column>
               <el-table-column v-if="tab === 'finish'" align="center" prop="updateTime" label="结束时间" width="160"></el-table-column>
-              <el-table-column label="操作" align="center" :width="165">
+              <el-table-column label="操作" align="center" :width="165" fixed="right">
                 <template #default="scope">
                   <el-row v-if="tab === 'running'" :gutter="10" class="mb8">
                     <el-col :span="1.5">
@@ -154,8 +148,8 @@
       </el-table>
     </el-dialog>
     <!-- 流程变量开始 -->
-    <el-dialog v-model="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
-      <el-card v-loading="variableLoading" class="box-card">
+    <el-dialog v-model="variableVisible" v-if="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
+      <el-card v-loading="variableLoading">
         <template #header>
           <div class="clearfix">
             <span
@@ -167,6 +161,19 @@
           <VueJsonPretty :data="formatToJsonObject(variables)" />
         </div>
       </el-card>
+      <el-card v-loading="variableLoading">
+        <el-form ref="ruleFormRef" :model="form" :inline="true" :rules="rules" label-width="120px">
+          <el-form-item label="变量KEY" prop="key">
+            <el-input v-model="form.key" placeholder="请输入变量KEY" />
+          </el-form-item>
+          <el-form-item label="变量值" prop="value">
+            <el-input v-model="form.value" placeholder="请输入变量值" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
     </el-dialog>
     <!-- 流程变量结束 -->
 
@@ -176,7 +183,15 @@
 </template>
 
 <script setup lang="ts">
-import { pageByRunning, pageByFinish, deleteByInstanceIds, instanceVariable, invalid } from '@/api/workflow/instance';
+import {
+  pageByRunning,
+  pageByFinish,
+  deleteByInstanceIds,
+  deleteHisByInstanceIds,
+  instanceVariable,
+  invalid,
+  updateVariable
+} from '@/api/workflow/instance';
 import { categoryTree } from '@/api/workflow/category';
 import { CategoryTreeVO } from '@/api/workflow/category/types';
 import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
@@ -185,6 +200,7 @@ import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
 import VueJsonPretty from 'vue-json-pretty';
 import 'vue-json-pretty/lib/styles.css';
 import UserSelect from '@/components/UserSelect/index.vue';
+import { ElForm, FormInstance } from 'element-plus';
 //审批记录组件
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
@@ -192,7 +208,12 @@ const queryFormRef = ref<ElFormInstance>();
 const categoryTreeRef = ref<ElTreeInstance>();
 import { ref } from 'vue';
 import { UserVO } from '@/api/system/user/types';
-
+const form = ref<Record<string, any>>({
+  instanceId: undefined,
+  key: undefined,
+  value: undefined
+});
+const ruleFormRef = ref<FormInstance>();
 const userSelectRef = ref<InstanceType<typeof UserSelect>>();
 // 遮罩层
 const loading = ref(true);
@@ -208,6 +229,8 @@ const multiple = ref(true);
 const showSearch = ref(true);
 // 总条数
 const total = ref(0);
+// 实例id
+const instanceId = ref(undefined);
 
 // 流程变量是否显示
 const variableVisible = ref(false);
@@ -333,7 +356,7 @@ const handleDelete = async (row: FlowInstanceVO) => {
     await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
     getProcessInstanceRunningList();
   } else {
-    await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
+    await deleteHisByInstanceIds(instanceIdList).finally(() => (loading.value = false));
     getProcessInstanceFinishList();
   }
   proxy?.$modal.msgSuccess('删除成功');
@@ -378,12 +401,16 @@ const handleView = (row) => {
 
 //查询流程变量
 const handleInstanceVariable = async (row: FlowInstanceVO) => {
+  instanceId.value = row.id;
   variableLoading.value = true;
   variableVisible.value = true;
   processDefinitionName.value = row.flowName;
   const data = await instanceVariable(row.id);
   variables.value = data.data.variable;
   variableLoading.value = false;
+  form.value.instanceId = undefined;
+  form.value.key = undefined;
+  form.value.value = undefined;
 };
 
 /**
@@ -414,6 +441,36 @@ const userSelectCallBack = (data: UserVO[]) => {
     queryParams.value.createByIds = selectUserIds.value;
   }
 };
+const rules = reactive<Record<string, any>>({
+  key: [
+    {
+      required: true,
+      message: '请输入KEY',
+      trigger: 'blur'
+    }
+  ],
+  value: [
+    {
+      required: true,
+      message: '请输入变量值',
+      trigger: 'blur'
+    }
+  ]
+});
+
+const handleVariable = async (formEl: FormInstance | undefined) => {
+  await formEl.validate(async (valid, fields) => {
+    if (valid) {
+      form.value.instanceId = instanceId.value;
+      await proxy?.$modal.confirm('是否确认提交?');
+      await updateVariable(form.value);
+      proxy?.$modal.msgSuccess('操作成功');
+      const data = await instanceVariable(instanceId.value);
+      variables.value = data.data.variable;
+    }
+  });
+};
+
 onMounted(() => {
   getProcessInstanceRunningList();
   getTreeselect();

+ 360 - 0
src/views/workflow/spel/index.vue

@@ -0,0 +1,360 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="组件名称" prop="componentName">
+              <el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="方法名" prop="methodName">
+              <el-input v-model="queryParams.methodName" placeholder="请输入方法名" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:spel:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['workflow:spel:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:spel:remove']">删除</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="spelList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="序号" type="index" width="60" align="center">
+          <template #default="scope">
+            <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="组件名称" align="center">
+          <template #default="scope">
+            {{ scope.row.componentName || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="方法名称" align="center">
+          <template #default="scope">
+            {{ scope.row.methodName || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="参数名称" align="center">
+          <template #default="scope">
+            {{ scope.row.methodParams || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="SPEL表达式" align="center" prop="viewSpel" />
+        <el-table-column label="状态" align="center" prop="status">
+          <template #default="scope">
+            <el-tag v-if="scope.row.status === '0'">正常</el-tag>
+            <el-tag v-else>停用</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center">
+          <template #default="scope">
+            {{ scope.row.remark || '-' }}
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:spel:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:spel:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改流程spel表达式定义对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="550px" append-to-body>
+      <el-form ref="spelFormRef" :model="form" :rules="rules" label-width="100px">
+        <!-- 组件名称 -->
+        <el-form-item label="组件名称" prop="componentName">
+          <el-input v-model="form.componentName" placeholder="请输入组件名称" @input="updateViewSpel" />
+          <template #label>
+            <span>
+              <el-tooltip content="注册到Spring容器中的组件名,如:spelRuleComponent" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+              组件名称
+            </span>
+          </template>
+        </el-form-item>
+        <el-form-item label="方法名称" prop="methodName">
+          <el-input v-model="form.methodName" placeholder="请输入方法名称" @input="updateViewSpel" />
+          <template #label>
+            <span>
+              <el-tooltip content="组件中的方法名称,如:selectDeptLeaderById" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+              方法名称
+            </span>
+          </template>
+        </el-form-item>
+        <el-form-item label="方法参数" prop="methodParams">
+          <el-input v-model="form.methodParams" placeholder="请输入方法参数" @input="updateViewSpel" />
+          <template #label>
+            <span>
+              <el-tooltip content="方法参数,如:deptId, 多个使用 ',' 分隔,单参数变量仅支持单个方法参数" placement="top">
+                <el-icon><question-filled /></el-icon>
+              </el-tooltip>
+              方法参数
+            </span>
+          </template>
+        </el-form-item>
+
+        <!-- 改为只读文本展示 -->
+        <el-form-item label="SPEL表达式">
+          <span class="preview-box">
+            {{ form.viewSpel || '例如:#{@组件名.方法名(#方法参数)} 或 ${方法参数}' }}
+          </span>
+        </el-form-item>
+        <el-form-item label="状态" prop="status">
+          <el-radio-group v-model="form.status">
+            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
+              {{ dict.label }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Spel" lang="ts">
+import { listSpel, getSpel, delSpel, addSpel, updateSpel } from '@/api/workflow/spel';
+import { SpelVO, SpelQuery, SpelForm } from '@/api/workflow/spel/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
+
+const spelList = ref<SpelVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+const spelFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: SpelForm = {
+  id: undefined,
+  componentName: undefined,
+  methodName: undefined,
+  methodParams: undefined,
+  viewSpel: undefined,
+  status: '0',
+  remark: undefined,
+}
+const data = reactive<PageData<SpelForm, SpelQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    componentName: undefined,
+    methodName: undefined,
+    methodParams: undefined,
+    viewSpel: undefined,
+    status: '0',
+    params: {
+    }
+  },
+  rules: {
+    status: [
+      { required: true, message: "状态不能为空", trigger: "change" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询流程spel表达式定义列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listSpel(queryParams.value);
+  spelList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  spelFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: SpelVO[]) => {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加流程spel表达式定义";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: SpelVO) => {
+  reset();
+  const _id = row?.id || ids.value[0]
+  const res = await getSpel(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改流程spel表达式定义";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  spelFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateSpel(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addSpel(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: SpelVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除流程spel表达式定义编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+  await delSpel(_ids);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 控制是否显示 viewSpel 输入框 */
+const showViewSpelInput = ref(false);
+
+/** 更新 spel 预览值并决定是否显示输入框 */
+const updateViewSpel = () => {
+  const comp = (form.value.componentName || '').trim();
+  const method = (form.value.methodName || '').trim();
+  const paramStr = (form.value.methodParams || '').trim();
+
+  if (!comp && !method && !paramStr) {
+    form.value.viewSpel = '';
+    return;
+  }
+
+  // 替换变量值:只有参数存在,组件和方法都不存在
+  if (!comp && !method && paramStr) {
+    const paramList = paramStr.split(',')
+      .map(p => p.trim())
+      .filter(p => p.length > 0);
+
+    if (paramList.length === 1) {
+      form.value.viewSpel = `\${${paramList[0]}}`;
+      return;
+    }
+  }
+
+  // 如果缺少组件或方法,提示填写
+  if (!comp || !method) {
+    form.value.viewSpel = '请填写组件名称和方法名';
+    return;
+  }
+
+  let paramList = [];
+
+  if (paramStr) {
+    // 分割并过滤掉空参数
+    paramList = paramStr.split(',')
+      .map(p => p.trim())
+      .filter(p => p.length > 0);
+  }
+
+  const paramPart = paramList.length > 0
+    ? '(' + paramList.map(p => `#${p}`).join(',') + ')'
+    : '()';
+
+  form.value.viewSpel = `#{@${comp}.${method}${paramPart}}`;
+};
+
+/** 监听所有字段变化 */
+watch(
+  () => [form.value.componentName, form.value.methodName, form.value.methodParams],
+  updateViewSpel
+);
+
+onMounted(() => {
+  getList();
+});
+</script>
+<style lang="scss">
+.preview-box {
+  width: 100%;
+  padding: 10px 12px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  color: #333;
+  font-family: monospace; /* 等宽字体更清晰 */
+  white-space: nowrap;    /* 禁止换行 */
+  overflow-x: auto;       /* 超出宽度时显示水平滚动条 */
+  min-height: 36px;       /* 与 el-input 高度对齐 */
+  line-height: 1.5;
+}
+</style>

+ 33 - 11
src/views/workflow/task/allTaskWaiting.vue

@@ -27,7 +27,8 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5" v-if="tab === 'waiting'">
-            <el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUpdate">修改办理人 </el-button>
+            <el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()">修改办理人 </el-button>
+            <el-button type="primary" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()">催办 </el-button>
           </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
         </el-row>
@@ -38,14 +39,16 @@
         <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
           <el-table-column type="selection" width="55" align="center" />
           <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
-          <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
-          <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
+          <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+          <el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
+          <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" width="120" label="流程定义名称"></el-table-column>
+          <el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
           <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
           <el-table-column align="center" prop="version" label="版本号" width="90">
             <template #default="scope"> v{{ scope.row.version }}.0</template>
           </el-table-column>
-          <el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
-          <el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
+          <el-table-column align="center" prop="nodeName" :show-overflow-tooltip="true" label="任务名称"></el-table-column>
+          <el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
           <el-table-column align="center" label="办理人">
             <template #default="scope">
               <template v-if="tab === 'waiting'">
@@ -97,21 +100,24 @@
       </el-tabs>
     </el-card>
     <!-- 选人组件 -->
-    <UserSelect ref="userSelectRef" :multiple="false" @confirm-call-back="submitCallback"></UserSelect>
+    <UserSelect ref="userSelectRef" :multiple="userMultiple" @confirm-call-back="submitCallback"></UserSelect>
     <!-- 流程干预组件 -->
     <processMeddle ref="processMeddleRef" @submitCallback="getWaitingList"></processMeddle>
     <!-- 申请人 -->
     <UserSelect ref="applyUserSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
+    <!-- 流程干预组件 -->
+    <messageType ref="messageTypeRef" @submitCallback="handleUserTask"></messageType>
   </div>
 </template>
 
 <script setup lang="ts">
-import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee } from '@/api/workflow/task';
+import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee, urgeTask } from '@/api/workflow/task';
 import UserSelect from '@/components/UserSelect';
 import { TaskQuery } from '@/api/workflow/task/types';
 import workflowCommon from '@/api/workflow/workflowCommon';
 import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
 import processMeddle from '@/components/Process/processMeddle';
+import messageType from '@/components/Process/MessageType';
 import { UserVO } from '@/api/system/user/types';
 import { TabsPaneContext } from 'element-plus';
 //选人组件
@@ -120,6 +126,8 @@ const userSelectRef = ref<InstanceType<typeof UserSelect>>();
 const processMeddleRef = ref<InstanceType<typeof processMeddle>>();
 //选人组件
 const applyUserSelectRef = ref<InstanceType<typeof UserSelect>>();
+//消息组件
+const messageTypeRef = ref<InstanceType<typeof messageType>>();
 const queryFormRef = ref<ElFormInstance>();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
@@ -132,13 +140,14 @@ const ids = ref<Array<any>>([]);
 const single = ref(true);
 // 非多个禁用
 const multiple = ref(true);
+const userMultiple = ref(false);
 // 显示搜索条件
 const showSearch = ref(true);
 // 总条数
 const total = ref(0);
 // 模型定义表格数据
 const taskList = ref([]);
-const title = ref('');
+const buttonType = ref('');
 //申请人id
 const selectUserIds = ref<Array<number | string>>([]);
 //申请人选择数量
@@ -204,10 +213,24 @@ const getFinishList = () => {
     loading.value = false;
   });
 };
+// 打开催办
+const handleUrgeTaskOpen = () => {
+  messageTypeRef.value.open();
+};
 //打开修改选人
-const handleUpdate = () => {
+const handleUserOpen = () => {
   userSelectRef.value.open();
 };
+
+//打开修改选人
+const handleUserTask = async (data) => {
+  await proxy?.$modal.confirm('是否确认提交?');
+  data.taskIdList = ids.value;
+  await urgeTask(data);
+  messageTypeRef.value.close();
+  proxy?.$modal.msgSuccess('操作成功');
+  handleQuery();
+};
 //修改办理人
 const submitCallback = async (data) => {
   if (data && data.length > 0) {
@@ -227,8 +250,7 @@ const handleView = (row) => {
     taskId: row.id,
     type: 'view',
     formCustom: row.formCustom,
-    formPath: row.formPath,
-    instanceId: row.instanceId
+    formPath: row.formPath
   });
   workflowCommon.routerJump(routerJumpVo, proxy);
 };

+ 2 - 0
src/views/workflow/task/taskCopyList.vue

@@ -31,6 +31,8 @@
       <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
         <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
         <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
         <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>

+ 9 - 7
src/views/workflow/task/taskFinish.vue

@@ -36,14 +36,16 @@
       <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
-        <el-table-column align="center" prop="flowName" label="流程定义名称"></el-table-column>
-        <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
+        <el-table-column align="center" prop="flowName" width="120" label="流程定义名称"></el-table-column>
+        <el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
         <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
         <el-table-column align="center" prop="version" label="版本号" width="90">
           <template #default="scope"> v{{ scope.row.version }}.0</template>
         </el-table-column>
-        <el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
-        <el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
+        <el-table-column align="center" prop="nodeName" :show-overflow-tooltip="true" label="任务名称"></el-table-column>
+        <el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
         <el-table-column align="center" prop="approverName" label="办理人">
           <template #default="scope">
             <el-tag type="success">
@@ -51,17 +53,17 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
+        <el-table-column align="center" label="流程状态" prop="flowStatus" min-width="80">
           <template #default="scope">
             <dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
           </template>
         </el-table-column>
-        <el-table-column align="center" label="任务状态" prop="flowTaskStatus" min-width="70">
+        <el-table-column align="center" label="任务状态" prop="flowTaskStatus" min-width="80">
           <template #default="scope">
             <dict-tag :options="wf_task_status" :value="scope.row.flowTaskStatus"></dict-tag>
           </template>
         </el-table-column>
-        <el-table-column align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
+        <el-table-column align="center" prop="createTime" label="创建时间" :show-overflow-tooltip="true" width="150"></el-table-column>
         <el-table-column label="操作" align="center" width="200">
           <template #default="scope">
             <el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>

+ 2 - 0
src/views/workflow/task/taskWaiting.vue

@@ -36,6 +36,8 @@
       <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
         <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
         <el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
         <el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>