兰宝车间质量管理系统-前端
疯狂的狮子Li
2025-01-20 5e440a7dc434c43eb828fa62cf9c12b0078b8565
!173 发布 5.3.0-BETA 公测版本
Merge pull request !173 from 疯狂的狮子Li/dev
已添加8个文件
已删除55个文件
已修改89个文件
12234 ■■■■ 文件已修改
.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintignore 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.cjs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
eslint.config.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/monitor/online/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept/index.ts 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/dept/types.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenant/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/category/index.ts 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/category/types.ts 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definition/index.ts 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definition/types.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definitionConfig/index.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/definitionConfig/types.ts 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/formManage/index.ts 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/formManage/types.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/instance/index.ts 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/instance/types.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/model/index.ts 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/model/types.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/nodeConfig/types.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processDefinition/index.ts 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processDefinition/types.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processInstance/index.ts 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/processInstance/types.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/task/index.ts 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/task/types.ts 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/workflowCommon/index.ts 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/workflow/workflowCommon/types.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/defaultXML.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/lang/zh.ts 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/moddle/flowable.ts 1250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Palette/CustomPaletteProvider.ts 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Renderer/CustomRenderer.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/Translate/index.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/module/index.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/showConfig.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/assets/style/index.scss 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/hooks/usePanel.ts 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/hooks/useParseElement.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/index.vue 496 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/GatewayPanel.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/ParticipantPanel.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/ProcessPanel.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/SequenceFlowPanel.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/StartEndPanel.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/SubProcessPanel.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/TaskPanel.vue 491 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/index.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/DueDate.vue 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/ExecutionListener.vue 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/ListenerParam.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/bpmn/panel/property/TaskListener.vue 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BpmnDesign/index.vue 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BpmnView/index.vue 411 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImageUpload/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Process/approvalRecord.vue 224 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Process/multiInstanceUser.vue 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Process/processMeddle.vue 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Process/submitVerify.vue 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RoleSelect/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/UserSelect/index.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/permission/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/SettingTypeEnum.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/bpmn/IndexEnums.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enums/layout/LayoutEnum.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/en_US.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/en_US.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/index.ts 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/zh_CN.json 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/lang/zh_CN.ts 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TopBar/search.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/cache.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tab.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/dict.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/modeler.ts 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/permission.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.ts 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/editor/global.d.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/index.d.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/moddle.d.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/bpmn/panel.d.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/module.d.ts 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/sse.ts 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/validate.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/demo/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/tree/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/oper-info-dialog.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 81 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/client/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/config.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenant/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenantPackage/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/onlineDevice.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/category/index.vue 194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/formManage/index.vue 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/leave/index.vue 68 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/leave/leaveEdit.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/model/index.vue 383 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/processDefinition/components/processPreview.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/processDefinition/design.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/processDefinition/index.vue 600 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/processInstance/index.vue 359 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/task/allTaskWaiting.vue 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/task/myDocument.vue 140 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/task/taskCopyList.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/task/taskFinish.vue 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/workflow/task/taskWaiting.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/i18n.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite/plugins/index.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -11,7 +11,7 @@
VITE_APP_CONTEXT_PATH = '/'
# ç›‘控地址
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/applications'
# SnailJob æŽ§åˆ¶å°åœ°å€
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
.eslintignore
ÎļþÒÑɾ³ý
.eslintrc.cjs
ÎļþÒÑɾ³ý
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) ç‰ˆæœ¬ã€‚
- æˆå‘˜é¡¹ç›®: åŸºäºŽ vben(ant-design-vue) çš„前端项目 [ruoyi-plus-vben](https://gitee.com/dapppp/ruoyi-plus-vben)
- æˆå‘˜é¡¹ç›®: åŸºäºŽ vben5(ant-design-vue) çš„前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
- é…å¥—后端代码仓库地址
- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
eslint.config.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import { readFile } from 'node:fs/promises';
import prettier from 'eslint-plugin-prettier';
/**
 * https://blog.csdn.net/sayUonly/article/details/123482912
 * è‡ªåŠ¨å¯¼å…¥çš„é…ç½®
 */
const autoImportFile = new URL('./.eslintrc-auto-import.json', import.meta.url);
const autoImportGlobals = JSON.parse(await readFile(autoImportFile, 'utf8'));
/** @type {import('eslint').Linter.Config[]} */
export default [
  {
    /**
     * ä¸éœ€è¦.eslintignore文件 è€Œæ˜¯åœ¨è¿™é‡Œé…ç½®
     */
    ignores: [
      '*.sh',
      'node_modules',
      '*.md',
      '*.woff',
      '*.ttf',
      '.vscode',
      '.idea',
      'dist',
      '/public',
      '/docs',
      '.husky',
      '.local',
      '/bin',
      '.eslintrc.cjs',
      'prettier.config.js',
      'src/assets',
      'tailwind.config.js'
    ]
  },
  { files: ['**/*.{js,mjs,cjs,ts,vue}'] },
  {
    languageOptions: {
      globals: globals.browser
    }
  },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/essential'],
  {
    files: ['**/*.vue'],
    languageOptions: {
      parserOptions: {
        parser: tseslint.parser
      }
    }
  },
  {
    languageOptions: {
      globals: {
        // è‡ªåŠ¨å¯¼å…¥çš„é…ç½® undef
        ...autoImportGlobals.globals,
        DialogOption: 'readonly',
        LayoutSetting: 'readonly'
      }
    },
    plugins: { prettier },
    rules: {
      '@typescript-eslint/no-empty-function': 'off',
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-unused-vars': 'off',
      '@typescript-eslint/no-this-alias': 'off',
      // vue
      'vue/multi-word-component-names': 'off',
      'vue/valid-define-props': 'off',
      'vue/no-v-model-argument': 'off',
      'prefer-rest-params': 'off',
      // prettier
      'prettier/prettier': 'error',
      // å…è®¸ä½¿ç”¨ç©ºObject类型 {}
      '@typescript-eslint/no-empty-object-type': 'off',
      '@typescript-eslint/no-unused-expressions': 'off'
    }
  }
];
package.json
@@ -1,6 +1,7 @@
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "name": "ruoyi-vue-plus",
  "version": "5.2.3",
  "version": "5.3.0-BETA",
  "description": "RuoYi-Vue-Plus多租户管理系统",
  "author": "LionLi",
  "license": "MIT",
@@ -10,7 +11,8 @@
    "build:prod": "vite build --mode production",
    "build:dev": "vite build --mode development",
    "preview": "vite preview",
    "lint:eslint": "eslint  --fix --ext .ts,.js,.vue ./src ",
    "lint:eslint": "eslint",
    "lint:eslint:fix": "eslint --fix",
    "prettier": "prettier --write ."
  },
  "repository": {
@@ -21,71 +23,65 @@
    "@element-plus/icons-vue": "2.3.1",
    "@highlightjs/vue-plugin": "2.1.0",
    "@vueup/vue-quill": "1.2.0",
    "@vueuse/core": "10.9.0",
    "@vueuse/core": "11.3.0",
    "animate.css": "4.1.1",
    "await-to-js": "3.0.0",
    "axios": "1.6.8",
    "bpmn-js": "16.4.0",
    "axios": "1.7.8",
    "crypto-js": "4.2.0",
    "diagram-js": "12.3.0",
    "didi": "9.0.2",
    "echarts": "5.5.0",
    "element-plus": "2.7.8",
    "element-plus": "2.8.8",
    "file-saver": "2.0.5",
    "fuse.js": "7.0.0",
    "highlight.js": "11.9.0",
    "image-conversion": "^2.1.1",
    "image-conversion": "2.1.1",
    "js-cookie": "3.0.5",
    "jsencrypt": "3.3.2",
    "nprogress": "0.2.0",
    "pinia": "2.1.7",
    "pinia": "2.2.6",
    "screenfull": "6.0.2",
    "vue": "3.4.34",
    "vue": "3.5.13",
    "vue-cropper": "1.1.1",
    "vue-i18n": "9.10.2",
    "vue-router": "4.3.2",
    "vue-types": "5.1.1",
    "vue-i18n": "10.0.5",
    "vue-json-pretty": "2.4.0",
    "vue-router": "4.4.5",
    "vue-types": "5.1.3",
    "vxe-table": "4.5.22"
  },
  "devDependencies": {
    "@iconify/json": "2.2.201",
    "@intlify/unplugin-vue-i18n": "3.0.1",
    "@eslint/js": "9.15.0",
    "@iconify/json": "2.2.276",
    "@types/crypto-js": "4.2.2",
    "@types/file-saver": "2.0.7",
    "@types/js-cookie": "3.0.6",
    "@types/node": "18.18.2",
    "@types/nprogress": "0.2.3",
    "@typescript-eslint/eslint-plugin": "7.3.1",
    "@typescript-eslint/parser": "7.3.1",
    "@unocss/preset-attributify": "0.58.6",
    "@unocss/preset-icons": "0.58.6",
    "@unocss/preset-uno": "0.58.6",
    "@unocss/preset-attributify": "0.64.1",
    "@unocss/preset-icons": "0.64.1",
    "@unocss/preset-uno": "0.64.1",
    "@vitejs/plugin-vue": "5.0.4",
    "@vue/compiler-sfc": "3.4.23",
    "autoprefixer": "10.4.18",
    "eslint": "8.57.0",
    "eslint-config-prettier": "9.1.0",
    "eslint-define-config": "2.1.0",
    "eslint-plugin-prettier": "5.1.3",
    "eslint-plugin-promise": "6.1.1",
    "eslint-plugin-node": "11.1.0",
    "eslint-plugin-import": "2.29.1",
    "eslint-plugin-vue": "9.23.0",
    "eslint": "9.15.0",
    "eslint-plugin-prettier": "^5.2.1",
    "eslint-plugin-vue": "9.31.0",
    "fast-glob": "3.3.2",
    "globals": "15.12.0",
    "postcss": "8.4.36",
    "prettier": "3.2.5",
    "sass": "1.72.0",
    "typescript": "5.4.5",
    "unocss": "0.58.6",
    "typescript": "5.7.2",
    "typescript-eslint": "8.16.0",
    "unocss": "0.64.1",
    "unplugin-auto-import": "0.17.5",
    "unplugin-icons": "0.18.5",
    "unplugin-vue-components": "0.26.0",
    "unplugin-vue-setup-extend-plus": "1.0.1",
    "vite": "5.2.12",
    "vite": "5.4.11",
    "vite-plugin-compression": "0.5.1",
    "vite-plugin-svg-icons": "2.0.1",
    "vitest": "1.5.0",
    "vue-eslint-parser": "9.4.2",
    "vue-tsc": "2.0.13"
  }
}
src/api/login.ts
@@ -51,10 +51,12 @@
 * æ³¨é”€
 */
export function logout() {
  request({
    url: '/resource/sse/close',
    method: 'get'
  });
  if (import.meta.env.VITE_APP_SSE === 'true') {
    request({
      url: '/resource/sse/close',
      method: 'get'
    });
  }
  return request({
    url: '/auth/logout',
    method: 'post'
@@ -100,11 +102,11 @@
}
// èŽ·å–ç§Ÿæˆ·åˆ—è¡¨
export function getTenantList(): AxiosPromise<TenantInfo> {
export function getTenantList(isToken: boolean): AxiosPromise<TenantInfo> {
  return request({
    url: '/auth/tenant/list',
    headers: {
      isToken: false
      isToken: isToken
    },
    method: 'get'
  });
src/api/monitor/online/index.ts
@@ -30,7 +30,7 @@
// åˆ é™¤å½“前在线设备
export function delOnline(tokenId: string) {
  return request({
    url: '/monitor/online/' + tokenId,
    method: 'post'
    url: '/monitor/online/myself/' + tokenId,
    method: 'delete'
  });
}
src/api/system/dept/index.ts
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DeptForm, DeptQuery, DeptVO } from './types';
import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types';
// æŸ¥è¯¢éƒ¨é—¨åˆ—表
export const listDept = (query?: DeptQuery) => {
@@ -8,6 +8,17 @@
    url: '/system/dept/list',
    method: 'get',
    params: query
  });
};
/**
 * é€šè¿‡deptIds查询部门
 * @param deptIds
 */
export const optionSelect = (deptIds: (number | string)[]): AxiosPromise<DeptVO[]> => {
  return request({
    url: '/system/dept/optionselect?deptIds=' + deptIds,
    method: 'get'
  });
};
@@ -28,7 +39,7 @@
};
// æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
export const treeselect = (): AxiosPromise<DeptVO[]> => {
export const treeselect = (): AxiosPromise<DeptTreeVO[]> => {
  return request({
    url: '/system/dept/treeselect',
    method: 'get'
src/api/system/dept/types.ts
@@ -29,6 +29,18 @@
}
/**
 * éƒ¨é—¨ç±»åž‹
 */
export interface DeptTreeVO extends BaseEntity {
  id: number | string;
  label: string;
  parentId: number | string;
  weight: number;
  children: DeptTreeVO[];
  disabled: boolean;
}
/**
 * éƒ¨é—¨è¡¨å•类型
 */
export interface DeptForm {
src/api/system/tenant/index.ts
@@ -96,6 +96,6 @@
export function syncTenantDict() {
  return request({
    url: '/system/tenant/syncTenantDict',
    method: 'get',
    method: 'get'
  });
}
src/api/system/user/index.ts
@@ -1,4 +1,4 @@
import { 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';
@@ -202,7 +202,7 @@
/**
 * æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
 */
export const deptTreeSelect = (): AxiosPromise<DeptVO[]> => {
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
  return request({
    url: '/system/user/deptTree',
    method: 'get'
src/api/workflow/category/index.ts
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { CategoryVO, CategoryForm, CategoryQuery } from '@/api/workflow/category/types';
import { CategoryVO, CategoryForm, CategoryQuery, CategoryTreeVO } from '@/api/workflow/category/types';
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»åˆ—表
@@ -18,11 +18,11 @@
/**
 * æŸ¥è¯¢æµç¨‹åˆ†ç±»è¯¦ç»†
 * @param id
 * @param categoryId
 */
export const getCategory = (id: string | number): AxiosPromise<CategoryVO> => {
export const getCategory = (categoryId: string | number): AxiosPromise<CategoryVO> => {
  return request({
    url: '/workflow/category/' + id,
    url: '/workflow/category/' + categoryId,
    method: 'get'
  });
};
@@ -53,11 +53,24 @@
/**
 * åˆ é™¤æµç¨‹åˆ†ç±»
 * @param id
 * @param categoryId
 */
export const delCategory = (id: string | number | Array<string | number>) => {
export const delCategory = (categoryId: string | number | Array<string | number>) => {
  return request({
    url: '/workflow/category/' + id,
    url: '/workflow/category/' + categoryId,
    method: 'delete'
  });
};
/**
 * èŽ·å–æµç¨‹åˆ†ç±»æ ‘åˆ—è¡¨
 * @param query æµç¨‹å®žä¾‹id
 * @returns
 */
export const categoryTree = (query?: CategoryForm): AxiosPromise<CategoryTreeVO[]> => {
  return request({
    url: `/workflow/category/categoryTree`,
    method: 'get',
    params: query
  });
};
src/api/workflow/category/types.ts
@@ -1,18 +1,16 @@
export interface CategoryTreeVO {
  id: number | string;
  label: string;
  parentId: number | string;
  weight: number;
  children: CategoryTreeVO[];
}
export interface CategoryVO {
  /**
   * ä¸»é”®
   */
  id: string;
  /**
   * åˆ†ç±»åç§°
   * æµç¨‹åˆ†ç±»ID
   */
  categoryName: string;
  /**
   * åˆ†ç±»ç¼–码
   */
  categoryCode: string;
  categoryId: string | number;
  /**
   * çˆ¶çº§id
@@ -20,48 +18,55 @@
  parentId: string | number;
  /**
   * æŽ’序
   * æµç¨‹åˆ†ç±»åç§°
   */
  sortNum: number;
  categoryName: string;
  children?: CategoryVO[];
  /**
   * æ˜¾ç¤ºé¡ºåº
   */
  orderNum: number;
  /**
   * åˆ›å»ºæ—¶é—´
   */
  createTime: string;
  /**
   * å­å¯¹è±¡
   */
  children: CategoryVO[];
}
export interface CategoryForm extends BaseEntity {
  /**
   * ä¸»é”®
   */
  id?: string | number;
  /**
   * åˆ†ç±»åç§°
   * æµç¨‹åˆ†ç±»ID
   */
  categoryId?: string | number;
  /**
   * æµç¨‹åˆ†ç±»åç§°
   */
  categoryName?: string;
  /**
   * åˆ†ç±»ç¼–码
   */
  categoryCode?: string;
  /**
   * çˆ¶çº§id
   * çˆ¶æµç¨‹åˆ†ç±»id
   */
  parentId?: string | number;
  /**
   * æŽ’序
   * æ˜¾ç¤ºé¡ºåº
   */
  sortNum?: number;
  orderNum?: number;
}
export interface CategoryQuery extends PageQuery {
export interface CategoryQuery {
  /**
   * åˆ†ç±»åç§°
   * æµç¨‹åˆ†ç±»åç§°
   */
  categoryName?: string;
  /**
   * åˆ†ç±»ç¼–码
   */
  categoryCode?: string;
}
src/api/workflow/definition/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,170 @@
import request from '@/utils/request';
import { FlowDefinitionQuery, definitionXmlVO, FlowDefinitionForm, FlowDefinitionVo } from '@/api/workflow/definition/types';
import { AxiosPromise } from 'axios';
/**
 * èŽ·å–æµç¨‹å®šä¹‰åˆ—è¡¨
 * @param query æµç¨‹å®žä¾‹id
 * @returns
 */
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<FlowDefinitionVo[]> => {
  return request({
    url: `/workflow/definition/list`,
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢æœªå‘布的流程定义列表
 * @param query æµç¨‹å®žä¾‹id
 * @returns
 */
export const unPublishList = (query: FlowDefinitionQuery): AxiosPromise<FlowDefinitionVo[]> => {
  return request({
    url: `/workflow/definition/unPublishList`,
    method: 'get',
    params: query
  });
};
/**
 * é€šè¿‡æµç¨‹å®šä¹‰id获取xml
 * @param definitionId æµç¨‹å®šä¹‰id
 * @returns
 */
export const definitionXml = (definitionId: string): AxiosPromise<definitionXmlVO> => {
  return request({
    url: `/workflow/definition/definitionXml/${definitionId}`,
    method: 'get'
  });
};
/**
 * åˆ é™¤æµç¨‹å®šä¹‰
 * @param id æµç¨‹å®šä¹‰id
 * @returns
 */
export const deleteDefinition = (id: string | string[]) => {
  return request({
    url: `/workflow/definition/${id}`,
    method: 'delete'
  });
};
/**
 * æŒ‚èµ·/激活
 * @param definitionId æµç¨‹å®šä¹‰id
 * @param activityStatus çŠ¶æ€
 * @returns
 */
export const active = (definitionId: string, activityStatus: boolean) => {
  return request({
    url: `/workflow/definition/active/${definitionId}`,
    method: 'put',
    params: {
      active: activityStatus
    }
  });
};
/**
 * é€šè¿‡zip或xml部署流程定义
 * @returns
 */
export function importDef(data: any) {
  return request({
    url: '/workflow/definition/importDef',
    method: 'post',
    data: data,
    headers: {
      repeatSubmit: false
    }
  });
}
/**
 * å‘布流程定义
 * @param id æµç¨‹å®šä¹‰id
 * @returns
 */
export const publish = (id: string) => {
  return request({
    url: `/workflow/definition/publish/${id}`,
    method: 'put'
  });
};
/**
 * å–消发布流程定义
 * @param id æµç¨‹å®šä¹‰id
 * @returns
 */
export const unPublish = (id: string) => {
  return request({
    url: `/workflow/definition/unPublish/${id}`,
    method: 'put'
  });
};
/**
 * èŽ·å–æµç¨‹å®šä¹‰xml字符串
 * @param id æµç¨‹å®šä¹‰id
 * @returns
 */
export const xmlString = (id: string) => {
  return request({
    url: `/workflow/definition/xmlString/${id}`,
    method: 'get'
  });
};
/**
 * æ–°å¢ž
 * @param data å‚æ•°
 * @returns
 */
export const add = (data: FlowDefinitionForm) => {
  return request({
    url: `/workflow/definition`,
    method: 'post',
    data: data
  });
};
/**
 * ä¿®æ”¹
 * @param data å‚æ•°
 * @returns
 */
export const edit = (data: FlowDefinitionForm) => {
  return request({
    url: `/workflow/definition`,
    method: 'put',
    data: data
  });
};
/**
 * æŸ¥è¯¢è¯¦æƒ…
 * @param id å‚æ•°
 * @returns
 */
export const getInfo = (id: number | string) => {
  return request({
    url: `/workflow/definition/${id}`,
    method: 'get'
  });
};
/**
 * å¤åˆ¶æµç¨‹å®šä¹‰
 * @param id æµç¨‹å®šä¹‰id
 * @returns
 */
export const copy = (id: string) => {
  return request({
    url: `/workflow/definition/copy/${id}`,
    method: 'post'
  });
};
src/api/workflow/definition/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
export interface FlowDefinitionQuery extends PageQuery {
  flowCode?: string;
  flowName?: string;
  category: string | number;
  isPublish?: number;
}
export interface FlowDefinitionVo {
  id: string;
  flowName: string;
  flowCode: string;
  formPath: string;
  version: string;
  isPublish: number;
  activityStatus: number;
  createTime: Date;
  updateTime: Date;
}
export interface FlowDefinitionForm {
  id: string;
  flowName: string;
  flowCode: string;
  category: string;
  formPath: string;
}
export interface definitionXmlVO {
  xml: string[];
  xmlStr: string;
}
src/api/workflow/definitionConfig/index.ts
ÎļþÒÑɾ³ý
src/api/workflow/definitionConfig/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/formManage/index.ts
ÎļþÒÑɾ³ý
src/api/workflow/formManage/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/instance/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,101 @@
import request from '@/utils/request';
import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import { AxiosPromise } from 'axios';
/**
 * æŸ¥è¯¢è¿è¡Œä¸­å®žä¾‹åˆ—表
 * @param query
 * @returns {*}
 */
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
  return request({
    url: '/workflow/instance/pageByRunning',
    method: 'get',
    params: query
  });
};
/**
 * æŸ¥è¯¢å·²å®Œæˆå®žä¾‹åˆ—表
 * @param query
 * @returns {*}
 */
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
  return request({
    url: '/workflow/instance/pageByFinish',
    method: 'get',
    params: query
  });
};
/**
 * é€šè¿‡ä¸šåŠ¡id获取历史流程图
 */
export const flowImage = (businessId: string | number) => {
  return request({
    url: `/workflow/instance/flowImage/${businessId}` + '?t' + Math.random(),
    method: 'get'
  });
};
/**
 * åˆ†é¡µæŸ¥è¯¢å½“前登录人单据
 * @param query
 * @returns {*}
 */
export const pageByCurrent = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
  return request({
    url: '/workflow/instance/pageByCurrent',
    method: 'get',
    params: query
  });
};
/**
 * æ’¤é”€æµç¨‹
 * @param data å‚æ•°
 * @returns
 */
export const cancelProcessApply = (data: any) => {
  return request({
    url: `/workflow/instance/cancelProcessApply`,
    method: 'put',
    data: data
  });
};
/**
 * èŽ·å–æµç¨‹å˜é‡
 * @param instanceId å®žä¾‹id
 * @returns
 */
export const instanceVariable = (instanceId: string | number) => {
  return request({
    url: `/workflow/instance/instanceVariable/${instanceId}`,
    method: 'get'
  });
};
/**
 * åˆ é™¤
 * @param instanceIds æµç¨‹å®žä¾‹id
 * @returns
 */
export const deleteByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
  return request({
    url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
    method: 'delete'
  });
};
/**
 * ä½œåºŸæµç¨‹
 * @param data å‚æ•°
 * @returns
 */
export const invalid = (data: any) => {
  return request({
    url: `/workflow/instance/invalid`,
    method: 'post',
    data: data
  });
};
src/api/workflow/instance/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
import { FlowTaskVO } from '@/api/workflow/task/types';
export interface FlowInstanceQuery extends PageQuery {
  category?: string | number;
  nodeName?: string;
  flowCode?: string;
  flowName?: string;
  createByIds?: string[] | number[];
  businessId?: string;
}
export interface FlowInstanceVO extends BaseEntity {
  id: string | number;
  definitionId: string;
  flowName: string;
  flowCode: string;
  version: string;
  businessId: string;
  activityStatus: number;
  tenantId: string;
  createTime: string;
  createBy: string;
  flowStatus: string;
  flowStatusName: string;
  flowTaskList: FlowTaskVO[];
}
src/api/workflow/model/index.ts
ÎļþÒÑɾ³ý
src/api/workflow/model/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/nodeConfig/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/processDefinition/index.ts
ÎļþÒÑɾ³ý
src/api/workflow/processDefinition/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/processInstance/index.ts
ÎļþÒÑɾ³ý
src/api/workflow/processInstance/types.ts
ÎļþÒÑɾ³ý
src/api/workflow/task/index.ts
@@ -1,15 +1,15 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
import { TaskQuery, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
/**
 * æŸ¥è¯¢å¾…办列表
 * @param query
 * @returns {*}
 */
export const getPageByTaskWait = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
export const pageByTaskWait = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
  return request({
    url: '/workflow/task/getPageByTaskWait',
    url: '/workflow/task/pageByTaskWait',
    method: 'get',
    params: query
  });
@@ -20,9 +20,9 @@
 * @param query
 * @returns {*}
 */
export const getPageByTaskFinish = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
  return request({
    url: '/workflow/task/getPageByTaskFinish',
    url: '/workflow/task/pageByTaskFinish',
    method: 'get',
    params: query
  });
@@ -33,9 +33,9 @@
 * @param query
 * @returns {*}
 */
export const getPageByTaskCopy = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
  return request({
    url: '/workflow/task/getPageByTaskCopy',
    url: '/workflow/task/pageByTaskCopy',
    method: 'get',
    params: query
  });
@@ -46,9 +46,9 @@
 * @param query
 * @returns {*}
 */
export const getPageByAllTaskWait = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
  return request({
    url: '/workflow/task/getPageByAllTaskWait',
    url: '/workflow/task/pageByAllTaskWait',
    method: 'get',
    params: query
  });
@@ -59,9 +59,9 @@
 * @param query
 * @returns {*}
 */
export const getPageByAllTaskFinish = (query: TaskQuery): AxiosPromise<TaskVO[]> => {
export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
  return request({
    url: '/workflow/task/getPageByAllTaskFinish',
    url: '/workflow/task/pageByAllTaskFinish',
    method: 'get',
    params: query
  });
@@ -94,30 +94,6 @@
};
/**
 * è®¤é¢†ä»»åŠ¡
 * @param taskId
 * @returns {*}
 */
export const claim = (taskId: string): any => {
  return request({
    url: '/workflow/task/claim/' + taskId,
    method: 'post'
  });
};
/**
 * å½’还任务
 * @param taskId
 * @returns {*}
 */
export const returnTask = (taskId: string): any => {
  return request({
    url: '/workflow/task/returnTask/' + taskId,
    method: 'post'
  });
};
/**
 * ä»»åŠ¡é©³å›ž
 * @param data
 * @returns {*}
@@ -135,61 +111,24 @@
 * @param taskId
 * @returns
 */
export const getTaskById = (taskId: string) => {
export const getTask = (taskId: string) => {
  return request({
    url: '/workflow/task/getTaskById/' + taskId,
    url: '/workflow/task/getTask/' + taskId,
    method: 'get'
  });
};
/**
 * åŠ ç­¾
 * @param data
 * @returns
 */
export const addMultiInstanceExecution = (data: any) => {
  return request({
    url: '/workflow/task/addMultiInstanceExecution',
    method: 'post',
    data: data
  });
};
/**
 * å‡ç­¾
 * @param data
 * @returns
 */
export const deleteMultiInstanceExecution = (data: any) => {
  return request({
    url: '/workflow/task/deleteMultiInstanceExecution',
    method: 'post',
    data: data
  });
};
/**
 * ä¿®æ”¹ä»»åŠ¡åŠžç†äºº
 * @param taskIds
 * @param taskIdList
 * @param userId
 * @returns
 */
export const updateAssignee = (taskIds: Array<string>, userId: string) => {
export const updateAssignee = (taskIdList: Array<string>, userId: string) => {
  return request({
    url: `/workflow/task/updateAssignee/${taskIds}/${userId}`,
    method: 'put'
  });
};
/**
 * è½¬åŠžä»»åŠ¡
 * @returns
 */
export const transferTask = (data: any) => {
  return request({
    url: `/workflow/task/transferTask`,
    method: 'post',
    data: data
    url: `/workflow/task/updateAssignee/${userId}`,
    method: 'put',
    data: taskIdList
  });
};
@@ -206,59 +145,36 @@
};
/**
 * æŸ¥è¯¢æµç¨‹å˜é‡
 * @returns
 */
export const getInstanceVariable = (taskId: string) => {
  return request({
    url: `/workflow/task/getInstanceVariable/${taskId}`,
    method: 'get'
  });
};
/**
 * èŽ·å–å¯é©³å›žå¾—ä»»åŠ¡èŠ‚ç‚¹
 * @returns
 */
export const getTaskNodeList = (processInstanceId: string) => {
export const getBackTaskNode = (definitionId: string, nodeCode: string) => {
  return request({
    url: `/workflow/task/getTaskNodeList/${processInstanceId}`,
    url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
    method: 'get'
  });
};
/**
 * å§”托任务
 * ä»»åŠ¡æ“ä½œ æ“ä½œç±»åž‹ï¼Œå§”æ´¾ delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
 * @returns
 */
export const delegateTask = (data: any) => {
export const taskOperation = (data: TaskOperationBo, operation: string) => {
  return request({
    url: `/workflow/task/delegateTask`,
    url: `/workflow/task/taskOperation/${operation}`,
    method: 'post',
    data: data
  });
};
/**
 * æŸ¥è¯¢å·¥ä½œæµä»»åŠ¡ç”¨æˆ·é€‰æ‹©åŠ ç­¾äººå‘˜
 * @param taskId
 * @returns {*}
 * èŽ·å–å½“å‰ä»»åŠ¡åŠžç†äºº
 * @param taskId ä»»åŠ¡id
 * @returns
 */
export const getTaskUserIdsByAddMultiInstance = (taskId: string) => {
export const currentTaskAllUser = (taskId: string | number) => {
  return request({
    url: '/workflow/task/getTaskUserIdsByAddMultiInstance/' + taskId,
    method: 'get'
  });
};
/**
 * æŸ¥è¯¢å·¥ä½œæµé€‰æ‹©å‡ç­¾äººå‘˜
 * @param taskId
 * @returns {*}
 */
export const getListByDeleteMultiInstance = (taskId: string) => {
  return request({
    url: '/workflow/task/getListByDeleteMultiInstance/' + taskId,
    url: `/workflow/task/currentTaskAllUser/${taskId}`,
    method: 'get'
  });
};
src/api/workflow/task/types.ts
@@ -1,9 +1,8 @@
import { NodeConfigVO } from '@/api/workflow/nodeConfig/types';
import { DefinitionConfigVO } from '@/api/workflow/definitionConfig/types';
export interface TaskQuery extends PageQuery {
  name?: string;
  processDefinitionKey?: string;
  processDefinitionName?: string;
  nodeName?: string;
  flowCode?: string;
  flowName?: string;
  createByIds?: string[] | number[];
}
export interface ParticipantVo {
@@ -12,38 +11,38 @@
  candidateName: string[];
  claim: boolean;
}
export interface TaskVO extends BaseEntity {
  id: string;
  name: string;
  description?: string;
  priority: number;
  owner?: string;
  assignee?: string | number;
  assigneeName?: string;
  processInstanceId: string;
  executionId: string;
  taskDefinitionId?: any;
  processDefinitionId: string;
  endTime?: string;
  taskDefinitionKey: string;
  dueDate?: string;
  category?: any;
  parentTaskId?: any;
  tenantId: string;
  claimTime?: string;
  businessStatus?: string;
  businessStatusName?: string;
  processDefinitionName?: string;
  processDefinitionKey?: string;
  participantVo?: ParticipantVo;
  multiInstance?: boolean;
  businessKey?: string;
  wfNodeConfigVo?: NodeConfigVO;
  wfDefinitionConfigVo?: DefinitionConfigVO;
export interface FlowTaskVO {
  id: string | number;
  createTime?: Date;
  updateTime?: Date;
  tenantId?: string;
  definitionId?: string;
  instanceId: string;
  flowName: string;
  businessId: string;
  nodeCode: string;
  nodeName: string;
  flowCode: string;
  flowStatus: string;
  formCustom: string;
  formPath: string;
  nodeType: number;
  nodeRatio: string | number;
  version?: string;
}
export interface VariableVo {
  key: string;
  value: string;
}
export interface TaskOperationBo {
  //委派/转办人的用户ID(必填,准对委派/转办人操作)
  userId?: string;
  //加签/减签人的用户ID列表(必填,针对加签/减签操作)
  userIds?: string[];
  //任务ID(必填)
  taskId: string | number;
  //意见或备注信息(可选)
  message?: string;
}
src/api/workflow/workflowCommon/index.ts
@@ -2,28 +2,14 @@
export default {
  routerJump(routerJumpVo: RouterJumpVo, proxy) {
    if (routerJumpVo.wfNodeConfigVo && routerJumpVo.wfNodeConfigVo.formType === 'static' && routerJumpVo.wfNodeConfigVo.wfFormManageVo) {
      proxy.$tab.closePage(proxy.$route);
      proxy.$router.push({
        path: `${routerJumpVo.wfNodeConfigVo.wfFormManageVo.router}`,
        query: {
          id: routerJumpVo.businessKey,
          type: routerJumpVo.type,
          taskId: routerJumpVo.taskId
        }
      });
    } else if (routerJumpVo.wfNodeConfigVo && routerJumpVo.wfNodeConfigVo.formType === 'dynamic' && routerJumpVo.wfNodeConfigVo.wfFormManageVo) {
      proxy.$tab.closePage(proxy.$route);
      proxy.$router.push({
        path: `${routerJumpVo.wfNodeConfigVo.wfFormManageVo.router}`,
        query: {
          id: routerJumpVo.businessKey,
          type: routerJumpVo.type,
          taskId: routerJumpVo.taskId
        }
      });
    } else {
      proxy?.$modal.msgError('请到模型配置菜单!');
    }
    proxy.$tab.closePage(proxy.$route);
    proxy.$router.push({
      path: routerJumpVo.formPath,
      query: {
        id: routerJumpVo.businessId,
        type: routerJumpVo.type,
        taskId: routerJumpVo.taskId
      }
    });
  }
};
src/api/workflow/workflowCommon/types.ts
@@ -1,16 +1,13 @@
import { NodeConfigVO } from '@/api/workflow/nodeConfig/types';
import { DefinitionConfigVO } from '@/api/workflow/definitionConfig/types';
export interface RouterJumpVo {
  wfNodeConfigVo: NodeConfigVO;
  wfDefinitionConfigVo: DefinitionConfigVO;
  businessKey: string;
  taskId: string;
  businessId: string;
  taskId: string | number;
  type: string;
  formCustom: string;
  formPath: string;
}
export interface StartProcessBo {
  businessKey: string | number;
  tableName: string;
  businessId: string | number;
  flowCode: string;
  variables: any;
}
src/assets/styles/element-ui.scss
@@ -1,4 +1,3 @@
.el-collapse {
  .collapse__title {
    font-weight: 600;
src/assets/styles/variables.module.scss
@@ -14,14 +14,14 @@
  --tableHeaderBg: #f8f8f9;
  --tableHeaderTextColor: #515a6e;
  // å·¥ä½œæµ
  --bpmn-panel-border: #eeeeee;
  --bpmn-panel-box-shadow: #cccccc;
  --bpmn-panel-bar-background-color: #f5f7fa;
  // ele
  --brder-color: #e8e8e8
  --brder-color: #e8e8e8;
  // æ·»åŠ  tag ç›¸å…³å˜é‡
  --tags-view-active-bg: var(--el-color-primary);
  --tags-view-active-border-color: var(--el-color-primary);
}
html.dark {
  --menuBg: #1d1e1f;
  --menuColor: #bfcbd9;
@@ -41,26 +41,40 @@
  .el-tree-node__content {
    --el-color-primary-light-9: #262727;
  }
  .el-button--primary {
    --el-button-bg-color: var(--el-color-primary-dark-6);
    --el-button-border-color: var(--el-color-primary-light-2);
  }
  .el-switch {
    --el-switch-on-color: var(--el-color-primary-dark-6);
    --el-switch-border-color: var(--el-color-primary-light-2);
  }
  .el-tag--primary {
    --el-tag-bg-color: var(--el-color-primary-dark-6);
    --el-tag-border-color: var(--el-color-primary-light-2);
  }
  // åœ¨æ·±è‰²æ¨¡å¼ä¸‹ä½¿ç”¨æ›´æ·±çš„颜色
  --tags-view-active-bg: var(--el-color-primary-dark-6);
  --tags-view-active-border-color: var(--el-color-primary-light-2);
  // vxe-table ä¸»é¢˜
  --vxe-font-color: #98989E;
  --vxe-primary-color: #2C7ECF;
  --vxe-icon-background-color: #98989E;
  --vxe-table-font-color: #98989E;
  --vxe-font-color: #98989e;
  --vxe-primary-color: #2c7ecf;
  --vxe-icon-background-color: #98989e;
  --vxe-table-font-color: #98989e;
  --vxe-table-resizable-color: #95969a;
  --vxe-table-header-background-color: #28282A;
  --vxe-table-header-background-color: #28282a;
  --vxe-table-body-background-color: #151518;
  --vxe-table-background-color: #4a5663;
  --vxe-table-border-width: 1px;
  --vxe-table-border-color: #37373A;
  --vxe-toolbar-background-color: #37373A;
  // å·¥ä½œæµ
  --bpmn-panel-border: #37373A;
  --bpmn-panel-box-shadow: #37373A;
  --bpmn-panel-bar-background-color: #37373A;
  --vxe-table-border-color: #37373a;
  --vxe-toolbar-background-color: #37373a;
  // ele
  --brder-color: #37373A
  --brder-color: #37373a;
}
// base color
@@ -118,4 +132,4 @@
  dangerColor: $--color-danger;
  infoColor: $--color-info;
  warningColor: $--color-warning;
}
}
src/bpmn/assets/defaultXML.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/lang/zh.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/moddle/flowable.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/module/ContextPad/CustomContextPadProvider.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/module/Palette/CustomPaletteProvider.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/module/Renderer/CustomRenderer.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/module/Translate/index.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/module/index.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/showConfig.ts
ÎļþÒÑɾ³ý
src/bpmn/assets/style/index.scss
ÎļþÒÑɾ³ý
src/bpmn/hooks/usePanel.ts
ÎļþÒÑɾ³ý
src/bpmn/hooks/useParseElement.ts
ÎļþÒÑɾ³ý
src/bpmn/index.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/GatewayPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/ParticipantPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/ProcessPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/SequenceFlowPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/StartEndPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/SubProcessPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/TaskPanel.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/index.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/property/DueDate.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/property/ExecutionListener.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/property/ListenerParam.vue
ÎļþÒÑɾ³ý
src/bpmn/panel/property/TaskListener.vue
ÎļþÒÑɾ³ý
src/components/BpmnDesign/index.vue
ÎļþÒÑɾ³ý
src/components/BpmnView/index.vue
ÎļþÒÑɾ³ý
src/components/Breadcrumb/index.vue
@@ -11,21 +11,53 @@
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router';
import usePermissionStore from '@/store/modules/permission';
const route = useRoute();
const router = useRouter();
const permissionStore = usePermissionStore();
const levelList = ref<RouteLocationMatched[]>([]);
const getBreadcrumb = () => {
  // only show routes with meta.title
  let matched = route.matched.filter((item) => item.meta && item.meta.title);
  const first = matched[0];
  let matched = [];
  const pathNum = findPathNum(route.path);
  // multi-level menu
  if (pathNum > 2) {
    const reg = /\/\w+/gi;
    const pathList = route.path.match(reg).map((item, index) => {
      if (index !== 0) item = item.slice(1);
      return item;
    });
    getMatched(pathList, permissionStore.defaultRoutes, matched);
  } else {
    matched = route.matched.filter((item) => item.meta && item.meta.title);
  }
  // åˆ¤æ–­æ˜¯å¦ä¸ºé¦–页
  if (!isDashboard(first)) {
    matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched);
  if (!isDashboard(matched[0])) {
    matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
  }
  levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
};
const findPathNum = (str, char = '/') => {
  let index = str.indexOf(char);
  let num = 0;
  while (index !== -1) {
    num++;
    index = str.indexOf(char, index + 1);
  }
  return num;
};
const getMatched = (pathList, routeList, matched) => {
  let data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
  if (data) {
    matched.push(data);
    if (data.children && pathList.length) {
      pathList.shift();
      getMatched(pathList, data.children, matched);
    }
  }
};
const isDashboard = (route: RouteLocationMatched) => {
  const name = route && (route.name as string);
  if (!name) {
src/components/FileUpload/index.vue
@@ -121,6 +121,11 @@
      return false;
    }
  }
  // æ ¡æ£€æ–‡ä»¶åæ˜¯å¦åŒ…含特殊字符
  if (file.name.includes(',')) {
    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
    return false;
  }
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
src/components/ImageUpload/index.vue
@@ -139,6 +139,10 @@
    proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join('/')}图片格式文件!`);
    return false;
  }
  if (file.name.includes(',')) {
    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
    return false;
  }
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
src/components/Process/approvalRecord.vue
@@ -2,39 +2,63 @@
  <div class="container">
    <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 label="流程图" name="bpmn">
          <BpmnView ref="bpmnViewRef"></BpmnView>
        <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>
        </el-tab-pane>
        <el-tab-pane v-loading="loading" label="审批信息" name="info">
          <div>
            <el-table :data="historyList" style="width: 100%" border fit>
              <el-table-column type="index" label="序号" align="center" width="60"></el-table-column>
              <el-table-column prop="name" label="任务名称" sortable align="center"></el-table-column>
              <el-table-column prop="nickName" :show-overflow-tooltip="true" label="办理人" sortable align="center">
              <el-table-column prop="nodeName" label="任务名称" sortable align="center"></el-table-column>
              <el-table-column prop="approveName" :show-overflow-tooltip="true" label="办理人" sortable align="center">
                <template #default="scope">
                  <el-tag type="success">{{ scope.row.nickName || '无' }}</el-tag>
                  <template v-if="scope.row.approveName">
                    <el-tag v-for="(item, index) in scope.row.approveName.split(',')" :key="index" type="success">{{ item }}</el-tag>
                  </template>
                  <template v-else> <el-tag type="success">无</el-tag></template>
                </template>
              </el-table-column>
              <el-table-column label="状态" sortable align="center">
              <el-table-column prop="flowStatus" label="状态" width="80" sortable align="center">
                <template #default="scope">
                  <el-tag type="success">{{ scope.row.statusName }}</el-tag>
                  <dict-tag :options="wf_task_status" :value="scope.row.flowStatus"></dict-tag>
                </template>
              </el-table-column>
              <el-table-column prop="comment" label="审批意见" sortable align="center"></el-table-column>
              <el-table-column prop="startTime" label="开始时间" sortable align="center"></el-table-column>
              <el-table-column prop="endTime" label="结束时间" sortable align="center"></el-table-column>
              <el-table-column prop="runDuration" label="运行时长" sortable align="center"></el-table-column>
              <el-table-column prop="attachmentList" label="附件" sortable align="center">
              <el-table-column prop="message" label="审批意见" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
              <el-table-column prop="createTime" label="开始时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
              <el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
              <el-table-column
                prop="runDuration"
                label="运行时常"
                width="140"
                :show-overflow-tooltip="true"
                sortable
                align="center"
              ></el-table-column>
              <el-table-column prop="attachmentList" width="120" label="附件" align="center">
                <template #default="scope">
                  <el-popover v-if="scope.row.attachmentList && scope.row.attachmentList.length > 0" placement="right" :width="310" trigger="click">
                    <template #reference>
                      <el-button style="margin-right: 16px">附件</el-button>
                      <el-button type="primary" style="margin-right: 16px">附件</el-button>
                    </template>
                    <el-table border :data="scope.row.attachmentList">
                      <el-table-column prop="name" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column>
                      <el-table-column prop="originalName" width="202" :show-overflow-tooltip="true" label="附件名称"></el-table-column>
                      <el-table-column prop="name" width="80" align="center" :show-overflow-tooltip="true" label="操作">
                        <template #default="tool">
                          <el-button type="text" @click="handleDownload(tool.row.contentId)">下载</el-button>
                          <el-button type="text" @click="handleDownload(tool.row.ossId)">下载</el-button>
                        </template>
                      </el-table-column>
                    </el-table>
@@ -49,42 +73,160 @@
  </div>
</template>
<script lang="ts" setup>
import BpmnView from '@/components/BpmnView/index.vue';
import processApi from '@/api/workflow/processInstance';
import { flowImage } from '@/api/workflow/instance';
import { propTypes } from '@/utils/propTypes';
import { listByIds } from '@/api/system/oss';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
const props = defineProps({
  width: propTypes.string.def('70%'),
  width: propTypes.string.def('80%'),
  height: propTypes.string.def('100%')
});
const loading = ref(false);
const visible = ref(false);
const historyList = ref<Array<any>>([]);
const tabActiveName = ref('bpmn');
const bpmnViewRef = ref<BpmnView>();
const tabActiveName = ref('image');
const imgUrl = ref('');
//初始化查询审批记录
const init = async (businessKey: string | number) => {
const init = async (businessId: string | number) => {
  visible.value = true;
  loading.value = true;
  tabActiveName.value = 'bpmn';
  tabActiveName.value = 'image';
  historyList.value = [];
  processApi.getHistoryRecord(businessKey).then((resp) => {
    historyList.value = resp.data;
    loading.value = false;
  flowImage(businessId).then((resp) => {
    if (resp.data) {
      historyList.value = resp.data.list;
      imgUrl.value = 'data:image/gif;base64,' + resp.data.image;
      if (historyList.value.length > 0) {
        historyList.value.forEach((item) => {
          if (item.ext) {
            getIds(item.ext).then((res) => {
              item.attachmentList = res.data;
            });
          } else {
            item.attachmentList = [];
          }
        });
      }
      loading.value = false;
    }
  });
  await nextTick(() => {
    bpmnViewRef.value.init(businessKey);
  });
};
const getIds = async (ids: string | number) => {
  const res = await listByIds(ids);
  return res;
};
/** ä¸‹è½½æŒ‰é’®æ“ä½œ */
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'
}));
/**
 * å¯¹å¤–暴露子组件方法
 */
@@ -113,4 +255,26 @@
    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>
src/components/Process/multiInstanceUser.vue
ÎļþÒÑɾ³ý
src/components/Process/processMeddle.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,207 @@
<template>
  <el-dialog v-model="visible" draggable title="流程干预" :width="props.width" :height="props.height" :close-on-click-modal="false">
    <el-descriptions v-loading="loading" class="margin-top" :title="`${task.flowName}(${task.flowCode})`" :column="2" border>
      <el-descriptions-item label="任务名称">{{ task.nodeName }}</el-descriptions-item>
      <el-descriptions-item label="节点编码">{{ task.nodeCode }}</el-descriptions-item>
      <el-descriptions-item label="开始时间">{{ task.createTime }}</el-descriptions-item>
      <el-descriptions-item label="流程实例ID">{{ task.instanceId }}</el-descriptions-item>
      <el-descriptions-item label="版本号">{{ task.version }}.0</el-descriptions-item>
      <el-descriptions-item label="业务ID">{{ task.businessId }}</el-descriptions-item>
    </el-descriptions>
    <template #footer>
      <span class="dialog-footer">
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> è½¬åŠž </el-button>
        <el-button
          v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
          :disabled="buttonDisabled"
          type="primary"
          @click="openMultiInstanceUser"
        >
          åŠ ç­¾
        </el-button>
        <el-button
          v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
          :disabled="buttonDisabled"
          type="primary"
          @click="handleTaskUser"
        >
          å‡ç­¾
        </el-button>
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> ç»ˆæ­¢ </el-button>
      </span>
    </template>
    <!-- è½¬åŠž -->
    <UserSelect ref="transferTaskRef" :multiple="false" @confirm-call-back="handleTransferTask"></UserSelect>
    <!-- åŠ ç­¾ç»„ä»¶ -->
    <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
    <el-dialog v-model="deleteSignatureVisible" draggable title="减签人员" width="700px" height="400px" append-to-body :close-on-click-modal="false"
      ><div>
        <el-table :data="deleteUserList" border>
          <el-table-column prop="nodeName" label="任务名称" />
          <el-table-column prop="nickName" label="办理人" />
          <el-table-column label="操作" align="center" width="160">
            <template #default="scope">
              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </el-dialog>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes';
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
import UserSelect from '@/components/UserSelect';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { getTask, taskOperation, currentTaskAllUser, terminationTask } from '@/api/workflow/task';
const props = defineProps({
  width: propTypes.string.def('50%'),
  height: propTypes.string.def('100%')
});
const emits = defineEmits(['submitCallback']);
const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
const multiInstanceUserRef = ref<InstanceType<typeof UserSelect>>();
//遮罩层
const loading = ref(true);
//按钮
const buttonDisabled = ref(true);
const visible = ref(false);
//减签弹窗
const deleteSignatureVisible = ref(false);
//可减签的人员
const deleteUserList = ref<any>([]);
//任务
const task = ref<FlowTaskVO>({
  id: undefined,
  createTime: undefined,
  updateTime: undefined,
  tenantId: undefined,
  definitionId: undefined,
  instanceId: undefined,
  flowName: undefined,
  businessId: undefined,
  nodeCode: undefined,
  nodeName: undefined,
  flowCode: undefined,
  flowStatus: undefined,
  nodeType: undefined,
  nodeRatio: undefined,
  version: undefined
});
const open = (taskId: string) => {
  visible.value = true;
  getTask(taskId).then((response) => {
    loading.value = false;
    buttonDisabled.value = false;
    task.value = response.data;
  });
};
//打开转办
const openTransferTask = () => {
  transferTaskRef.value.open();
};
//转办
const handleTransferTask = async (data) => {
  if (data && data.length > 0) {
    const taskOperationBo = reactive<TaskOperationBo>({
      userId: data[0].userId,
      taskId: task.value.id,
      message: ''
    });
    await proxy?.$modal.confirm('是否确认提交?');
    loading.value = true;
    buttonDisabled.value = true;
    await taskOperation(taskOperationBo, 'transferTask').finally(() => {
      loading.value = false;
      buttonDisabled.value = false;
    });
    visible.value = false;
    emits('submitCallback');
    proxy?.$modal.msgSuccess('操作成功');
  } else {
    proxy?.$modal.msgWarning('请选择用户!');
  }
};
//加签
const openMultiInstanceUser = async () => {
  multiInstanceUserRef.value.open();
};
//加签
const addMultiInstanceUser = async (data) => {
  if (data && data.length > 0) {
    const taskOperationBo = reactive<TaskOperationBo>({
      userIds: data.map((e) => e.userId),
      taskId: task.value.id,
      message: ''
    });
    await proxy?.$modal.confirm('是否确认提交?');
    loading.value = true;
    buttonDisabled.value = true;
    await taskOperation(taskOperationBo, 'addSignature').finally(() => {
      loading.value = false;
      buttonDisabled.value = false;
    });
    visible.value = false;
    emits('submitCallback');
    proxy?.$modal.msgSuccess('操作成功');
  } else {
    proxy?.$modal.msgWarning('请选择用户!');
  }
};
//减签
const deleteMultiInstanceUser = async (row) => {
  await proxy?.$modal.confirm('是否确认提交?');
  loading.value = true;
  buttonDisabled.value = true;
  const taskOperationBo = reactive<TaskOperationBo>({
    userIds: [row.userId],
    taskId: task.value.id,
    message: ''
  });
  await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
    loading.value = false;
    buttonDisabled.value = false;
  });
  visible.value = false;
  emits('submitCallback');
  proxy?.$modal.msgSuccess('操作成功');
};
//获取办理人
const handleTaskUser = async () => {
  let data = await currentTaskAllUser(task.value.id);
  deleteUserList.value = data.data;
  if (deleteUserList.value && deleteUserList.value.length > 0) {
    deleteUserList.value.forEach((e) => {
      e.nodeName = task.value.nodeName;
    });
  }
  deleteSignatureVisible.value = true;
};
//终止任务
const handleTerminationTask = async () => {
  let params = {
    taskId: task.value.id,
    comment: ''
  };
  await proxy?.$modal.confirm('是否确认终止?');
  loading.value = true;
  buttonDisabled.value = true;
  await terminationTask(params).finally(() => {
    loading.value = false;
    buttonDisabled.value = false;
  });
  visible.value = false;
  emits('submitCallback');
  proxy?.$modal.msgSuccess('操作成功');
};
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  open
});
</script>
src/components/Process/submitVerify.vue
@@ -3,47 +3,47 @@
    <el-form v-loading="loading" :model="form" label-width="120px">
      <el-form-item label="消息提醒">
        <el-checkbox-group v-model="form.messageType">
          <el-checkbox label="1" name="type" disabled>站内信</el-checkbox>
          <el-checkbox label="2" name="type">邮件</el-checkbox>
          <el-checkbox label="3" name="type">短信</el-checkbox>
          <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 v-if="task.businessStatus === 'waiting'" label="附件">
        <fileUpload v-model="form.fileId" :file-type="['doc', 'xls', 'ppt', 'txt', 'pdf', 'xlsx', 'docx', 'zip']" :file-size="'20'" />
      <el-form-item v-if="task.flowStatus === 'waiting'" 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="抄送">
        <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.userName }}
          {{ user.nickName }}
        </el-tag>
      </el-form-item>
      <el-form-item v-if="task.businessStatus === 'waiting'" label="审批意见">
      <el-form-item v-if="task.flowStatus === 'waiting'" label="审批意见">
        <el-input v-model="form.message" type="textarea" resize="none" />
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask"> æäº¤ </el-button>
        <el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openDelegateTask"> å§”托 </el-button>
        <el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> è½¬åŠž </el-button>
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openDelegateTask"> å§”托 </el-button>
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> è½¬åŠž </el-button>
        <el-button
          v-if="task.businessStatus === 'waiting' && task.multiInstance"
          v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
          :disabled="buttonDisabled"
          type="primary"
          @click="addMultiInstanceUser"
          @click="openMultiInstanceUser"
        >
          åŠ ç­¾
        </el-button>
        <el-button
          v-if="task.businessStatus === 'waiting' && task.multiInstance"
          v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
          :disabled="buttonDisabled"
          type="primary"
          @click="deleteMultiInstanceUser"
          @click="handleTaskUser"
        >
          å‡ç­¾
        </el-button>
        <el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> ç»ˆæ­¢ </el-button>
        <el-button v-if="task.businessStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen"> é€€å›ž </el-button>
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> ç»ˆæ­¢ </el-button>
        <el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen"> é€€å›ž </el-button>
        <el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
      </span>
    </template>
@@ -54,14 +54,14 @@
    <!-- å§”托 -->
    <UserSelect ref="delegateTaskRef" :multiple="false" @confirm-call-back="handleDelegateTask"></UserSelect>
    <!-- åŠ ç­¾ç»„ä»¶ -->
    <multiInstanceUser ref="multiInstanceUserRef" :title="title" @submit-callback="closeDialog" />
    <UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
    <!-- é©³å›žå¼€å§‹ -->
    <el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
      <el-form v-if="task.businessStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px">
      <el-form v-if="task.flowStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px">
        <el-form-item label="驳回节点">
          <el-select v-model="backForm.targetActivityId" clearable placeholder="请选择" style="width: 300px">
            <el-option v-for="item in taskNodeList" :key="item.nodeId" :label="item.nodeName" :value="item.nodeId" />
          <el-select v-model="backForm.nodeCode" clearable placeholder="请选择" style="width: 300px">
            <el-option v-for="item in taskNodeList" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode" />
          </el-select>
        </el-form-item>
        <el-form-item label="消息提醒">
@@ -83,6 +83,19 @@
      </template>
    </el-dialog>
    <!-- é©³å›žç»“束 -->
    <el-dialog v-model="deleteSignatureVisible" draggable title="减签人员" width="700px" height="400px" append-to-body :close-on-click-modal="false">
      <div>
        <el-table :data="deleteUserList" border>
          <el-table-column prop="nodeName" label="任务名称" />
          <el-table-column prop="nickName" label="办理人" />
          <el-table-column label="操作" align="center" width="160">
            <template #default="scope">
              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">删除 </el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </el-dialog>
</template>
@@ -90,18 +103,17 @@
import { ref } from 'vue';
import { ComponentInternalInstance } from 'vue';
import { ElForm } from 'element-plus';
import { completeTask, backProcess, getTaskById, transferTask, terminationTask, getTaskNodeList, delegateTask } from '@/api/workflow/task';
import { completeTask, backProcess, getTask, taskOperation, terminationTask, getBackTaskNode, currentTaskAllUser } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect';
import MultiInstanceUser from '@/components/Process/multiInstanceUser.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { UserVO } from '@/api/system/user/types';
import { TaskVO } from '@/api/workflow/task/types';
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
const delegateTaskRef = ref<InstanceType<typeof UserSelect>>();
//加签组件
const multiInstanceUserRef = ref<InstanceType<typeof MultiInstanceUser>>();
const multiInstanceUserRef = ref<InstanceType<typeof UserSelect>>();
const props = defineProps({
  taskVariables: {
@@ -119,65 +131,53 @@
const selectCopyUserList = ref<UserVO[]>([]);
//抄送人id
const selectCopyUserIds = ref<string>(undefined);
// é©³å›žæ˜¯å¦æ˜¾ç¤º
//可减签的人员
const deleteUserList = ref<any>([]);
//驳回是否显示
const backVisible = ref(false);
const backLoading = ref(true);
const backButtonDisabled = ref(true);
// å¯é©³å›žå¾—任务节点
const taskNodeList = ref([]);
//任务
const task = ref<TaskVO>({
const task = ref<FlowTaskVO>({
  id: undefined,
  name: undefined,
  description: undefined,
  priority: undefined,
  owner: undefined,
  assignee: undefined,
  assigneeName: undefined,
  processInstanceId: undefined,
  executionId: undefined,
  taskDefinitionId: undefined,
  processDefinitionId: undefined,
  endTime: undefined,
  taskDefinitionKey: undefined,
  dueDate: undefined,
  category: undefined,
  parentTaskId: undefined,
  createTime: undefined,
  updateTime: undefined,
  tenantId: undefined,
  claimTime: undefined,
  businessStatus: undefined,
  businessStatusName: undefined,
  processDefinitionName: undefined,
  processDefinitionKey: undefined,
  participantVo: undefined,
  multiInstance: undefined,
  businessKey: undefined,
  wfNodeConfigVo: undefined
  definitionId: undefined,
  instanceId: undefined,
  flowName: undefined,
  businessId: undefined,
  nodeCode: undefined,
  nodeName: undefined,
  flowCode: undefined,
  flowStatus: undefined,
  formCustom: undefined,
  formPath: undefined,
  nodeType: undefined,
  nodeRatio: undefined
});
//加签 å‡ç­¾æ ‡é¢˜
const title = ref('');
const dialog = reactive<DialogOption>({
  visible: false,
  title: '提示'
});
//减签弹窗
const deleteSignatureVisible = ref(false);
const form = ref<Record<string, any>>({
  taskId: undefined,
  message: undefined,
  variables: {},
  messageType: ['1'],
  wfCopyList: []
  flowCopyList: []
});
const backForm = ref<Record<string, any>>({
  taskId: undefined,
  targetActivityId: undefined,
  nodeCode: undefined,
  message: undefined,
  variables: {},
  messageType: ['1']
});
const closeDialog = () => {
  dialog.visible = false;
};
//打开弹窗
const openDialog = (id?: string) => {
  selectCopyUserIds.value = undefined;
@@ -189,7 +189,7 @@
  loading.value = true;
  buttonDisabled.value = true;
  nextTick(() => {
    getTaskById(taskId.value).then((response) => {
    getTask(taskId.value).then((response) => {
      task.value = response.data;
      loading.value = false;
      buttonDisabled.value = false;
@@ -205,15 +205,15 @@
  form.value.taskId = taskId.value;
  form.value.taskVariables = props.taskVariables;
  if (selectCopyUserList.value && selectCopyUserList.value.length > 0) {
    let wfCopyList = [];
    let flowCopyList = [];
    selectCopyUserList.value.forEach((e) => {
      let copyUser = {
        userId: e.userId,
        userName: e.nickName
      };
      wfCopyList.push(copyUser);
      flowCopyList.push(copyUser);
    });
    form.value.wfCopyList = wfCopyList;
    form.value.flowCopyList = flowCopyList;
  }
  await proxy?.$modal.confirm('是否确认提交?');
  loading.value = true;
@@ -236,11 +236,11 @@
  backVisible.value = true;
  backLoading.value = true;
  backButtonDisabled.value = true;
  let data = await getTaskNodeList(task.value.processInstanceId);
  let data = await getBackTaskNode(task.value.definitionId, task.value.nodeCode);
  taskNodeList.value = data.data;
  backLoading.value = false;
  backButtonDisabled.value = false;
  backForm.value.targetActivityId = taskNodeList.value[0].nodeId;
  backForm.value.nodeCode = taskNodeList.value[0].nodeCode;
};
/** é©³å›žæµç¨‹ */
const handleBackProcess = async () => {
@@ -249,7 +249,10 @@
  loading.value = true;
  backLoading.value = true;
  backButtonDisabled.value = true;
  await backProcess(backForm.value).finally(() => (loading.value = false));
  await backProcess(backForm.value).finally(() => {
    loading.value = false;
    buttonDisabled.value = false;
  });
  dialog.visible = false;
  backLoading.value = false;
  backButtonDisabled.value = false;
@@ -282,18 +285,48 @@
  selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
};
//加签
const addMultiInstanceUser = () => {
  if (multiInstanceUserRef.value) {
    title.value = '加签人员';
    multiInstanceUserRef.value.getAddMultiInstanceList(taskId.value, []);
const openMultiInstanceUser = async () => {
  multiInstanceUserRef.value.open();
};
//加签
const addMultiInstanceUser = async (data) => {
  if (data && data.length > 0) {
    const taskOperationBo = reactive<TaskOperationBo>({
      userIds: data.map((e) => e.userId),
      taskId: taskId.value,
      message: form.value.message
    });
    await proxy?.$modal.confirm('是否确认提交?');
    loading.value = true;
    buttonDisabled.value = true;
    await taskOperation(taskOperationBo, 'addSignature').finally(() => {
      loading.value = false;
      buttonDisabled.value = false;
    });
    dialog.visible = false;
    emits('submitCallback');
    proxy?.$modal.msgSuccess('操作成功');
  } else {
    proxy?.$modal.msgWarning('请选择用户!');
  }
};
//减签
const deleteMultiInstanceUser = () => {
  if (multiInstanceUserRef.value) {
    title.value = '减签人员';
    multiInstanceUserRef.value.getDeleteMultiInstanceList(taskId.value);
  }
const deleteMultiInstanceUser = async (row) => {
  await proxy?.$modal.confirm('是否确认提交?');
  loading.value = true;
  buttonDisabled.value = true;
  const taskOperationBo = reactive<TaskOperationBo>({
    userIds: [row.userId],
    taskId: taskId.value,
    message: form.value.message
  });
  await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
    loading.value = false;
    buttonDisabled.value = false;
  });
  dialog.visible = false;
  emits('submitCallback');
  proxy?.$modal.msgSuccess('操作成功');
};
//打开转办
const openTransferTask = () => {
@@ -302,15 +335,18 @@
//转办
const handleTransferTask = async (data) => {
  if (data && data.length > 0) {
    let params = {
      taskId: taskId.value,
    const taskOperationBo = reactive<TaskOperationBo>({
      userId: data[0].userId,
      comment: form.value.message
    };
      taskId: taskId.value,
      message: form.value.message
    });
    await proxy?.$modal.confirm('是否确认提交?');
    loading.value = true;
    buttonDisabled.value = true;
    await transferTask(params).finally(() => (loading.value = false));
    await taskOperation(taskOperationBo, 'transferTask').finally(() => {
      loading.value = false;
      buttonDisabled.value = false;
    });
    dialog.visible = false;
    emits('submitCallback');
    proxy?.$modal.msgSuccess('操作成功');
@@ -326,15 +362,18 @@
//委托
const handleDelegateTask = async (data) => {
  if (data && data.length > 0) {
    let params = {
      taskId: taskId.value,
    const taskOperationBo = reactive<TaskOperationBo>({
      userId: data[0].userId,
      nickName: data[0].nickName
    };
      taskId: taskId.value,
      message: form.value.message
    });
    await proxy?.$modal.confirm('是否确认提交?');
    loading.value = true;
    buttonDisabled.value = true;
    await delegateTask(params).finally(() => (loading.value = false));
    await taskOperation(taskOperationBo, 'delegateTask').finally(() => {
      loading.value = false;
      buttonDisabled.value = false;
    });
    dialog.visible = false;
    emits('submitCallback');
    proxy?.$modal.msgSuccess('操作成功');
@@ -343,7 +382,7 @@
  }
};
//终止任务
const handleTerminationTask = async (data) => {
const handleTerminationTask = async () => {
  let params = {
    taskId: taskId.value,
    comment: form.value.message
@@ -351,11 +390,24 @@
  await proxy?.$modal.confirm('是否确认终止?');
  loading.value = true;
  buttonDisabled.value = true;
  await terminationTask(params).finally(() => (loading.value = false));
  await terminationTask(params).finally(() => {
    loading.value = false;
    buttonDisabled.value = false;
  });
  dialog.visible = false;
  emits('submitCallback');
  proxy?.$modal.msgSuccess('操作成功');
};
const handleTaskUser = async () => {
  let data = await currentTaskAllUser(taskId.value);
  deleteUserList.value = data.data;
  if (deleteUserList.value && deleteUserList.value.length > 0) {
    deleteUserList.value.forEach((e) => {
      e.nodeName = task.value.nodeName;
    });
  }
  deleteSignatureVisible.value = true;
};
/**
 * å¯¹å¤–暴露子组件方法
src/components/RoleSelect/index.vue
@@ -53,7 +53,7 @@
          </vxe-column>
          <vxe-column field="createTime" title="创建时间" align="center">
            <template #default="scope">
              <span>{{ parseTime(scope.row.createTime) }}</span>
              <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
            </template>
          </vxe-column>
        </vxe-table>
src/components/TopNav/index.vue
@@ -91,8 +91,8 @@
  let activePath = path;
  if (path !== undefined && path.lastIndexOf('/') > 0 && hideList.indexOf(path) === -1) {
    const tmpPath = path.substring(1, path.length);
    activePath = '/' + tmpPath.substring(0, tmpPath.indexOf('/'));
    if (!route.meta.link) {
      activePath = '/' + tmpPath.substring(0, tmpPath.indexOf('/'));
      appStore.toggleSideBarHide(false);
    }
  } else if (!route.children) {
src/components/UserSelect/index.vue
@@ -43,7 +43,7 @@
          <el-card shadow="hover">
            <template v-if="prop.multiple" #header>
              <el-tag v-for="user in selectUserList" :key="user.userId" closable style="margin: 2px" @close="handleCloseTag(user)">
                {{ user.userName }}
                {{ user.nickName }}
              </el-tag>
            </template>
@@ -100,14 +100,14 @@
<script setup lang="ts">
import api from '@/api/system/user';
import { UserQuery, UserVO } from '@/api/system/user/types';
import { DeptVO } from '@/api/system/dept/types';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { VxeTableInstance } from 'vxe-table';
import useDialog from '@/hooks/useDialog';
interface PropType {
  modelValue?: UserVO[] | UserVO | undefined;
  multiple?: boolean;
  data?: string | number | (string | number)[];
  data?: string | number | (string | number)[] | undefined;
}
const prop = withDefaults(defineProps<PropType>(), {
  multiple: true,
@@ -125,7 +125,7 @@
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const deptOptions = ref<DeptTreeVO[]>([]);
const selectUserList = ref<UserVO[]>([]);
const deptTreeRef = ref<ElTreeInstance>();
@@ -166,7 +166,7 @@
const computedIds = (data) => {
  if (data instanceof Array) {
    return [...data];
    return data.map(item => String(item));
  } else if (typeof data === 'string') {
    return data.split(',');
  } else if (typeof data === 'number') {
src/directive/permission/index.ts
@@ -31,7 +31,7 @@
    const { roles } = useUserStore();
    if (value && value instanceof Array && value.length > 0) {
      const hasRole = roles.some((role: string) => {
        return role === 'admin' || value.includes(role);
        return role === 'superadmin' || role === 'admin' || value.includes(role);
      });
      if (!hasRole) {
        el.parentNode && el.parentNode.removeChild(el);
src/enums/SettingTypeEnum.ts
ÎļþÒÑɾ³ý
src/enums/bpmn/IndexEnums.ts
ÎļþÒÑɾ³ý
src/enums/layout/LayoutEnum.ts
ÎļþÒÑɾ³ý
src/lang/en_US.json
ÎļþÒÑɾ³ý
src/lang/en_US.ts
@@ -6,11 +6,68 @@
  },
  // ç™»å½•页面国际化
  login: {
    selectPlaceholder: 'Please select/enter a company name',
    username: 'Username',
    password: 'Password',
    login: 'Login',
    logging: 'Logging...',
    code: 'Verification Code',
    copyright: ''
    rememberPassword: 'Remember me',
    switchRegisterPage: 'Sign up now',
    rule: {
      tenantId: {
        required: 'Please enter your tenant id'
      },
      username: {
        required: 'Please enter your account'
      },
      password: {
        required: 'Please enter your password'
      },
      code: {
        required: 'Please enter a verification code'
      }
    },
    social: {
      wechat: 'Wechat Login',
      maxkey: 'MaxKey Login',
      topiam: 'TopIam Login',
      gitee: 'Gitee Login',
      github: 'Github Login'
    }
  },
  // æ³¨å†Œé¡µé¢å›½é™…化
  register: {
    selectPlaceholder: 'Please select/enter a company name',
    username: 'Username',
    password: 'Password',
    confirmPassword: 'Confirm Password',
    register: 'Register',
    registering: 'Registering...',
    registerSuccess: 'Congratulations, your {username} account has been registered!',
    code: 'Verification Code',
    switchLoginPage: 'Log in with an existing account',
    rule: {
      tenantId: {
        required: 'Please enter your tenant id'
      },
      username: {
        required: 'Please enter your account',
        length: 'The length of the user account must be between {min} and {max}'
      },
      password: {
        required: 'Please enter your password',
        length: 'The user password must be between {min} and {max} in length',
        pattern: "Can't contain illegal characters: {strings}"
      },
      code: {
        required: 'Please enter a verification code'
      },
      confirmPassword: {
        required: 'Please enter your password again',
        equalToPassword: 'The password entered twice is inconsistent'
      }
    }
  },
  // å¯¼èˆªæ å›½é™…化
  navbar: {
src/lang/index.ts
@@ -2,7 +2,8 @@
import { createI18n } from 'vue-i18n';
import { LanguageEnum } from '@/enums/LanguageEnum';
import messages from '@intlify/unplugin-vue-i18n/messages';
import zh_CN from '@/lang/zh_CN';
import en_US from '@/lang/en_US';
/**
 * èŽ·å–å½“å‰è¯­è¨€
@@ -21,7 +22,12 @@
  allowComposition: true,
  legacy: false,
  locale: getLanguage(),
  messages
  messages: {
    zh_CN: zh_CN,
    en_US: en_US
  }
});
export default i18n;
export type LanguageType = typeof zh_CN;
src/lang/zh_CN.json
ÎļþÒÑɾ³ý
src/lang/zh_CN.ts
@@ -6,12 +6,70 @@
  },
  // ç™»å½•页面国际化
  login: {
    selectPlaceholder: '请选择/输入公司名称',
    username: '用户名',
    password: '密码',
    login: '登 å½•',
    code: '请输入验证码',
    copyright: ''
    logging: '登 å½• ä¸­...',
    code: '验证码',
    rememberPassword: '记住我',
    switchRegisterPage: '立即注册',
    rule: {
      tenantId: {
        required: '请输入您的租户编号'
      },
      username: {
        required: '请输入您的账号'
      },
      password: {
        required: '请输入您的密码'
      },
      code: {
        required: '请输入验证码'
      }
    },
    social: {
      wechat: '微信登录',
      maxkey: 'MaxKey登录',
      topiam: 'TopIam登录',
      gitee: 'Gitee登录',
      github: 'Github登录'
    }
  },
  // æ³¨å†Œé¡µé¢å›½é™…化
  register: {
    selectPlaceholder: '请选择/输入公司名称',
    username: '用户名',
    password: '密码',
    confirmPassword: '确认密码',
    register: '注 å†Œ',
    registering: '注 å†Œ ä¸­...',
    registerSuccess: '恭喜你,您的账号 {username} æ³¨å†ŒæˆåŠŸï¼',
    code: '验证码',
    switchLoginPage: '使用已有账户登录',
    rule: {
      tenantId: {
        required: '请输入您的租户编号'
      },
      username: {
        required: '请输入您的账号',
        length: '用户账号长度必须介于 {min} å’Œ {max} ä¹‹é—´'
      },
      password: {
        required: '请输入您的密码',
        length: '用户密码长度必须介于 {min} å’Œ {max} ä¹‹é—´',
        pattern: '不能包含非法字符:{strings}'
      },
      code: {
        required: '请输入验证码'
      },
      confirmPassword: {
        required: '请再次输入您的密码',
        equalToPassword: '两次输入的密码不一致'
      }
    }
  },
  // å¯¼èˆªæ å›½é™…化
  navbar: {
    full: '全屏',
    language: '语言',
src/layout/components/AppMain.vue
@@ -20,6 +20,7 @@
import IframeToggle from './IframeToggle/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const tagsViewStore = useTagsViewStore();
// éšæœºåŠ¨ç”»é›†åˆ
@@ -37,6 +38,20 @@
  },
  { immediate: true }
);
onMounted(() => {
  addIframe()
})
watchEffect((route) => {
  addIframe()
})
function addIframe() {
  if (route.meta.link) {
    useTagsViewStore().addIframeView(route)
  }
}
</script>
<style lang="scss" scoped>
src/layout/components/Navbar.vue
@@ -13,7 +13,7 @@
          clearable
          filterable
          reserve-keyword
          :placeholder="$t('navbar.selectTenant')"
          :placeholder="proxy.$t('navbar.selectTenant')"
          @change="dynamicTenantEvent"
          @clear="dynamicClearEvent"
        >
@@ -29,7 +29,7 @@
          </div>
        </el-tooltip>
        <!-- æ¶ˆæ¯ -->
        <el-tooltip :content="$t('navbar.message')" effect="dark" placement="bottom">
        <el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
          <div>
            <el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
              <template #reference>
@@ -47,19 +47,19 @@
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip :content="$t('navbar.document')" effect="dark" placement="bottom">
        <el-tooltip :content="proxy.$t('navbar.document')" effect="dark" placement="bottom">
          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip :content="$t('navbar.full')" effect="dark" placement="bottom">
        <el-tooltip :content="proxy.$t('navbar.full')" effect="dark" placement="bottom">
          <screenfull id="screenfull" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip :content="$t('navbar.language')" effect="dark" placement="bottom">
        <el-tooltip :content="proxy.$t('navbar.language')" effect="dark" placement="bottom">
          <lang-select id="lang-select" class="right-menu-item hover-effect" />
        </el-tooltip>
        <el-tooltip :content="$t('navbar.layoutSize')" effect="dark" placement="bottom">
        <el-tooltip :content="proxy.$t('navbar.layoutSize')" effect="dark" placement="bottom">
          <size-select id="size-select" class="right-menu-item hover-effect" />
        </el-tooltip>
      </template>
@@ -72,13 +72,13 @@
          <template #dropdown>
            <el-dropdown-menu>
              <router-link v-if="!dynamic" to="/user/profile">
                <el-dropdown-item>{{ $t('navbar.personalCenter') }}</el-dropdown-item>
                <el-dropdown-item>{{ proxy.$t('navbar.personalCenter') }}</el-dropdown-item>
              </router-link>
              <el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">
                <span>{{ $t('navbar.layoutSetting') }}</span>
                <span>{{ proxy.$t('navbar.layoutSetting') }}</span>
              </el-dropdown-item>
              <el-dropdown-item divided command="logout">
                <span>{{ $t('navbar.logout') }}</span>
                <span>{{ proxy.$t('navbar.logout') }}</span>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
@@ -98,6 +98,7 @@
import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
import { TenantVO } from '@/api/types';
import notice from './notice/index.vue';
import router from '@/router';
const appStore = useAppStore();
const userStore = useUserStore();
@@ -126,23 +127,23 @@
  if (companyName.value != null && companyName.value !== '') {
    await dynamicTenant(tenantId);
    dynamic.value = true;
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/');
    proxy?.$tab.refreshPage();
    await proxy?.$router.push('/');
    await proxy?.proxy.$tab.closeAllPage();
    await proxy?.proxy.$tab.refreshPage();
  }
};
const dynamicClearEvent = async () => {
  await dynamicClear();
  dynamic.value = false;
  proxy?.$tab.closeAllPage();
  proxy?.$router.push('/');
  proxy?.$tab.refreshPage();
  await proxy?.$router.push('/');
  await proxy?.proxy.$tab.closeAllPage();
  await proxy?.proxy.$tab.refreshPage();
};
/** ç§Ÿæˆ·åˆ—表 */
const initTenantList = async () => {
  const { data } = await getTenantList();
  const { data } = await getTenantList(true);
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
@@ -163,8 +164,14 @@
    cancelButtonText: '取消',
    type: 'warning'
  });
  await userStore.logout();
  location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
  userStore.logout().then(() => {
    router.replace({
      path: '/login',
      query: {
        redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
      }
    });
  });
};
const emits = defineEmits(['setLayout']);
src/layout/components/Sidebar/SidebarItem.vue
@@ -59,11 +59,9 @@
  const showingChildren = children.filter((item) => {
    if (item.hidden) {
      return false;
    } else {
      // Temp set(will be used if only has one showing child)
      onlyOneChild.value = item;
      return true;
    }
    onlyOneChild.value = item;
    return true;
  });
  // When there is only one child router, the child router is displayed by default
src/layout/components/TagsView/index.vue
@@ -70,8 +70,8 @@
const activeStyle = (tag: RouteLocationNormalized) => {
  if (!isActive(tag)) return {};
  return {
    'background-color': theme.value,
    'border-color': theme.value
    'background-color': 'var(--tags-view-active-bg)',
    'border-color': 'var(--tags-view-active-border-color)'
  };
};
const isAffix = (tag: RouteLocationNormalized) => {
@@ -135,11 +135,7 @@
  }
  if (name) {
    useTagsViewStore().addView(route as any);
    if (route.meta.link) {
      useTagsViewStore().addIframeView(route as any);
    }
  }
  return false;
};
const moveToCurrentTag = () => {
  nextTick(() => {
src/layout/components/TopBar/search.vue
@@ -66,7 +66,7 @@
  state.isShowSearch = false;
};
// èœå•搜索数据过滤
const menuSearch = (queryString: string, cb: Function) => {
const menuSearch = (queryString: string, cb: (options: any[]) => void) => {
  let options = state.menuList.filter((item) => {
    return item.title.indexOf(queryString) > -1;
  });
src/layout/index.vue
@@ -27,7 +27,7 @@
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import { initWebSocket } from '@/utils/websocket';
import { initSSE } from "@/utils/sse";
import { initSSE } from '@/utils/sse';
const settingsStore = useSettingsStore();
const theme = computed(() => settingsStore.theme);
src/permission.ts
@@ -3,14 +3,18 @@
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { getToken } from '@/utils/auth';
import { isHttp } from '@/utils/validate';
import { isHttp, isPathMatch } from '@/utils/validate';
import { isRelogin } from '@/utils/request';
import useUserStore from '@/store/modules/user';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/social-callback'];
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
const isWhiteList = (path: string) => {
  return whiteList.some(pattern => isPathMatch(pattern, path))
}
router.beforeEach(async (to, from, next) => {
  NProgress.start();
@@ -20,7 +24,7 @@
    if (to.path === '/login') {
      next({ path: '/' });
      NProgress.done();
    } else if (whiteList.indexOf(to.path as string) !== -1) {
    } else if (isWhiteList(to.path)) {
      next();
    } else {
      if (useUserStore().roles.length === 0) {
@@ -40,7 +44,7 @@
              router.addRoute(route); // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
            }
          });
          // @ts-ignore
          // @ts-expect-error hack方法 ç¡®ä¿addRoutes已完成
          next({ path: to.path, replace: true, params: to.params, query: to.query, hash: to.hash, name: to.name as string }); // hack方法 ç¡®ä¿addRoutes已完成
        }
      } else {
@@ -49,7 +53,7 @@
    }
  } else {
    // æ²¡æœ‰token
    if (whiteList.indexOf(to.path as string) !== -1) {
    if (isWhiteList(to.path)) {
      // åœ¨å…ç™»å½•白名单,直接进入
      next();
    } else {
src/plugins/cache.ts
@@ -26,6 +26,7 @@
    if (value != null) {
      return JSON.parse(value);
    }
    return null;
  },
  remove(key: string) {
    sessionStorage.removeItem(key);
@@ -59,6 +60,7 @@
    if (value != null) {
      return JSON.parse(value);
    }
    return null;
  },
  remove(key: string) {
    localStorage.removeItem(key);
src/plugins/tab.ts
@@ -1,5 +1,5 @@
import router from '@/router';
import {RouteLocationMatched, RouteLocationNormalized, RouteLocationRaw} from 'vue-router';
import { RouteLocationMatched, RouteLocationNormalized, RouteLocationRaw } from 'vue-router';
import useTagsViewStore from '@/store/modules/tagsView';
export default {
src/router/index.ts
@@ -103,7 +103,7 @@
        path: 'role/:userId(\\d+)',
        component: () => import('@/views/system/user/authRole.vue'),
        name: 'AuthRole',
        meta: { title: '分配角色', activeMenu: '/system/user', icon: '' }
        meta: { title: '分配角色', activeMenu: '/system/user', icon: '', noCache: true }
      }
    ]
  },
@@ -117,7 +117,7 @@
        path: 'user/:roleId(\\d+)',
        component: () => import('@/views/system/role/authUser.vue'),
        name: 'AuthUser',
        meta: { title: '分配用户', activeMenu: '/system/role', icon: '' }
        meta: { title: '分配用户', activeMenu: '/system/role', icon: '', noCache: true }
      }
    ]
  },
@@ -131,7 +131,7 @@
        path: 'index/:dictId(\\d+)',
        component: () => import('@/views/system/dict/data.vue'),
        name: 'Data',
        meta: { title: '字典数据', activeMenu: '/system/dict', icon: '' }
        meta: { title: '字典数据', activeMenu: '/system/dict', icon: '', noCache: true }
      }
    ]
  },
@@ -145,7 +145,7 @@
        path: 'index',
        component: () => import('@/views/system/oss/config.vue'),
        name: 'OssConfig',
        meta: { title: '配置管理', activeMenu: '/system/oss', icon: '' }
        meta: { title: '配置管理', activeMenu: '/system/oss', icon: '', noCache: true }
      }
    ]
  },
@@ -176,6 +176,20 @@
        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 }
      }
    ]
  }
];
@@ -189,9 +203,8 @@
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { top: 0 };
    }
    return { top: 0 };
  }
});
src/store/modules/dict.ts
@@ -1,29 +1,15 @@
export const useDictStore = defineStore('dict', () => {
  const dict = ref<
    Array<{
      key: string;
      value: DictDataOption[];
    }>
  >([]);
  const dict = ref<Map<string, DictDataOption[]>>(new Map());
  /**
   * èŽ·å–å­—å…¸
   * @param _key å­—å…¸key
   */
  const getDict = (_key: string): DictDataOption[] | null => {
    if (_key == null && _key == '') {
    if (!_key) {
      return null;
    }
    try {
      for (let i = 0; i < dict.value.length; i++) {
        if (dict.value[i].key == _key) {
          return dict.value[i].value;
        }
      }
    } catch (e) {
      return null;
    }
    return null;
    return dict.value.get(_key) || null;
  };
  /**
@@ -32,11 +18,15 @@
   * @param _value å­—å…¸value
   */
  const setDict = (_key: string, _value: DictDataOption[]) => {
    if (_key !== null && _key !== '') {
      dict.value.push({
        key: _key,
        value: _value
      });
    if (!_key) {
      return false;
    }
    try {
      dict.value.set(_key, _value);
      return true;
    } catch (e) {
      console.error('Error in setDict:', e);
      return false;
    }
  };
@@ -45,25 +35,22 @@
   * @param _key
   */
  const removeDict = (_key: string): boolean => {
    let bln = false;
    try {
      for (let i = 0; i < dict.value.length; i++) {
        if (dict.value[i].key == _key) {
          dict.value.splice(i, 1);
          return true;
        }
      }
    } catch (e) {
      bln = false;
    if (!_key) {
      return false;
    }
    return bln;
    try {
      return dict.value.delete(_key);
    } catch (e) {
      console.error('Error in removeDict:', e);
      return false;
    }
  };
  /**
   * æ¸…空字典
   */
  const cleanDict = (): void => {
    dict.value = [];
    dict.value.clear();
  };
  return {
src/store/modules/modeler.ts
ÎļþÒÑɾ³ý
src/store/modules/permission.ts
@@ -158,9 +158,12 @@
export const loadView = (view: any, name: string) => {
  let res;
  for (const path in modules) {
    const dir = path.split('views/')[1].split('.vue')[0];
    const viewsIndex = path.indexOf('/views/');
    let dir = path.substring(viewsIndex + 7);
    dir = dir.substring(0, dir.lastIndexOf('.vue'));
    if (dir === view) {
      res = createCustomNameComponent(modules[path], { name });
      return res;
    }
  }
  return res;
src/store/modules/tagsView.ts
@@ -31,7 +31,7 @@
  const delIframeView = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
    return new Promise((resolve) => {
      iframeViews.value = iframeViews.value.filter((item: RouteLocationNormalized) => item.path !== view.path);
      resolve([...iframeViews.value as RouteLocationNormalized[]]);
      resolve([...(iframeViews.value as RouteLocationNormalized[])]);
    });
  };
  const addVisitedView = (view: RouteLocationNormalized): void => {
@@ -54,7 +54,7 @@
        delCachedView(view);
      }
      resolve({
        visitedViews: [...visitedViews.value as RouteLocationNormalized[]],
        visitedViews: [...(visitedViews.value as RouteLocationNormalized[])],
        cachedViews: [...cachedViews.value]
      });
    });
@@ -68,7 +68,7 @@
          break;
        }
      }
      resolve([...visitedViews.value as RouteLocationNormalized[]]);
      resolve([...(visitedViews.value as RouteLocationNormalized[])]);
    });
  };
  const delCachedView = (view?: RouteLocationNormalized): Promise<string[]> => {
@@ -92,7 +92,7 @@
      delOthersVisitedViews(view);
      delOthersCachedViews(view);
      resolve({
        visitedViews: [...visitedViews.value as RouteLocationNormalized[]],
        visitedViews: [...(visitedViews.value as RouteLocationNormalized[])],
        cachedViews: [...cachedViews.value]
      });
    });
@@ -103,7 +103,7 @@
      visitedViews.value = visitedViews.value.filter((v: RouteLocationNormalized) => {
        return v.meta?.affix || v.path === view.path;
      });
      resolve([...visitedViews.value as RouteLocationNormalized[]]);
      resolve([...(visitedViews.value as RouteLocationNormalized[])]);
    });
  };
  const delOthersCachedViews = (view: RouteLocationNormalized): Promise<string[]> => {
@@ -124,7 +124,7 @@
      delAllVisitedViews();
      delAllCachedViews();
      resolve({
        visitedViews: [...visitedViews.value as RouteLocationNormalized[]],
        visitedViews: [...(visitedViews.value as RouteLocationNormalized[])],
        cachedViews: [...cachedViews.value]
      });
    });
@@ -132,7 +132,7 @@
  const delAllVisitedViews = (): Promise<RouteLocationNormalized[]> => {
    return new Promise((resolve) => {
      visitedViews.value = visitedViews.value.filter((tag: RouteLocationNormalized) => tag.meta?.affix);
      resolve([...visitedViews.value as RouteLocationNormalized[]]);
      resolve([...(visitedViews.value as RouteLocationNormalized[])]);
    });
  };
@@ -167,7 +167,7 @@
        }
        return false;
      });
      resolve([...visitedViews.value as RouteLocationNormalized[]]);
      resolve([...(visitedViews.value as RouteLocationNormalized[])]);
    });
  };
  const delLeftTags = (view: RouteLocationNormalized): Promise<RouteLocationNormalized[]> => {
@@ -186,7 +186,7 @@
        }
        return false;
      });
      resolve([...visitedViews.value as RouteLocationNormalized[]]);
      resolve([...(visitedViews.value as RouteLocationNormalized[])]);
    });
  };
src/types/bpmn/editor/global.d.ts
ÎļþÒÑɾ³ý
src/types/bpmn/index.d.ts
ÎļþÒÑɾ³ý
src/types/bpmn/moddle.d.ts
ÎļþÒÑɾ³ý
src/types/bpmn/panel.d.ts
ÎļþÒÑɾ³ý
src/types/module.d.ts
@@ -8,10 +8,11 @@
import { handleTree, addDateRange, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi';
import { getConfigKey, updateConfigByKey } from '@/api/system/config';
import { download as rd } from '@/utils/request';
import type { LanguageType } from '@/lang';
export {};
declare module 'vue' {
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    // å…¨å±€æ–¹æ³•声明
    $modal: typeof modal;
@@ -20,6 +21,11 @@
    $auth: typeof auth;
    $cache: typeof cache;
    animate: typeof animate;
    /**
     * i18n $t方法支持ts类型提示
     * @param key i18n key
     */
    $t(key: ObjKeysToUnion<LanguageType>): string;
    useDict: typeof useDict;
    addDateRange: typeof addDateRange;
@@ -33,7 +39,13 @@
  }
}
declare module 'vform3-builds' {
  const content: any;
  export = content;
}
/**
 * { a: 1, b: { ba: { baa: 1, bab: 2 }, bb: 2} } ---> a | b.ba.baa | b.ba.bab | b.bb
 * https://juejin.cn/post/7280062870670606397
 */
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;
src/utils/request.ts
@@ -10,7 +10,7 @@
import { getLanguage } from '@/lang';
import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
import { encrypt, decrypt } from '@/utils/jsencrypt';
import router from "@/router";
import router from '@/router';
const encryptHeader = 'encrypt-key';
let downloadLoadingInstance: LoadingInstance;
src/utils/sse.ts
@@ -8,11 +8,8 @@
    return;
  }
  url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID
  const {
    data,
    error
  } = useEventSource(url, [], {
  url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
  const { data, error } = useEventSource(url, [], {
    autoReconnect: {
      retries: 10,
      delay: 3000,
src/utils/validate.ts
@@ -1,4 +1,16 @@
/**
 * è·¯å¾„匹配器
 * @param {string} pattern
 * @param {string} path
 * @returns {Boolean}
 */
export function isPathMatch(pattern: string, path: string) {
  const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*')
  const regex = new RegExp(`^${regexPattern}$`)
  return regex.test(path)
}
/**
 * åˆ¤æ–­url是否是http或https
 * @returns {Boolean}
 * @param url
src/utils/websocket.ts
@@ -7,7 +7,7 @@
  if (import.meta.env.VITE_APP_WEBSOCKET === 'false') {
    return;
  }
  url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID
  url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
  useWebSocket(url, {
    autoReconnect: {
      // é‡è¿žæœ€å¤§æ¬¡æ•°
@@ -16,14 +16,14 @@
      delay: 1000,
      onFailed() {
        console.log('websocket重连失败');
      },
      }
    },
    heartbeat: {
      message: JSON.stringify({type: 'ping'}),
      message: JSON.stringify({ type: 'ping' }),
      // å‘送心跳的间隔
      interval: 10000,
      // æŽ¥æ”¶åˆ°å¿ƒè·³response的超时时间
      pongTimeout: 2000,
      pongTimeout: 2000
    },
    onConnected() {
      console.log('websocket已经连接');
src/views/demo/demo/index.vue
@@ -45,7 +45,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
src/views/demo/tree/index.vue
@@ -25,7 +25,7 @@
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
src/views/index.vue
@@ -33,7 +33,7 @@
          * éƒ¨ç½²æ–¹å¼ Docker å®¹å™¨ç¼–排 ä¸€é”®éƒ¨ç½²ä¸šåŠ¡é›†ç¾¤<br />
          * å›½é™…化 SpringMessage Spring标准国际化方案<br />
        </p>
        <p><b>当前版本:</b> <span>v5.2.3</span></p>
        <p><b>当前版本:</b> <span>v5.3.0-BETA</span></p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
src/views/login.vue
@@ -1,56 +1,73 @@
<template>
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <div class="title-box">
        <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
        <lang-select />
      </div>
      <el-form-item v-if="tenantEnabled" prop="tenantId">
        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
        <el-select v-model="loginForm.tenantId" filterable :placeholder="proxy.$t('login.selectPlaceholder')" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
      </el-form-item>
      <el-form-item prop="username">
        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('login.username')">
          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin">
        <el-input
          v-model="loginForm.password"
          type="password"
          size="large"
          auto-complete="off"
          :placeholder="proxy.$t('login.password')"
          @keyup.enter="handleLogin"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item v-if="captchaEnabled" prop="code">
        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
        <el-input
          v-model="loginForm.code"
          size="large"
          auto-complete="off"
          :placeholder="proxy.$t('login.code')"
          style="width: 63%"
          @keyup.enter="handleLogin"
        >
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" class="login-code-img" @click="getCode" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">记住密码</el-checkbox>
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
      <el-form-item style="float: right">
        <el-button circle title="微信登录" @click="doSocialLogin('wechat')">
        <el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
          <svg-icon icon-class="wechat" />
        </el-button>
        <el-button circle title="MaxKey登录" @click="doSocialLogin('maxkey')">
        <el-button circle :title="proxy.$t('login.social.maxkey')" @click="doSocialLogin('maxkey')">
          <svg-icon icon-class="maxkey" />
        </el-button>
        <el-button circle title="TopIam登录" @click="doSocialLogin('topiam')">
        <el-button circle :title="proxy.$t('login.social.topiam')" @click="doSocialLogin('topiam')">
          <svg-icon icon-class="topiam" />
        </el-button>
        <el-button circle title="Gitee登录" @click="doSocialLogin('gitee')">
        <el-button circle :title="proxy.$t('login.social.gitee')" @click="doSocialLogin('gitee')">
          <svg-icon icon-class="gitee" />
        </el-button>
        <el-button circle title="Github登录" @click="doSocialLogin('github')">
        <el-button circle :title="proxy.$t('login.social.github')" @click="doSocialLogin('github')">
          <svg-icon icon-class="github" />
        </el-button>
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
          <span v-if="!loading">登 å½•</span>
          <span v-else>登 å½• ä¸­...</span>
          <span v-if="!loading">{{ proxy.$t('login.login') }}</span>
          <span v-else>{{ proxy.$t('login.logging') }}</span>
        </el-button>
        <div v-if="register" style="float: right">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
          <router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
        </div>
      </el-form-item>
    </el-form>
@@ -68,9 +85,13 @@
import { LoginData, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
import { HttpStatus } from '@/enums/RespEnum';
import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const router = useRouter();
const { t } = useI18n();
const loginForm = ref<LoginData>({
  tenantId: '000000',
@@ -82,10 +103,10 @@
} as LoginData);
const loginRules: ElFormRules = {
  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
  tenantId: [{ required: true, trigger: 'blur', message: t('login.rule.tenantId.required') }],
  username: [{ required: true, trigger: 'blur', message: t('login.rule.username.required') }],
  password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }],
  code: [{ required: true, trigger: 'change', message: t('login.rule.code.required') }]
};
const codeUrl = ref('');
@@ -105,7 +126,7 @@
watch(
  () => router.currentRoute.value,
  (newRoute: any) => {
    redirect.value = newRoute.query && decodeURIComponent(newRoute.query.redirect);
    redirect.value = newRoute.query && newRoute.query.redirect && decodeURIComponent(newRoute.query.redirect);
  },
  { immediate: true }
);
@@ -176,7 +197,7 @@
 * èŽ·å–ç§Ÿæˆ·åˆ—è¡¨
 */
const initTenantList = async () => {
  const { data } = await getTenantList();
  const { data } = await getTenantList(false);
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
@@ -218,10 +239,19 @@
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
.title-box {
  display: flex;
  .title {
    margin: 0px auto 30px auto;
    text-align: center;
    color: #707070;
  }
  :deep(.lang-select--style) {
    line-height: 0;
    color: #7483a3;
  }
}
.login-form {
src/views/monitor/logininfor/index.vue
@@ -54,7 +54,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['monitor:logininfor:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -94,7 +94,7 @@
        <el-table-column label="描述" align="center" prop="msg" :show-overflow-tooltip="true" />
        <el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.loginTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
          </template>
        </el-table-column>
      </el-table>
src/views/monitor/online/index.vue
@@ -42,7 +42,7 @@
        <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
        <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.loginTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
src/views/monitor/operlog/index.vue
@@ -57,7 +57,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -95,7 +95,7 @@
        </el-table-column>
        <el-table-column label="操作日期" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
          <template #default="scope">
            <span>{{ parseTime(scope.row.operTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.operTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column
@@ -123,56 +123,14 @@
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ“ä½œæ—¥å¿—详细 -->
    <el-dialog v-model="dialog.visible" title="操作日志详细" width="700px" append-to-body>
      <el-form :model="form" label-width="100px">
        <el-row>
          <el-col :span="24">
            <el-form-item label="登录信息:">{{ form.operName }} / {{ form.deptName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="请求信息:">{{ form.requestMethod }} {{ form.operUrl }}</el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="操作模块:">{{ form.title }} / {{ typeFormat(form) }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="操作状态:">
              <div v-if="form.status === 0">正常</div>
              <div v-else-if="form.status === 1">失败</div>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="消耗时间:">{{ form.costTime }}毫秒</el-form-item>
          </el-col>
          <el-col :span="10">
            <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item v-if="form.status === 1" label="异常信息:">{{ form.errorMsg }}</el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="dialog.visible = false">关 é—­</el-button>
        </div>
      </template>
    </el-dialog>
    <OperInfoDialog ref="operInfoDialogRef" />
  </div>
</template>
<script setup name="Operlog" lang="ts">
import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
import { OperLogForm, OperLogQuery, OperLogVO } from '@/api/monitor/operlog/types';
import OperInfoDialog from './oper-info-dialog.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_oper_type, sys_common_status } = toRefs<any>(proxy?.useDict('sys_oper_type', 'sys_common_status'));
@@ -188,11 +146,6 @@
const operLogTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
});
const data = reactive<PageData<OperLogForm, OperLogQuery>>({
  form: {
@@ -267,11 +220,13 @@
  queryParams.value.isAsc = column.order;
  getList();
};
const operInfoDialogRef = ref<InstanceType<typeof OperInfoDialog>>();
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
const handleView = (row: OperLogVO) => {
  dialog.visible = true;
  form.value = row;
  operInfoDialogRef.value.openDialog(row);
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: OperLogVO) => {
  const operIds = row?.operId || ids.value;
src/views/monitor/operlog/oper-info-dialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
<template>
  <el-dialog v-model="open" title="操作日志详细" width="700px" append-to-body close-on-click-modal @closed="info = null">
    <el-descriptions v-if="info" :column="1" border>
      <el-descriptions-item label="操作状态">
        <template #default>
          <el-tag v-if="info.status === 0" type="success">正常</el-tag>
          <el-tag v-else-if="info.status === 1" type="danger">失败</el-tag>
        </template>
      </el-descriptions-item>
      <el-descriptions-item label="登录信息">
        <template #default> {{ info.operName }} / {{ info.deptName }} / {{ info.operIp }} / {{ info.operLocation }} </template>
      </el-descriptions-item>
      <el-descriptions-item label="请求信息">
        <template #default> {{ info.requestMethod }} {{ info.operUrl }} </template>
      </el-descriptions-item>
      <el-descriptions-item label="操作模块">
        <template #default> {{ info.title }} / {{ typeFormat(info) }} </template>
      </el-descriptions-item>
      <el-descriptions-item label="操作方法">
        <template #default>
          {{ info.method }}
        </template>
      </el-descriptions-item>
      <el-descriptions-item label="请求参数">
        <template #default>
          <div class="max-h-300px overflow-y-auto">
            <VueJsonPretty :data="formatToJsonObject(info.operParam)" />
          </div>
        </template>
      </el-descriptions-item>
      <el-descriptions-item label="返回参数">
        <template #default>
          <div class="max-h-300px overflow-y-auto">
            <VueJsonPretty :data="formatToJsonObject(info.jsonResult)" />
          </div>
        </template>
      </el-descriptions-item>
      <el-descriptions-item label="消耗时间">
        <template #default>
          <span> {{ info.costTime }}ms </span>
        </template>
      </el-descriptions-item>
      <el-descriptions-item label="操作时间">
        <template #default> {{ proxy.parseTime(info.operTime) }}</template>
      </el-descriptions-item>
      <el-descriptions-item v-if="info.status === 1" label="异常信息">
        <template #default>
          <span class="text-danger"> {{ info.errorMsg }}</span>
        </template>
      </el-descriptions-item>
    </el-descriptions>
  </el-dialog>
</template>
<script setup lang="ts">
import type { OperLogForm } from '@/api/monitor/operlog/types';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
const open = ref(false);
const info = ref<OperLogForm | null>(null);
function openDialog(row: OperLogForm) {
  info.value = row;
  open.value = true;
}
function closeDialog() {
  open.value = false;
}
defineExpose({
  openDialog,
  closeDialog
});
/**
 * json转为对象
 * @param data åŽŸå§‹æ•°æ®
 */
function formatToJsonObject(data: string) {
  try {
    return JSON.parse(data);
  } catch (error) {
    return data;
  }
}
/**
 * å­—典信息
 */
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_oper_type } = toRefs<any>(proxy?.useDict('sys_oper_type'));
const typeFormat = (row: OperLogForm) => {
  return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
};
</script>
<style scoped>
/**
label宽度固定
*/
:deep(.el-descriptions__label) {
  min-width: 100px;
}
/**
文字超过 æ¢è¡Œæ˜¾ç¤º
*/
:deep(.el-descriptions__content) {
  max-width: 300px;
}
</style>
src/views/register.vue
@@ -1,20 +1,30 @@
<template>
  <div class="register">
    <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <div class="title-box">
        <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
        <lang-select />
      </div>
      <el-form-item v-if="tenantEnabled" prop="tenantId">
        <el-select v-model="registerForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
        <el-select v-model="registerForm.tenantId" filterable :placeholder="proxy.$t('register.selectPlaceholder')" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
      </el-form-item>
      <el-form-item prop="username">
        <el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
        <el-input v-model="registerForm.username" type="text" size="large" auto-complete="off" :placeholder="proxy.$t('register.username')">
          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input v-model="registerForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleRegister">
        <el-input
          v-model="registerForm.password"
          type="password"
          size="large"
          auto-complete="off"
          :placeholder="proxy.$t('register.password')"
          @keyup.enter="handleRegister"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
@@ -24,14 +34,21 @@
          type="password"
          size="large"
          auto-complete="off"
          placeholder="确认密码"
          :placeholder="proxy.$t('register.confirmPassword')"
          @keyup.enter="handleRegister"
        >
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item v-if="captchaEnabled" prop="code">
        <el-input v-model="registerForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
        <el-input
          v-model="registerForm.code"
          size="large"
          auto-complete="off"
          :placeholder="proxy.$t('register.code')"
          style="width: 63%"
          @keyup.enter="handleRegister"
        >
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="register-code">
@@ -40,11 +57,11 @@
      </el-form-item>
      <el-form-item style="width: 100%">
        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleRegister">
          <span v-if="!loading">注 å†Œ</span>
          <span v-else>注 å†Œ ä¸­...</span>
          <span v-if="!loading">{{ proxy.$t('register.register') }}</span>
          <span v-else>{{ proxy.$t('register.registering') }}</span>
        </el-button>
        <div style="float: right">
          <router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
          <router-link class="link-type" :to="'/login'">{{ proxy.$t('register.switchLoginPage') }}</router-link>
        </div>
      </el-form-item>
    </el-form>
@@ -59,8 +76,13 @@
import { getCodeImg, register, getTenantList } from '@/api/login';
import { RegisterForm, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
import { useI18n } from 'vue-i18n';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const { t } = useI18n();
const registerForm = ref<RegisterForm>({
  tenantId: '',
@@ -77,28 +99,28 @@
const equalToPassword = (rule: any, value: string, callback: any) => {
  if (registerForm.value.password !== value) {
    callback(new Error('两次输入的密码不一致'));
    callback(new Error(t('register.rule.confirmPassword.equalToPassword')));
  } else {
    callback();
  }
};
const registerRules: ElFormRules = {
  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
  tenantId: [{ required: true, trigger: 'blur', message: t('register.rule.tenantId.required') }],
  username: [
    { required: true, trigger: 'blur', message: '请输入您的账号' },
    { min: 2, max: 20, message: '用户账号长度必须介于 2 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
    { required: true, trigger: 'blur', message: t('register.rule.username.required') },
    { min: 2, max: 20, message: t('register.rule.username.length', { min: 2, max: 20 }), trigger: 'blur' }
  ],
  password: [
    { required: true, trigger: 'blur', message: '请输入您的密码' },
    { min: 5, max: 20, message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´', trigger: 'blur' },
    { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', trigger: 'blur' }
    { required: true, trigger: 'blur', message: t('register.rule.password.required') },
    { min: 5, max: 20, message: t('register.rule.password.length', { min: 5, max: 20 }), trigger: 'blur' },
    { pattern: /^[^<>"'|\\]+$/, message: t('register.rule.password.pattern', { strings: '< > " \' \\ |' }), trigger: 'blur' }
  ],
  confirmPassword: [
    { required: true, trigger: 'blur', message: '请再次输入您的密码' },
    { required: true, trigger: 'blur', message: t('register.rule.confirmPassword.required') },
    { required: true, validator: equalToPassword, trigger: 'blur' }
  ],
  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
  code: [{ required: true, trigger: 'change', message: t('register.rule.code.required') }]
};
const codeUrl = ref('');
const loading = ref(false);
@@ -114,7 +136,8 @@
      const [err] = await to(register(registerForm.value));
      if (!err) {
        const username = registerForm.value.username;
        await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + ' æ³¨å†ŒæˆåŠŸï¼</font>', '系统提示', {
        await ElMessageBox.alert('<span style="color: red; ">' + t('register.registerSuccess', { username }) + '</font>', '系统提示', {
          app: undefined,
          dangerouslyUseHTMLString: true,
          type: 'success'
        });
@@ -140,7 +163,7 @@
};
const initTenantList = async () => {
  const { data } = await getTenantList();
  const { data } = await getTenantList(false);
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
@@ -166,10 +189,20 @@
  background-size: cover;
}
.title {
  margin: 0 auto 30px auto;
  text-align: center;
  color: #707070;
.title-box {
  display: flex;
  .title {
    margin: 0px auto 30px auto;
    text-align: center;
    color: #707070;
  }
  :deep(.lang-select--style) {
    line-height: 0;
    color: #7483a3;
  }
}
.register-form {
src/views/system/client/index.vue
@@ -41,7 +41,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
src/views/system/config/index.vue
@@ -56,7 +56,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -74,7 +74,7 @@
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
@@ -101,7 +101,7 @@
          <el-input v-model="form.configKey" placeholder="请输入参数键名" />
        </el-form-item>
        <el-form-item label="参数键值" prop="configValue">
          <el-input v-model="form.configValue" placeholder="请输入参数键值" />
          <el-input v-model="form.configValue" type="textarea" placeholder="请输入参数键值" />
        </el-form-item>
        <el-form-item label="系统内置" prop="configType">
          <el-radio-group v-model="form.configType">
src/views/system/dept/index.vue
@@ -33,7 +33,7 @@
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -55,7 +55,7 @@
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="200">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column fixed="right" align="center" label="操作">
src/views/system/dict/data.vue
@@ -40,7 +40,7 @@
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -66,7 +66,7 @@
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
src/views/system/dict/index.vue
@@ -49,10 +49,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -70,7 +67,7 @@
        <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
@@ -112,15 +109,11 @@
<script setup name="Dict" lang="ts">
import useDictStore from '@/store/modules/dict';
import useUserStore from "@/store/modules/user";
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
import { syncTenantDict } from "@/api/system/tenant";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const userId = ref(userStore.userId);
const typeList = ref<DictTypeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
@@ -245,12 +238,6 @@
  await refreshCache();
  proxy?.$modal.msgSuccess('刷新成功');
  useDictStore().cleanDict();
};
/**同步租户字典*/
const handleSyncTenantDict = async () => {
  await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
  let res = await syncTenantDict();
  proxy?.$modal.msgSuccess(res.msg);
};
onMounted(() => {
src/views/system/menu/index.vue
@@ -30,7 +30,7 @@
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
src/views/system/notice/index.vue
@@ -40,7 +40,7 @@
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -61,7 +61,7 @@
        <el-table-column label="创建者" align="center" prop="createByName" width="100" />
        <el-table-column label="创建时间" align="center" prop="createTime" width="100">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
src/views/system/oss/config.vue
@@ -41,7 +41,7 @@
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -159,8 +159,8 @@
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
  { key: 0, label: `主建`, visible: true },
  { key: 1, label: `配置key`, visible: false },
  { key: 0, label: `主建`, visible: false },
  { key: 1, label: `配置key`, visible: true },
  { key: 2, label: `访问站点`, visible: true },
  { key: 3, label: `自定义域名`, visible: true },
  { key: 4, label: `桶名称`, visible: true },
src/views/system/oss/index.vue
@@ -62,7 +62,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:ossConfig:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -93,7 +93,7 @@
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="上传人" align="center" prop="createByName" />
src/views/system/post/index.vue
@@ -81,7 +81,7 @@
              <el-col :span="1.5">
                <el-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
              <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">
@@ -99,7 +99,7 @@
            </el-table-column>
            <el-table-column label="创建时间" align="center" prop="createTime" width="180">
              <template #default="scope">
                <span>{{ parseTime(scope.row.createTime) }}</span>
                <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
src/views/system/role/authUser.vue
@@ -30,7 +30,7 @@
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="getList"></right-toolbar>
          <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">
src/views/system/role/index.vue
@@ -51,7 +51,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -68,7 +68,7 @@
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime">
          <template #default="scope">
            <span>{{ parseTime(scope.row.createTime) }}</span>
            <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
          </template>
        </el-table-column>
@@ -223,7 +223,8 @@
  { value: '2', label: '自定数据权限' },
  { value: '3', label: '本部门数据权限' },
  { value: '4', label: '本部门及以下数据权限' },
  { value: '5', label: '仅本人数据权限' }
  { value: '5', label: '仅本人数据权限' },
  { value: '6', label: '部门及以下或本人数据权限' }
]);
const queryFormRef = ref<ElFormInstance>();
src/views/system/role/selectUser.vue
@@ -27,7 +27,7 @@
          </el-table-column>
          <el-table-column label="创建时间" align="center" prop="createTime" width="180">
            <template #default="scope">
              <span>{{ parseTime(scope.row.createTime) }}</span>
              <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
            </template>
          </el-table-column>
        </el-table>
src/views/system/tenant/index.vue
@@ -44,7 +44,10 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <el-col :span="1.5">
            <el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
          </el-col>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -58,7 +61,7 @@
        <el-table-column label="社会信用代码" align="center" prop="licenseNumber" />
        <el-table-column label="过期时间" align="center" prop="expireTime" width="180">
          <template #default="scope">
            <span>{{ parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
            <span>{{ proxy.parseTime(scope.row.expireTime, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="租户状态" align="center" prop="status">
@@ -141,13 +144,25 @@
</template>
<script setup name="Tenant" lang="ts">
import { listTenant, getTenant, delTenant, addTenant, updateTenant, changeTenantStatus, syncTenantPackage } from '@/api/system/tenant';
import {
  listTenant,
  getTenant,
  delTenant,
  addTenant,
  updateTenant,
  changeTenantStatus,
  syncTenantPackage,
  syncTenantDict
} from '@/api/system/tenant';
import { selectTenantPackage } from '@/api/system/tenantPackage';
import useUserStore from '@/store/modules/user';
import { TenantForm, TenantQuery, TenantVO } from '@/api/system/tenant/types';
import { TenantPkgVO } from '@/api/system/tenantPackage/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const userId = ref(userStore.userId);
const tenantList = ref<TenantVO[]>([]);
const packageList = ref<TenantPkgVO[]>([]);
const buttonLoading = ref(false);
@@ -343,6 +358,13 @@
  );
};
/**同步租户字典*/
const handleSyncTenantDict = async () => {
  await proxy?.$modal.confirm('确认要同步所有租户字典吗?');
  let res = await syncTenantDict();
  proxy?.$modal.msgSuccess(res.msg);
};
onMounted(() => {
  getList();
});
src/views/system/tenantPackage/index.vue
@@ -35,7 +35,7 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['system:tenantPackage:export']" type="warning" plain icon="Download" @click="handleExport">导出 </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
src/views/system/user/authRole.vue
@@ -39,7 +39,7 @@
          <el-table-column label="权限字符" align="center" prop="roleKey" />
          <el-table-column label="创建时间" align="center" prop="createTime" width="180">
            <template #default="scope">
              <span>{{ parseTime(scope.row.createTime) }}</span>
              <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
            </template>
          </el-table-column>
        </el-table>
@@ -80,7 +80,7 @@
/** å•击选中行数据 */
const clickRow = (row: RoleVO) => {
  row.flag = !row.flag
  row.flag = !row.flag;
  tableRef.value?.toggleRowSelection(row, row.flag);
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
src/views/system/user/index.vue
@@ -63,12 +63,12 @@
                <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button v-has-permi="['system:user:add']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
                <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
                  ä¿®æ”¹
                </el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button v-has-permi="['system:user:delete']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
                <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
                  åˆ é™¤
                </el-button>
              </el-col>
@@ -81,13 +81,13 @@
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
                      <el-dropdown-item icon="Top" @click="handleImport"> å¯¼å…¥æ•°æ®</el-dropdown-item>
                      <el-dropdown-item icon="Download" @click="handleExport"> å¯¼å‡ºæ•°æ®</el-dropdown-item>
                      <el-dropdown-item v-has-permi="['system:user:import']" icon="Top" @click="handleImport">导入数据</el-dropdown-item>
                      <el-dropdown-item v-has-permi="['system:user:export']" icon="Download" @click="handleExport">导出数据</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
              <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
            </el-row>
          </template>
@@ -154,7 +154,7 @@
            <el-form-item label="归属部门" prop="deptId">
              <el-tree-select
                v-model="form.deptId"
                :data="deptOptions"
                :data="enabledDeptOptions"
                :props="{ value: 'id', label: 'label', children: 'children' }"
                value-key="id"
                placeholder="请选择归属部门"
@@ -287,7 +287,7 @@
<script setup name="User" lang="ts">
import api from '@/api/system/user';
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { DeptVO } from '@/api/system/dept/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';
@@ -307,7 +307,8 @@
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const deptOptions = ref<DeptTreeVO[]>([]);
const enabledDeptOptions = ref<DeptTreeVO[]>([]);
const initPassword = ref<string>('');
const postOptions = ref<PostVO[]>([]);
const roleOptions = ref<RoleVO[]>([]);
@@ -393,7 +394,7 @@
        message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´',
        trigger: 'blur'
      },
      { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', trigger: 'blur' }
      { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
    ],
    email: [
      {
@@ -431,12 +432,6 @@
  }
);
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getTreeSelect = async () => {
  const res = await api.deptTreeSelect();
  deptOptions.value = res.data;
};
/** æŸ¥è¯¢ç”¨æˆ·åˆ—表 */
const getList = async () => {
  loading.value = true;
@@ -444,6 +439,26 @@
  loading.value = false;
  userList.value = res.rows;
  total.value = res.total;
};
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getDeptTree = async () => {
  const res = await api.deptTreeSelect();
  deptOptions.value = res.data;
  enabledDeptOptions.value = filterDisabledDept(res.data);
};
/** è¿‡æ»¤ç¦ç”¨çš„部门 */
const filterDisabledDept = (deptList: DeptTreeVO[]) => {
  return deptList.filter(dept => {
    if (dept.disabled) {
      return false;
    }
    if (dept.children && dept.children.length) {
      dept.children = filterDisabledDept(dept.children);
    }
    return true;
  });
};
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
@@ -506,7 +521,7 @@
      inputErrorMessage: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´',
      inputValidator: (value) => {
        if (/<|>|"|'|\||\\/.test(value)) {
          return '不能包含非法字符:< > " \' \\\ |';
          return '不能包含非法字符:< > " \' \\ |';
        }
      }
    })
@@ -564,15 +579,6 @@
  uploadRef.value?.submit();
}
/** åˆå§‹åŒ–部门数据 */
const initTreeData = async () => {
  // åˆ¤æ–­éƒ¨é—¨çš„æ•°æ®æ˜¯å¦å­˜åœ¨ï¼Œå­˜åœ¨ä¸èŽ·å–ï¼Œä¸å­˜åœ¨åˆ™èŽ·å–
  if (deptOptions.value === undefined) {
    const { data } = await treeselect();
    deptOptions.value = data;
  }
};
/** é‡ç½®æ“ä½œè¡¨å• */
const reset = () => {
  form.value = { ...initFormData };
@@ -590,7 +596,6 @@
  const { data } = await api.getUser();
  dialog.visible = true;
  dialog.title = '新增用户';
  await initTreeData();
  postOptions.value = data.posts;
  roleOptions.value = data.roles;
  form.value.password = initPassword.value.toString();
@@ -603,7 +608,6 @@
  const { data } = await api.getUser(userId);
  dialog.visible = true;
  dialog.title = '修改用户';
  await initTreeData();
  Object.assign(form.value, data.user);
  postOptions.value = data.posts;
  roleOptions.value = data.roles;
@@ -643,7 +647,7 @@
  form.value.status = '1';
};
onMounted(() => {
  getTreeSelect(); // åˆå§‹åŒ–部门数据
  getDeptTree(); // åˆå§‹åŒ–部门数据
  getList(); // åˆå§‹åŒ–列表数据
  proxy?.getConfigKey('sys.user.initPassword').then((response) => {
    initPassword.value = response.data;
src/views/system/user/profile/onlineDevice.vue
@@ -12,7 +12,7 @@
      <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
      <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
        <template #default="scope">
          <span>{{ parseTime(scope.row.loginTime) }}</span>
          <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
src/views/system/user/profile/resetPwd.vue
@@ -45,7 +45,7 @@
      message: '长度在 6 åˆ° 20 ä¸ªå­—符',
      trigger: 'blur'
    },
    { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', trigger: 'blur' }
    { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
  ],
  confirmPassword: [
    { required: true, message: '确认密码不能为空', trigger: 'blur' },
src/views/tool/gen/importTable.vue
@@ -63,14 +63,18 @@
/** æŸ¥è¯¢å‚数列表 */
const show = (dataName: string) => {
  getDataNameList();
  if (dataName) {
    queryParams.dataName = dataName;
  } else {
    queryParams.dataName = 'master';
  }
  getList();
  visible.value = true;
  getDataNames().then((res) => {
    if (res.code == 200) {
      dataNameList.value = res.data;
      if (dataName) {
        queryParams.dataName = dataName;
      } else {
        queryParams.dataName = dataNameList.value[0];
      }
      getList();
      visible.value = true;
    }
  });
};
/** å•击选择行 */
const clickRow = (row: DbTableVO) => {
@@ -110,11 +114,6 @@
    visible.value = false;
    emit('ok');
  }
};
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
const getDataNameList = async () => {
  const res = await getDataNames();
  dataNameList.value = res.data;
};
defineExpose({
src/views/tool/gen/index.vue
@@ -52,7 +52,7 @@
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -113,8 +113,8 @@
</template>
<script setup name="Gen" lang="ts">
import { listTable, previewTable, delTable, genCode, synchDb, getDataNames } 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';
@@ -153,17 +153,6 @@
const dialog = reactive<DialogOption>({
  visible: false,
  title: '代码预览'
});
onActivated(() => {
  const time = route.query.t;
  if (time != null && time != uniqueId.value) {
    uniqueId.value = time as string;
    queryParams.value.pageNum = Number(route.query.pageNum);
    dateRange.value = ['', ''];
    queryFormRef.value?.resetFields();
    getList();
  }
});
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
@@ -248,6 +237,13 @@
};
onMounted(() => {
  const time = route.query.t;
  if (time != null && time != uniqueId.value) {
    uniqueId.value = time as string;
    queryParams.value.pageNum = Number(route.query.pageNum);
    dateRange.value = ['', ''];
    queryFormRef.value?.resetFields();
  }
  getList();
  getDataNameList();
});
src/views/workflow/category/index.vue
@@ -6,9 +6,6 @@
          <el-form-item label="分类名称" prop="categoryName">
            <el-input v-model="queryParams.categoryName" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="分类编码" prop="categoryCode">
            <el-input v-model="queryParams.categoryCode" 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>
@@ -21,64 +18,66 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:category:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:category:add']">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="categoryTableRef"
        v-loading="loading"
        :data="categoryList"
        row-key="id"
        row-key="categoryId"
        :default-expand-all="isExpandAll"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
        <el-table-column label="分类名称" prop="categoryName" />
        <el-table-column label="分类编码" align="center" prop="categoryCode" />
        <el-table-column label="排序" align="center" prop="sortNum" />
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <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">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button v-hasPermi="['workflow:category:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:category:edit']" />
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button v-hasPermi="['workflow:category:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['workflow:category:add']" />
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button v-hasPermi="['workflow:category:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:category:remove']" />
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµç¨‹åˆ†ç±»å¯¹è¯æ¡† -->
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="categoryFormRef" v-loading="loading" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="父级分类" prop="parentId">
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
      <el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="上级分类" prop="parentId">
          <el-tree-select
            v-model="form.parentId"
            :data="categoryOptions"
            :props="{ value: 'id', label: 'categoryName', children: 'children' }"
            value-key="id"
            placeholder="请选择父级id"
            :props="{ value: 'categoryId', label: 'categoryName', children: 'children' }"
            value-key="categoryId"
            placeholder="请选择上级分类"
            check-strictly
          />
        </el-form-item>
        <el-form-item label="分类名称" prop="categoryName">
          <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
        </el-form-item>
        <el-form-item label="分类编码" prop="categoryCode">
          <el-input v-model="form.categoryCode" placeholder="请输入分类编码" />
        </el-form-item>
        <el-form-item label="排序" prop="sortNum">
          <el-input-number v-model="form.sortNum" placeholder="请输入排序" controls-position="right" :min="0" />
        </el-form-item>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="分类名称" prop="categoryName">
              <el-input v-model="form.categoryName" placeholder="请输入分类名称" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="排序" prop="orderNum">
              <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
            </el-form-item>
          </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>
@@ -89,16 +88,17 @@
</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 = {
  id: number;
  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,34 +109,32 @@
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 = {
  id: undefined,
  categoryName: undefined,
  categoryCode: undefined,
  categoryId: undefined,
  categoryName: "",
  parentId: undefined,
  sortNum: 0
};
  orderNum: 0,
}
const data = reactive<PageData<CategoryForm, CategoryQuery>>({
  form: { ...initFormData },
  form: {...initFormData},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    categoryName: undefined,
    categoryCode: undefined
  },
  rules: {
    id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
    categoryName: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
    categoryCode: [{ required: true, message: '分类编码不能为空', trigger: 'blur' }],
    parentId: [{ required: true, message: '父级id不能为空', trigger: 'blur' }]
    categoryId: [
      { required: true, message: "流程分类ID不能为空", trigger: "blur" }
    ],
    parentId: [{ required: true, message: "请选择上级分类", trigger: "change" }],
    categoryName: [{ required: true, message: "请输入分类名称", trigger: "blur" }]
  }
});
@@ -146,116 +144,112 @@
const getList = async () => {
  loading.value = true;
  const res = await listCategory(queryParams.value);
  const data = proxy?.handleTree<CategoryVO>(res.data, 'id', '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: CategoryOption = { id: 0, categoryName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
  categoryOptions.value.push(data);
  // å¤„理树形数据
  const data = proxy?.handleTree<CategoryOption>(res.data, "categoryId", "parentId");
  if (data) {
    categoryOptions.value = data; // å°†å¤„理后的树形数据赋值
  }
};
// å–消按钮
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) => {
  reset();
  getTreeselect();
  if (row?.categoryId) {
    form.value.parentId = row.categoryId;
  } else {
    form.value.parentId = undefined;
  }
  dialog.visible = true;
  dialog.title = '添加流程分类';
  nextTick(() => {
    reset();
    getTreeselect();
    if (row != null && row.id) {
      form.value.parentId = row.id;
    } else {
      form.value.parentId = 0;
    }
  });
};
  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 = (row: CategoryVO) => {
  loading.value = true;
const handleUpdate = async (row: CategoryVO) => {
  reset();
  await getTreeselect();
  if (row != null) {
    form.value.parentId = row.parentId;
  }
  const res = await getCategory(row.categoryId);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = '修改流程分类';
  nextTick(async () => {
    reset();
    await getTreeselect();
    if (row != null) {
      form.value.parentId = row.id;
    }
    const res = await getCategory(row.id);
    loading.value = false;
    Object.assign(form.value, res.data);
  });
};
  dialog.title = "修改流程分类";
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  categoryFormRef.value.validate(async (valid: boolean) => {
  categoryFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateCategory(form.value).finally(() => (buttonLoading.value = false));
      if (form.value.categoryId) {
        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;
      await getList();
      getList();
    }
  });
};
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: CategoryVO) => {
  await proxy?.$modal.confirm('是否确认删除流程分类编号为"' + row.id + '"的数据项?');
  await proxy?.$modal.confirm('是否确认删除"' + row.categoryName + '"的分类?');
  loading.value = true;
  await delCategory(row.id).finally(() => (loading.value = false));
  await delCategory(row.categoryId).finally(() => loading.value = false);
  await getList();
  proxy?.$modal.msgSuccess('删除成功');
};
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
  getList();
src/views/workflow/formManage/index.vue
ÎļþÒÑɾ³ý
src/views/workflow/leave/index.vue
@@ -27,11 +27,11 @@
          <el-col :span="1.5">
            <el-button v-hasPermi="['workflow:leave:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="leaveList" @selection-change="handleSelectionChange">
      <el-table v-loading="loading" border :data="leaveList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column v-if="false" label="主键" align="center" prop="id" />
        <el-table-column label="请假类型" align="center">
@@ -41,12 +41,12 @@
        </el-table-column>
        <el-table-column label="开始时间" align="center" prop="startDate">
          <template #default="scope">
            <span>{{ parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
            <span>{{ proxy.parseTime(scope.row.startDate, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="结束时间" align="center" prop="endDate">
          <template #default="scope">
            <span>{{ parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
            <span>{{ proxy.parseTime(scope.row.endDate, '{y}-{m}-{d}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="请假天数" align="center" prop="leaveDays" />
@@ -56,38 +56,28 @@
            <dict-tag :options="wf_business_status" :value="scope.row.status"></dict-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <el-table-column label="操作" align="center" width="162">
          <template #default="scope">
            <el-button
              v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'"
              v-hasPermi="['workflow:leave:edit']"
              size="small"
              link
              type="primary"
              icon="Edit"
              @click="handleUpdate(scope.row)"
              >修改</el-button
            >
            <el-button
              v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'"
              v-hasPermi="['workflow:leave:remove']"
              size="small"
              link
              type="primary"
              icon="Delete"
              @click="handleDelete(scope.row)"
              >删除</el-button
            >
            <el-button link type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
            <el-button
              v-if="scope.row.status === 'waiting'"
              link
              size="small"
              type="primary"
              icon="Notification"
              @click="handleCancelProcessApply(scope.row.id)"
              >撤销</el-button
            >
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
                <el-button v-hasPermi="['workflow:leave:edit']" size="small" type="primary" icon="Edit" @click="handleUpdate(scope.row)"
                  >修改</el-button
                >
              </el-col>
              <el-col :span="1.5" v-if="scope.row.status === 'draft' || scope.row.status === 'cancel' || scope.row.status === 'back'">
                <el-button v-hasPermi="['workflow:leave:remove']" size="small" type="primary" icon="Delete" @click="handleDelete(scope.row)"
                  >删除</el-button
                >
              </el-col>
            </el-row>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
              </el-col>
              <el-col :span="1.5" v-if="scope.row.status === 'waiting'">
                <el-button size="small" type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.id)">撤销</el-button>
              </el-col>
            </el-row>
          </template>
        </el-table-column>
      </el-table>
@@ -99,7 +89,7 @@
<script setup name="Leave" lang="ts">
import { delLeave, listLeave } from '@/api/workflow/leave';
import { cancelProcessApply } from '@/api/workflow/processInstance';
import { cancelProcessApply } from '@/api/workflow/instance';
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -232,7 +222,11 @@
const handleCancelProcessApply = async (id: string) => {
  await proxy?.$modal.confirm('是否确认撤销当前单据?');
  loading.value = true;
  await cancelProcessApply(id).finally(() => (loading.value = false));
  let data = {
    businessId: id,
    message: '申请人撤销流程!'
  };
  await cancelProcessApply(data).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess('撤销成功');
};
src/views/workflow/leave/leaveEdit.vue
@@ -44,6 +44,17 @@
    <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>
@@ -80,6 +91,35 @@
    label: '婚假'
  }
];
const flowCodeOptions = [
  {
    value: 'leave1',
    label: '请假申请-普通'
  },
  {
    value: 'leave2',
    label: '请假申请-排他网关'
  },
  {
    value: 'leave3',
    label: '请假申请-并行网关'
  },
  {
    value: 'leave4',
    label: '请假申请-会签'
  },
  {
    value: 'leave5',
    label: '请假申请-并行会签网关'
  }
];
const flowCode = ref<string>('');
const dialogVisible = reactive<DialogOption>({
  visible: false,
  title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
@@ -88,8 +128,8 @@
const leaveFormRef = ref<ElFormInstance>();
const submitFormData = ref<StartProcessBo>({
  businessKey: '',
  tableName: '',
  businessId: '',
  flowCode: '',
  variables: {}
});
const taskVariables = ref<Record<string, any>>({});
@@ -119,6 +159,11 @@
  }
});
const handleClose = () => {
  dialogVisible.visible = false;
  flowCode.value = '';
  buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** è¡¨å•重置 */
@@ -174,6 +219,15 @@
          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;
          }
          //说明启动过先随意穿个参数
          if (flowCode.value === '' || flowCode.value === null) {
            flowCode.value = 'xx';
          }
          await handleStartWorkFlow(res.data);
        }
      }
@@ -183,17 +237,19 @@
  }
};
const submitFlow = async () => {
  handleStartWorkFlow(form.value);
  dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveVO) => {
  try {
    submitFormData.value.tableName = 'test_leave';
    submitFormData.value.businessKey = data.id;
    submitFormData.value.flowCode = flowCode.value;
    submitFormData.value.businessId = data.id;
    //流程变量
    taskVariables.value = {
      entity: data,
      leaveDays: data.leaveDays,
      userList: ["1", "3"],
      userList2: ["1", "3"]
      userList: ['1', '3', '4']
    };
    submitFormData.value.variables = taskVariables.value;
    const resp = await startWorkFlow(submitFormData.value);
src/views/workflow/model/index.vue
ÎļþÒÑɾ³ý
src/views/workflow/processDefinition/components/processPreview.vue
ÎļþÒÑɾ³ý
src/views/workflow/processDefinition/design.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
<template>
  <div ref="container" class="w-full h-[calc(100vh-88px)]">
    <iframe ref="iframe" :src="iframeUrl" frameborder="0" height="100%" style="height: 100%; width: inherit"></iframe>
  </div>
</template>
<script setup name="WarmFlow">
const { proxy } = getCurrentInstance();
import { onMounted } from 'vue';
import { getToken } from '@/utils/auth';
// definitionId为需要查询的流程定义id,
// disabled为是否可编辑, ä¾‹å¦‚:查看的时候不可编辑,不可保存
const iframeUrl = ref('');
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const iframeLoaded = () => {
  // iframe监听组件内设计器保存事件
  window.onmessage = (event) => {
    switch (event.data.method) {
      case 'close':
        close();
        break;
    }
  };
};
const open = async (definitionId, disabled) => {
  let 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' };
  proxy.$tab.closeOpenPage(obj);
}
onMounted(() => {
  iframeLoaded();
  open(proxy.$route.query.definitionId, proxy.$route.query.disabled);
});
/**
 * å¯¹å¤–暴露子组件方法
 */
defineExpose({
  open
});
</script>
src/views/workflow/processDefinition/index.vue
@@ -10,7 +10,7 @@
            class="mt-2"
            node-key="id"
            :data="categoryOptions"
            :props="{ label: 'categoryName', children: 'children' }"
            :props="{ label: 'label', children: 'children' }"
            :expand-on-click-node="false"
            :filter-node-method="filterNode"
            highlight-current
@@ -24,11 +24,11 @@
          <div v-show="showSearch" class="mb-[10px]">
            <el-card shadow="hover">
              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
                <el-form-item label="流程定义名称" prop="name">
                  <el-input v-model="queryParams.name" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
                <el-form-item label="流程定义名称" prop="flowName">
                  <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="流程定义KEY" prop="key">
                  <el-input v-model="queryParams.key" placeholder="请输入流程定义KEY" clearable @keyup.enter="handleQuery" />
                <el-form-item label="流程定义KEY" prop="flowCode">
                  <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义KEY" clearable @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -42,93 +42,86 @@
          <template #header>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
              <el-col :span="1.5">
                <el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
              </el-col>
              <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
            </el-row>
          </template>
          <el-table v-loading="loading" border :data="processDefinitionList" @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="name" label="流程定义名称" :show-overflow-tooltip="true"></el-table-column>
            <el-table-column align="center" prop="key" label="标识KEY" width="80"></el-table-column>
            <el-table-column align="center" prop="version" label="版本号" width="80">
              <template #default="scope"> v{{ scope.row.version }}.0</template>
            </el-table-column>
            <el-table-column align="center" prop="resourceName" label="流程XML" width="100" :show-overflow-tooltip="true">
              <template #default="scope">
                <el-link type="primary" @click="clickPreview(scope.row.id, 'xml')">{{ scope.row.resourceName }}</el-link>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="diagramResourceName" label="流程图片" width="100" :show-overflow-tooltip="true">
              <template #default="scope">
                <el-link type="primary" @click="clickPreview(scope.row.id, 'bpmn')">{{ scope.row.diagramResourceName }}</el-link>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="suspensionState" label="状态" width="80">
              <template #default="scope">
                <el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
                <el-tag v-else type="danger">挂起</el-tag>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="deploymentTime" label="部署时间" width="120" :show-overflow-tooltip="true"></el-table-column>
            <el-table-column align="center" label="表名/表单KEY" width="120" :show-overflow-tooltip="true">
              <template #default="scope">
                <span v-if="scope.row.wfDefinitionConfigVo">
                  {{ scope.row.wfDefinitionConfigVo.tableName }}
                </span>
              </template>
            </el-table-column>
            <el-table-column fixed="right" label="操作" align="center" width="220" class-name="small-padding fixed-width">
              <template #default="scope">
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button
                      link
                      type="primary"
                      size="small"
                      :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'"
                      @click="handleProcessDefState(scope.row)"
                    >
                      {{ scope.row.suspensionState === 1 ? '挂起流程' : '激活流程' }}
                    </el-button>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Document" @click="getProcessDefinitionHitoryList(scope.row.id, scope.row.key)">
                      åŽ†å²ç‰ˆæœ¬
                    </el-button>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                  </el-col>
                </el-row>
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Sort" @click="handleConvertToModel(scope.row)"> è½¬æ¢æ¨¡åž‹ </el-button>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Tickets" @click="handleDefinitionConfigOpen(scope.row)">绑定业务</el-button>
                  </el-col>
                </el-row>
              </template>
            </el-table-column>
          </el-table>
          <pagination
            v-show="total > 0"
            v-model:page="queryParams.pageNum"
            v-model:limit="queryParams.pageSize"
            :total="total"
            @pagination="getList"
          />
          <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
            <el-tab-pane label="已发布" name="0"></el-tab-pane>
            <el-tab-pane label="未发布" name="1"></el-tab-pane>
            <el-table v-loading="loading" border :data="processDefinitionList" @selection-change="handleSelectionChange">
              <el-table-column type="selection" width="55" align="center" />
              <el-table-column align="center" prop="id" label="主键" v-if="false"></el-table-column>
              <el-table-column align="center" prop="flowName" label="流程定义名称" :show-overflow-tooltip="true"></el-table-column>
              <el-table-column align="center" prop="flowCode" label="标识KEY" :show-overflow-tooltip="true"></el-table-column>
              <el-table-column align="center" prop="version" label="版本号" width="80">
                <template #default="scope"> v{{ scope.row.version }}.0</template>
              </el-table-column>
              <el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
                <template #default="scope">
                  <el-switch
                    v-model="scope.row.activityStatus"
                    :active-value="1"
                    :inactive-value="0"
                    @change="(status) => handleProcessDefState(scope.row, status)"
                  />
                </template>
              </el-table-column>
              <el-table-column align="center" prop="isPublish" label="发布状态" width="100">
                <template #default="scope">
                  <el-tag v-if="scope.row.isPublish == 0" type="danger">未发布</el-tag>
                  <el-tag v-else-if="scope.row.isPublish == 1" type="success">已发布</el-tag>
                  <el-tag v-else type="danger">失效</el-tag>
                </template>
              </el-table-column>
              <el-table-column fixed="right" label="操作" align="center" width="170" class-name="small-padding fixed-width">
                <template #default="scope">
                  <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                      <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
                    </el-col>
                    <el-col :span="1.5">
                      <el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
                    </el-col>
                  </el-row>
                  <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                      <el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
                        >流程设计</el-button
                      >
                      <el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
                    </el-col>
                    <el-col v-if="scope.row.isPublish !== 1" :span="1.5">
                      <el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
                    </el-col>
                  </el-row>
                </template>
              </el-table-column>
            </el-table>
            <pagination
              v-show="total > 0"
              v-model:page="queryParams.pageNum"
              v-model:limit="queryParams.pageSize"
              :total="total"
              @pagination="handleQuery"
            />
          </el-tabs>
        </el-card>
      </el-col>
    </el-row>
    <!-- é¢„览图片或xml -->
    <process-preview ref="previewRef" />
    <!-- éƒ¨ç½²æ–‡ä»¶ -->
    <el-dialog v-if="uploadDialog.visible" v-model="uploadDialog.visible" :title="uploadDialog.title" width="30%">
@@ -138,9 +131,9 @@
          <el-tree-select
            v-model="selectCategory"
            :data="categoryOptions"
            :props="{ value: 'categoryCode', label: 'categoryName', children: 'children' }"
            :props="{ value: 'id', label: 'label', children: 'children' }"
            filterable
            value-key="categoryCode"
            value-key="id"
            :render-after-expand="false"
            check-strictly
            style="width: 240px"
@@ -150,100 +143,47 @@
          class="upload-demo"
          drag
          multiple
          accept="application/zip,application/xml,.bpmn"
          accept="application/json,application/text"
          :before-upload="handlerBeforeUpload"
          :http-request="handerDeployProcessFile"
          :http-request="handlerImportDefinition"
        >
          <el-icon class="UploadFilled"><upload-filled /></el-icon>
          <div class="el-upload__text"><em>点击上传,选择BPMN流程文件</em></div>
          <div class="el-upload__text">仅支持 .zip、.bpmn20.xml、bpmn æ ¼å¼æ–‡ä»¶</div>
          <div class="el-upload__text"><em>点击上传,选择JSON流程文件</em></div>
          <div class="el-upload__text">仅支持json格式文件</div>
          <div class="el-upload__text">PS:如若部署请部署从本项目模型管理导出的数据</div>
        </el-upload>
      </div>
    </el-dialog>
    <!-- åŽ†å²ç‰ˆæœ¬ -->
    <el-dialog v-if="processDefinitionDialog.visible" v-model="processDefinitionDialog.visible" :title="processDefinitionDialog.title" width="70%">
      <el-table v-loading="loading" :data="processDefinitionHistoryList" @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="name" label="流程定义名称" :show-overflow-tooltip="true" min-width="80"></el-table-column>
        <el-table-column align="center" prop="key" label="标识KEY"></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="resourceName" label="流程XML" min-width="80" :show-overflow-tooltip="true">
          <template #default="scope">
            <el-link type="primary" @click="clickPreviewXML(scope.row.id)">{{ scope.row.resourceName }}</el-link>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="diagramResourceName" label="流程图片" min-width="80" :show-overflow-tooltip="true">
          <template #default="scope">
            <el-link type="primary" @click="clickPreviewImg(scope.row.id)">{{ scope.row.diagramResourceName }}</el-link>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="suspensionState" label="状态" min-width="70">
          <template #default="scope">
            <el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
            <el-tag v-else type="danger">挂起</el-tag>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="deploymentTime" label="部署时间" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column fixed="right" label="操作" align="center" width="200" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button
                  link
                  type="primary"
                  size="small"
                  :icon="scope.row.suspensionState === 1 ? 'Lock' : 'Unlock'"
                  @click="handleProcessDefState(scope.row)"
                >
                  {{ scope.row.suspensionState === 1 ? '挂起流程' : '激活流程' }}
                </el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="text" size="small" icon="Tickets" @click="handleDefinitionConfigOpen(scope.row)">绑定业务</el-button>
              </el-col>
            </el-row>
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button link type="primary" icon="Sort" size="small" @click="handleConvertToModel(scope.row)"> è½¬æ¢æ¨¡åž‹ </el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button link type="primary" icon="Delete" size="small" @click="handleDelete(scope.row)">删除</el-button>
              </el-col>
            </el-row>
          </template>
        </el-table-column>
      </el-table>
    </el-dialog>
    <!-- è¡¨å•配置 -->
    <el-dialog
      v-model="definitionConfigDialog.visible"
      :title="definitionConfigDialog.title"
      width="650px"
      append-to-body
      :close-on-click-modal="false"
    >
      <el-form :model="definitionConfigForm" label-width="auto">
        <el-form-item label="流程KEY">
          <el-input v-model="definitionConfigForm.processKey" disabled />
        </el-form-item>
        <el-form-item label="表名" prop="formId">
          <el-input v-model="definitionConfigForm.tableName" placeholder="示例:test_leave" />
        </el-form-item>
        <el-form-item label="备注">
          <el-input v-model="definitionConfigForm.remark" type="textarea" resize="none" />
        </el-form-item>
      </el-form>
    <!-- æ–°å¢ž/编辑流程定义 -->
    <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-item label="流程类别" prop="category">
            <el-tree-select
              v-model="form.category"
              :data="categoryOptions"
              :props="{ value: 'id', label: 'label', children: 'children' }"
              filterable
              value-key="id"
              :render-after-expand="false"
              check-strictly
              style="width: 100%"
            />
          </el-form-item>
          <el-form-item label="流程编码" prop="flowCode">
            <el-input v-model="form.flowCode" placeholder="请输入流程编码" maxlength="40" show-word-limit />
          </el-form-item>
          <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="flowName">
            <el-input v-model="form.formPath" placeholder="请输入表单路径" maxlength="100" show-word-limit />
          </el-form-item>
        </el-form>
        <div class="dialog-footer">
          <el-button @click="definitionConfigDialog.visible = false">取消</el-button>
          <el-button type="primary" @click="handlerSaveForm">保存</el-button>
          <el-button @click="modelDialog.visible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit">保存</el-button>
        </div>
      </template>
    </el-dialog>
@@ -251,53 +191,32 @@
</template>
<script lang="ts" setup name="processDefinition">
import {
  listProcessDefinition,
  definitionImage,
  definitionXml,
  deleteProcessDefinition,
  updateDefinitionState,
  convertToModel,
  deployProcessFile,
  getListByKey
} from '@/api/workflow/processDefinition';
import { getByTableNameNotDefId, getByDefId, saveOrUpdate } from '@/api/workflow/definitionConfig';
import ProcessPreview from './components/processPreview.vue';
import { listCategory } from '@/api/workflow/category';
import { CategoryVO } from '@/api/workflow/category/types';
import { ProcessDefinitionQuery, ProcessDefinitionVO } from '@/api/workflow/processDefinition/types';
import { DefinitionConfigForm } from '@/api/workflow/definitionConfig/types';
import { UploadRequestOptions, ElMessage, ElMessageBox } from 'element-plus';
import { listDefinition, deleteDefinition, active, importDef, unPublishList, publish, add, edit, getInfo, copy } from '@/api/workflow/definition';
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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const previewRef = ref<InstanceType<typeof ProcessPreview>>();
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
const definitionConfigForm = ref<DefinitionConfigForm>({});
type CategoryOption = {
  categoryCode: string;
  categoryName: string;
  children?: CategoryOption[];
};
const loading = ref(true);
const ids = ref<Array<any>>([]);
const deploymentIds = ref<Array<any>>([]);
const keys = ref<Array<any>>([]);
const flowCodeList = ref<Array<any>>([]);
const single = ref(true);
const multiple = ref(true);
const showSearch = ref(true);
const total = ref(0);
const uploadDialogLoading = ref(false);
const processDefinitionList = ref<ProcessDefinitionVO[]>([]);
const processDefinitionHistoryList = ref<ProcessDefinitionVO[]>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const processDefinitionList = ref<FlowDefinitionVo[]>([]);
const categoryOptions = ref<CategoryTreeVO[]>([]);
const categoryName = ref('');
/** éƒ¨ç½²æ–‡ä»¶åˆ†ç±»é€‰æ‹© */
const selectCategory = ref();
const defFormRef = ref<ElFormInstance>();
const activeName = ref('0');
const uploadDialog = reactive<DialogOption>({
  visible: false,
  title: '部署流程文件'
@@ -308,30 +227,49 @@
  title: '历史版本'
});
const definitionConfigDialog = reactive<DialogOption>({
const modelDialog = reactive<DialogOption>({
  visible: false,
  title: '流程定义配置'
  title: ''
});
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<ProcessDefinitionQuery>({
const queryParams = ref<FlowDefinitionQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  key: undefined,
  categoryCode: undefined
  flowName: undefined,
  flowCode: undefined,
  category: undefined
});
const rules = {
  category: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
  flowName: [{ required: true, message: '流程定义名称不能为空', trigger: 'blur' }],
  flowCode: [{ required: true, message: '流程定义编码不能为空', trigger: 'blur' }]
};
const initFormData: FlowDefinitionForm = {
  id: '',
  flowName: '',
  flowCode: '',
  category: '',
  formPath: ''
};
//流程定义参数
const form = ref<FlowDefinitionForm>({
  id: '',
  flowName: '',
  flowCode: '',
  category: '',
  formPath: ''
});
onMounted(() => {
  getList();
  handleQuery();
  getTreeselect();
});
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: CategoryVO) => {
  queryParams.value.categoryCode = data.categoryCode;
  if (data.categoryCode === 'ALL') {
    queryParams.value.categoryCode = '';
const handleNodeClick = (data: CategoryTreeVO) => {
  queryParams.value.category = data.id;
  if (data.id === '0') {
    queryParams.value.category = '';
  }
  handleQuery();
};
@@ -352,22 +290,27 @@
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»ä¸‹æ‹‰æ ‘结构 */
const getTreeselect = async () => {
  const res = await listCategory();
  categoryOptions.value = [];
  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
  categoryOptions.value.push(data);
  const res = await categoryTree();
  categoryOptions.value = res.data;
};
const handleClick = (tab: TabsPaneContext, event: Event) => {
  // v-model处理有延迟 éœ€è¦æ‰‹åŠ¨å¤„ç†
  activeName.value = tab.index;
  handleQuery();
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
  if (activeName.value === '0') {
    getList();
  } else {
    getUnPublishList();
  }
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  queryParams.value.categoryCode = '';
  queryParams.value.category = '';
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  handleQuery();
@@ -375,73 +318,70 @@
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: any) => {
  ids.value = selection.map((item: any) => item.id);
  deploymentIds.value = selection.map((item: any) => item.deploymentId);
  keys.value = selection.map((item: any) => item.key);
  flowCodeList.value = selection.map((item: any) => item.flowCode);
  single.value = selection.length !== 1;
  multiple.value = !selection.length;
};
//分页
const getList = async () => {
  loading.value = true;
  const resp = await listProcessDefinition(queryParams.value);
  const resp = await listDefinition(queryParams.value);
  processDefinitionList.value = resp.rows;
  total.value = resp.total;
  loading.value = false;
};
//获取历史流程定义
const getProcessDefinitionHitoryList = async (id: string, key: string) => {
  processDefinitionDialog.visible = true;
//查询未发布的流程定义列表
const getUnPublishList = async () => {
  loading.value = true;
  const resp = await getListByKey(key);
  if (resp.data && resp.data.length > 0) {
    processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id);
  }
  const resp = await unPublishList(queryParams.value);
  processDefinitionList.value = resp.rows;
  total.value = resp.total;
  loading.value = false;
};
type PreviewType = 'xml' | 'bpmn';
//预览 å…¬å…±æ–¹æ³•
const clickPreview = async (id: string, type: PreviewType) => {
  loading.value = true;
  const resp = await definitionXml(id);
  if (previewRef.value) {
    const xmlStr = resp.data.xmlStr;
    loading.value = false;
    previewRef.value.openDialog(xmlStr, type);
  }
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: ProcessDefinitionVO) => {
const handleDelete = async (row?: FlowDefinitionVo) => {
  const id = row?.id || ids.value;
  const deployIds = row?.deploymentId || deploymentIds.value;
  const defKeys = row?.key || keys.value;
  await proxy?.$modal.confirm('是否确认删除流程定义KEY为【' + defKeys + '】的数据项?');
  const defList = processDefinitionList.value.filter((x) => id.indexOf(x.id) != -1).map((x) => x.flowCode);
  await proxy?.$modal.confirm('是否确认删除流程定义KEY为【' + defList + '】的数据项?');
  loading.value = true;
  await deleteProcessDefinition(deployIds, id).finally(() => (loading.value = false));
  await getList();
  await deleteDefinition(id).finally(() => (loading.value = false));
  await handleQuery();
  proxy?.$modal.msgSuccess('删除成功');
};
/** æŒ‚èµ·/激活 */
const handleProcessDefState = async (row: ProcessDefinitionVO) => {
  let msg: string;
  if (row.suspensionState === 1) {
    msg = `暂停后,此流程下的所有任务都不允许往后流转,您确定挂起【${row.name || row.key}】吗?`;
  } else {
    msg = `启动后,此流程下的所有任务都允许往后流转,您确定激活【${row.name || row.key}】吗?`;
  }
  await proxy?.$modal.confirm(msg);
/** å‘布流程定义 */
const handlePublish = async (row?: FlowDefinitionVo) => {
  await proxy?.$modal.confirm(
    '是否确认发布流程定义KEY为【' + row.flowCode + '】版本为【' + row.version + '】的数据项?,发布后会将已发布流程定义改为失效!'
  );
  loading.value = true;
  await updateDefinitionState(row.id).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess('操作成功');
  await publish(row.id).finally(() => (loading.value = false));
  processDefinitionDialog.visible = false;
  activeName.value = "0"
  await handleQuery();
  proxy?.$modal.msgSuccess('发布成功');
};
/** æµç¨‹å®šä¹‰è½¬æ¢ä¸ºæ¨¡åž‹ */
const handleConvertToModel = async (row: ProcessDefinitionVO) => {
  await proxy?.$modal.confirm('是否确认转换流程定义key为【' + row.key + '】的数据项?');
  await convertToModel(row.id).finally(() => (loading.value = false));
  getList();
  proxy?.$modal.msgSuccess('操作成功');
/** æŒ‚èµ·/激活 */
const handleProcessDefState = async (row: FlowDefinitionVo, status: number | string | boolean) => {
  let msg: string;
  if (status === 0) {
    msg = `暂停后,此流程下的所有任务都不允许往后流转,您确定挂起【${row.flowName || row.flowCode}】吗?`;
  } else {
    msg = `启动后,此流程下的所有任务都允许往后流转,您确定激活【${row.flowName || row.flowCode}】吗?`;
  }
  try {
    loading.value = true;
    await proxy?.$modal.confirm(msg);
    await active(row.id, !!status);
    await handleQuery();
    proxy?.$modal.msgSuccess('操作成功');
  } catch (error) {
    row.activityStatus = status === 0 ? 1 : 0;
    console.error(error);
  } finally {
    loading.value = false;
  }
};
//上传文件前的钩子
@@ -456,15 +396,16 @@
  }
};
//部署文件
const handerDeployProcessFile = (data: UploadRequestOptions): XMLHttpRequest => {
const handlerImportDefinition = (data: UploadRequestOptions): XMLHttpRequest => {
  let formData = new FormData();
  uploadDialogLoading.value = true;
  formData.append('file', data.file);
  formData.append('categoryCode', selectCategory.value);
  deployProcessFile(formData)
  formData.append('category', selectCategory.value);
  importDef(formData)
    .then(() => {
      uploadDialog.visible = false;
      proxy?.$modal.msgSuccess('部署成功');
      activeName.value = "1"
      handleQuery();
    })
    .finally(() => {
@@ -472,46 +413,91 @@
    });
  return;
};
//打开流程定义配置
const handleDefinitionConfigOpen = async (row: ProcessDefinitionVO) => {
  definitionConfigDialog.visible = true;
  definitionConfigForm.value.processKey = row.key;
  definitionConfigForm.value.definitionId = row.id;
  definitionConfigForm.value.version = row.version;
  const resp = await getByDefId(row.id);
  if (resp.data) {
    definitionConfigForm.value = resp.data;
  } else {
    definitionConfigForm.value.tableName = undefined;
    definitionConfigForm.value.remark = undefined;
  }
};
//保存表单
const handlerSaveForm = async () => {
  getByTableNameNotDefId(definitionConfigForm.value.tableName, definitionConfigForm.value.definitionId).then((res) => {
    if (res.data && res.data.length > 0) {
      ElMessageBox.confirm('表名已被【' + res.data[0].processKey + '】版本v' + res.data[0].version + '.0绑定确认后将会删除绑定的流程KEY!', '提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        saveOrUpdate(definitionConfigForm.value).then((resp) => {
          if (resp.code === 200) {
            proxy?.$modal.msgSuccess('操作成功');
            definitionConfigDialog.visible = false;
            getList();
          }
        });
      });
    } else {
      saveOrUpdate(definitionConfigForm.value).then((resp) => {
        if (resp.code === 200) {
          proxy?.$modal.msgSuccess('操作成功');
          definitionConfigDialog.visible = false;
          getList();
        }
      });
/**
 * è®¾è®¡æµç¨‹
 * @param row
 */
const design = async (row: FlowDefinitionVo) => {
  proxy.$router.push({
    path: `/workflow/design/index`,
    query: {
      definitionId: row.id,
      disabled: false
    }
  });
};
/**
 * æŸ¥çœ‹æµç¨‹
 * @param row
 */
const designView = async (row: FlowDefinitionVo) => {
  proxy.$router.push({
    path: `/workflow/design/index`,
    query: {
      definitionId: row.id,
      disabled: true
    }
  });
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  defFormRef.value?.resetFields();
};
/**
 * æ–°å¢ž
 */
const handleAdd = async () => {
  reset();
  modelDialog.visible = true;
  modelDialog.title = '新增流程';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: FlowDefinitionVo) => {
  reset();
  const id = row?.id || ids.value[0];
  const res = await getInfo(id);
  Object.assign(form.value, res.data);
  modelDialog.visible = true;
  modelDialog.title = '修改流程';
};
const handleSubmit = async () => {
  defFormRef.value.validate(async (valid: boolean) => {
    if (valid) {
      loading.value = true;
      if (form.value.id) {
        await edit(form.value).finally(() => loading.value = false);
      } else {
        await add(form.value).finally(() => loading.value = false);
      }
      proxy?.$modal.msgSuccess('操作成功');
      modelDialog.visible = false;
      handleQuery();
    }
  });
};
//复制
const handleCopyDef = async (row: FlowDefinitionVo) => {
  ElMessageBox.confirm(`是否确认复制【${row.flowCode}】版本为【${row.version}】的流程定义!`, '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    loading.value = true;
    copy(row.id).then((resp) => {
      if (resp.code === 200) {
        proxy?.$modal.msgSuccess('操作成功');
        activeName.value = "1"
        handleQuery();
      }
    }).finally(() => loading.value = false);
  });
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExportDef = () => {
  proxy?.download(`/workflow/definition/exportDef/${ids.value[0]}`, {}, `${flowCodeList.value[0]}.json`);
};
</script>
src/views/workflow/processInstance/index.vue
@@ -10,7 +10,7 @@
            class="mt-2"
            node-key="id"
            :data="categoryOptions"
            :props="{ label: 'categoryName', children: 'children' }"
            :props="{ label: 'label', children: 'children' }"
            :expand-on-click-node="false"
            :filter-node-method="filterNode"
            highlight-current
@@ -20,23 +20,31 @@
        </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>
        <!--        <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">
              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
                <el-form-item label="流程定义名称" prop="name">
                  <el-input v-model="queryParams.name" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
                <el-form-item>
                  <el-badge :value="userSelectCount" :max="10" class="item">
                    <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
                  </el-badge>
                </el-form-item>
                <el-form-item label="流程定义KEY" prop="key">
                  <el-input v-model="queryParams.key" placeholder="请输入流程定义KEY" @keyup.enter="handleQuery" />
                <el-form-item label="任务名称" prop="nodeName">
                  <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="流程定义名称" label-width="100" prop="flowName">
                  <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="流程定义编码" label-width="100" prop="flowCode">
                  <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -52,69 +60,77 @@
              <el-col :span="1.5">
                <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
              <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
            </el-row>
          </template>
          <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="流程定义名称">
              <template #default="scope">
                <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="processDefinitionKey" label="流程定义KEY"></el-table-column>
            <el-table-column align="center" prop="processDefinitionVersion" label="版本号" width="90">
              <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template>
            </el-table-column>
            <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
              <template #default="scope">
                <el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
                <el-tag v-else type="danger">挂起</el-tag>
              </template>
            </el-table-column>
            <el-table-column align="center" label="流程状态" min-width="70">
              <template #default="scope">
                <dict-tag :options="wf_business_status" :value="scope.row.businessStatus"></dict-tag>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="startTime" label="启动时间" width="160"></el-table-column>
            <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="结束时间" width="160"></el-table-column>
            <el-table-column label="操作" align="center" :width="130">
              <template #default="scope">
                <el-row v-if="tab === 'running'" :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-popover :ref="`popoverRef${scope.$index}`" trigger="click" placement="left" :width="300">
                      <el-input v-model="deleteReason" resize="none" :rows="3" type="textarea" placeholder="请输入作废原因" />
                      <div style="text-align: right; margin: 5px 0px 0px 0px">
                        <el-button size="small" text @click="cancelPopover(scope.$index)">取消</el-button>
                        <el-button size="small" type="primary" @click="handleInvalid(scope.row)">确认</el-button>
                      </div>
                      <template #reference>
                        <el-button link type="primary" size="small" icon="CircleClose">作废</el-button>
                      </template>
                    </el-popover>
                  </el-col>
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                  </el-col>
                </el-row>
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button link type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
                  </el-col>
                </el-row>
              </template>
            </el-table-column>
          </el-table>
          <pagination
            v-show="total > 0"
            v-model:page="queryParams.pageNum"
            v-model:limit="queryParams.pageSize"
            :total="total"
            @pagination="handleQuery"
          />
          <el-tabs v-model="tab" @tab-click="changeTab">
            <el-tab-pane name="running" label="运行中"></el-tab-pane>
            <el-tab-pane name="finish" label="已完成"></el-tab-pane>
            <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="流程定义名称">
                <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="createByName" 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 v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
                <template #default="scope">
                  <el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
                  <el-tag v-else type="danger">挂起</el-tag>
                </template>
              </el-table-column>
              <el-table-column align="center" label="流程状态" min-width="70">
                <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">
                <template #default="scope">
                  <el-row v-if="tab === 'running'" :gutter="10" class="mb8">
                    <el-col :span="1.5">
                      <el-popover :ref="`popoverRef${scope.$index}`" trigger="click" placement="left" :width="300">
                        <el-input v-model="deleteReason" resize="none" :rows="3" type="textarea" placeholder="请输入作废原因" />
                        <div style="text-align: right; margin: 5px 0px 0px 0px">
                          <el-button size="small" text @click="cancelPopover(scope.$index)">取消</el-button>
                          <el-button size="small" type="primary" @click="handleInvalid(scope.row)">确认</el-button>
                        </div>
                        <template #reference>
                          <el-button type="danger" size="small" icon="CircleClose">作废</el-button>
                        </template>
                      </el-popover>
                    </el-col>
                    <el-col :span="1.5">
                      <el-button type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
                    </el-col>
                  </el-row>
                  <el-row :gutter="10" class="mb8">
                    <el-col :span="1.5">
                      <el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
                    </el-col>
                    <el-col :span="1.5">
                      <el-button type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> å˜é‡ </el-button>
                    </el-col>
                  </el-row>
                </template>
              </el-table-column>
            </el-table>
            <pagination
              v-show="total > 0"
              v-model:page="queryParams.pageNum"
              v-model:limit="queryParams.pageSize"
              :total="total"
              @pagination="handleQuery"
            />
          </el-tabs>
        </el-card>
      </el-col>
    </el-row>
@@ -122,6 +138,7 @@
      <el-table v-loading="loading" :data="processDefinitionHistoryList">
        <el-table-column fixed align="center" type="index" label="序号" width="60"></el-table-column>
        <el-table-column fixed align="center" prop="name" label="流程定义名称"></el-table-column>
        <el-table-column fixed align="center" prop="nodeName" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="key" label="标识Key"></el-table-column>
        <el-table-column align="center" prop="version" label="版本号" width="90">
          <template #default="scope"> v{{ scope.row.version }}.0</template>
@@ -133,42 +150,55 @@
          </template>
        </el-table-column>
        <el-table-column align="center" prop="deploymentTime" label="部署时间" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column fixed="right" label="操作" align="center" width="200" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-button link type="primary" size="small" icon="Sort" @click="handleChange(scope.row.id)">切换</el-button>
          </template>
        </el-table-column>
      </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">
        <template #header>
          <div class="clearfix">
            <span
              >流程定义名称:<el-tag>{{ processDefinitionName }}</el-tag></span
            >
          </div>
        </template>
        <div class="max-h-500px overflow-y-auto">
          <VueJsonPretty :data="formatToJsonObject(variables)" />
        </div>
      </el-card>
    </el-dialog>
    <!-- æµç¨‹å˜é‡ç»“束 -->
    <!-- ç”³è¯·äºº -->
    <UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
  </div>
</template>
<script lang="ts" setup>
import {
  getPageByRunning,
  getPageByFinish,
  deleteRunAndHisInstance,
  deleteFinishAndHisInstance,
  deleteRunInstance
} from '@/api/workflow/processInstance';
import { getListByKey, migrationDefinition } from '@/api/workflow/processDefinition';
import { listCategory } from '@/api/workflow/category';
import { CategoryVO } from '@/api/workflow/category/types';
import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
import { pageByRunning, pageByFinish, deleteByInstanceIds, instanceVariable, invalid } 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';
import workflowCommon from '@/api/workflow/workflowCommon';
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';
//审批记录组件
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
// é®ç½©å±‚
const loading = ref(true);
// é€‰ä¸­æ•°ç»„
const ids = ref<Array<any>>([]);
// é€‰ä¸­ä¸šåŠ¡id数组
const businessKeys = ref<Array<any>>([]);
// é€‰ä¸­å®žä¾‹id数组
const instanceIds = ref<Array<number | string>>([]);
// éžå•个禁用
const single = ref(true);
// éžå¤šä¸ªç¦ç”¨
@@ -177,10 +207,15 @@
const showSearch = ref(true);
// æ€»æ¡æ•°
const total = ref(0);
// æµç¨‹å®šä¹‰id
const processDefinitionId = ref<string>('');
// æµç¨‹å˜é‡æ˜¯å¦æ˜¾ç¤º
const variableVisible = ref(false);
const variableLoading = ref(true);
const variables = ref<string>('');
//流程定义名称
const processDefinitionName = ref();
// æ¨¡åž‹å®šä¹‰è¡¨æ ¼æ•°æ®
const processInstanceList = ref<ProcessInstanceVO[]>([]);
const processInstanceList = ref<FlowInstanceVO[]>([]);
const processDefinitionHistoryList = ref<Array<any>>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const categoryName = ref('');
@@ -191,7 +226,7 @@
});
type CategoryOption = {
  categoryCode: string;
  id: string;
  categoryName: string;
  children?: CategoryOption[];
};
@@ -199,20 +234,27 @@
const tab = ref('running');
// ä½œåºŸåŽŸå› 
const deleteReason = ref('');
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<ProcessInstanceQuery>({
const queryParams = ref<FlowInstanceQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  key: undefined,
  categoryCode: undefined
  nodeName: undefined,
  flowName: undefined,
  flowCode: undefined,
  createByIds: [],
  category: undefined
});
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: CategoryVO) => {
  queryParams.value.categoryCode = data.categoryCode;
  if (data.categoryCode === 'ALL') {
    queryParams.value.categoryCode = '';
const handleNodeClick = (data: CategoryTreeVO) => {
  queryParams.value.category = data.id;
  if (data.id === '0') {
    queryParams.value.category = '';
  }
  handleQuery();
};
@@ -233,11 +275,8 @@
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»ä¸‹æ‹‰æ ‘结构 */
const getTreeselect = async () => {
  const res = await listCategory();
  categoryOptions.value = [];
  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
  categoryOptions.value.push(data);
  const res = await categoryTree();
  categoryOptions.value = res.data;
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -251,22 +290,24 @@
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  queryParams.value.categoryCode = '';
  queryParams.value.category = '';
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  queryParams.value.createByIds = [];
  userSelectCount.value = 0;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: ProcessInstanceVO[]) => {
const handleSelectionChange = (selection: FlowInstanceVO[]) => {
  ids.value = selection.map((item: any) => item.id);
  businessKeys.value = selection.map((item: any) => item.businessKey);
  instanceIds.value = selection.map((item: FlowInstanceVO) => item.id);
  single.value = selection.length !== 1;
  multiple.value = !selection.length;
};
//分页
const getProcessInstanceRunningList = () => {
  loading.value = true;
  getPageByRunning(queryParams.value).then((resp) => {
  pageByRunning(queryParams.value).then((resp) => {
    processInstanceList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -275,7 +316,7 @@
//分页
const getProcessInstanceFinishList = () => {
  loading.value = true;
  getPageByFinish(queryParams.value).then((resp) => {
  pageByFinish(queryParams.value).then((resp) => {
    processInstanceList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -283,15 +324,15 @@
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: any) => {
  const businessKey = row.businessKey || businessKeys.value;
  await proxy?.$modal.confirm('是否确认删除业务id为【' + businessKey + '】的数据项?');
const handleDelete = async (row: FlowInstanceVO) => {
  const instanceIdList = row.id || instanceIds.value;
  await proxy?.$modal.confirm('是否确认删除?');
  loading.value = true;
  if ('running' === tab.value) {
    await deleteRunAndHisInstance(businessKey).finally(() => (loading.value = false));
    await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
    getProcessInstanceRunningList();
  } else {
    await deleteFinishAndHisInstance(businessKey).finally(() => (loading.value = false));
    await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
    getProcessInstanceFinishList();
  }
  proxy?.$modal.msgSuccess('删除成功');
@@ -299,22 +340,22 @@
const changeTab = async (data: string) => {
  processInstanceList.value = [];
  queryParams.value.pageNum = 1;
  if ('running' === data) {
  if ('running' === data.paneName) {
    getProcessInstanceRunningList();
  } else {
    getProcessInstanceFinishList();
  }
};
/** ä½œåºŸæŒ‰é’®æ“ä½œ */
const handleInvalid = async (row: ProcessInstanceVO) => {
  await proxy?.$modal.confirm('是否确认作废业务id为【' + row.businessKey + '】的数据项?');
const handleInvalid = async (row: FlowInstanceVO) => {
  await proxy?.$modal.confirm('是否确认作废?');
  loading.value = true;
  if ('running' === tab.value) {
    let param = {
      businessKey: row.businessKey,
      deleteReason: deleteReason.value
      id: row.id,
      comment: deleteReason.value
    };
    await deleteRunInstance(param).finally(() => (loading.value = false));
    await invalid(param).finally(() => (loading.value = false));
    getProcessInstanceRunningList();
    proxy?.$modal.msgSuccess('操作成功');
  }
@@ -322,41 +363,53 @@
const cancelPopover = async (index: any) => {
  (proxy?.$refs[`popoverRef${index}`] as any).hide(); //关闭弹窗
};
//获取流程定义
const getProcessDefinitionHitoryList = (id: string, key: string) => {
  processDefinitionDialog.visible = true;
  processDefinitionId.value = id;
  loading.value = true;
  getListByKey(key).then((resp) => {
    if (resp.data && resp.data.length > 0) {
      processDefinitionHistoryList.value = resp.data.filter((item: any) => item.id !== id);
    }
    loading.value = false;
  });
};
//切换流程版本
const handleChange = async (id: string) => {
  await proxy?.$modal.confirm('是否确认切换?');
  loading.value = true;
  migrationDefinition(processDefinitionId.value, id).then((resp) => {
    proxy?.$modal.msgSuccess('操作成功');
    getProcessInstanceRunningList();
    processDefinitionDialog.visible = false;
    loading.value = false;
  });
};
/** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
const handleView = (row) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: 'view'
    type: 'view',
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
//查询流程变量
const handleInstanceVariable = async (row: FlowInstanceVO) => {
  variableLoading.value = true;
  variableVisible.value = true;
  processDefinitionName.value = row.flowName;
  let data = await instanceVariable(row.id);
  variables.value = data.data.variable;
  variableLoading.value = false;
};
/**
 * json转为对象
 * @param data åŽŸå§‹æ•°æ®
 */
function formatToJsonObject(data: string) {
  try {
    return JSON.parse(data);
  } catch (error) {
    return data;
  }
}
//打开申请人选择
const openUserSelect = () => {
  userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
  userSelectCount.value = 0;
  if (data && data.length > 0) {
    userSelectCount.value = data.length;
    selectUserIds.value = data.map((item) => item.userId);
    queryParams.value.createByIds = selectUserIds.value;
  }
};
onMounted(() => {
  getProcessInstanceRunningList();
  getTreeselect();
src/views/workflow/task/allTaskWaiting.vue
@@ -1,25 +1,19 @@
<template>
  <div class="p-2">
    <div class="mb-[10px]">
      <el-card shadow="hover" class="text-center">
        <el-radio-group v-model="tab" @change="changeTab(tab)">
          <el-radio-button value="waiting">待办任务</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">
          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
            <el-form-item label="任务名称" prop="name">
              <el-input v-model="queryParams.name" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            <el-form-item>
              <el-badge :value="userSelectCount" :max="10" class="item">
                <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
              </el-badge>
            </el-form-item>
            <el-form-item label="流程定义名称" label-width="100" prop="processDefinitionName">
              <el-input v-model="queryParams.processDefinitionName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            <el-form-item label="任务名称" prop="nodeName">
              <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义KEY" label-width="100" prop="processDefinitionKey">
              <el-input v-model="queryParams.processDefinitionKey" placeholder="请输入流程定义KEY" @keyup.enter="handleQuery" />
            <el-form-item label="流程定义名称" label-width="100" prop="flowName">
              <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -32,122 +26,103 @@
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUpdate">修改办理人</el-button>
          <el-col :span="1.5" v-if="tab === 'waiting'">
            <el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUpdate">修改办理人 </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
        </el-row>
      </template>
      <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" align="center" label="流程定义名称">
          <template #default="scope">
            <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="processDefinitionKey" label="流程定义KEY"></el-table-column>
        <el-table-column align="center" prop="name" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="assigneeName" label="办理人">
          <template v-if="tab === 'waiting'" #default="scope">
            <template v-if="scope.row.participantVo && scope.row.assignee === null">
              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
                {{ item }}
              </el-tag>
      <el-tabs v-model="tab" @tab-click="changeTab">
        <el-tab-pane name="waiting" label="待办任务"> </el-tab-pane>
        <el-tab-pane name="finish" label="已办任务"> </el-tab-pane>
        <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 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" label="办理人">
            <template #default="scope">
              <template v-if="tab === 'waiting'">
                <template v-if="scope.row.assigneeNames">
                  <el-tag v-for="(name, index) in scope.row.assigneeNames.split(',')" :key="index" type="success">
                    {{ name }}
                  </el-tag>
                </template>
                <template v-else>
                  <el-tag type="success"> æ— </el-tag>
                </template>
              </template>
              <template v-else>
                <el-tag type="success"> {{ scope.row.approveName }}</el-tag>
              </template>
            </template>
            <template v-else>
              <el-tag type="success">
                {{ scope.row.assigneeName || '无' }}
              </el-tag>
          </el-table-column>
          <el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
            <template #default="scope">
              <dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
            </template>
          </template>
          <template v-else-if="tab === 'finish'" #default="scope">
            <el-tag type="success">
              {{ scope.row.assigneeName || '无' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column align="center" label="流程状态" min-width="70">
          <template #default="scope">
            <dict-tag v-if="tab === 'waiting'" :options="wf_business_status" :value="scope.row.businessStatus"></dict-tag>
            <el-tag v-else type="success">已完成</el-tag>
          </template>
        </el-table-column>
        <el-table-column v-if="tab === 'waiting'" align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
        <el-table-column v-if="tab === 'finish'" align="center" prop="startTime" label="创建时间" width="160"></el-table-column>
        <el-table-column label="操作" align="center" :width="tab === 'finish' ? '80' : '151'">
          <template #default="scope">
            <el-row :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button link type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
              </el-col>
              <el-col v-if="tab === 'waiting'" :span="1.5">
                <el-button link type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)">流程变量</el-button>
              </el-col>
            </el-row>
            <el-row v-if="scope.row.multiInstance" :gutter="10" class="mb8">
              <el-col :span="1.5">
                <el-button link type="primary" size="small" icon="Remove" @click="deleteMultiInstanceUser(scope.row)">减签</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button link type="primary" size="small" icon="CirclePlus" @click="addMultiInstanceUser(scope.row)">加签</el-button>
              </el-col>
            </el-row>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
        v-model:page="queryParams.pageNum"
        v-model:limit="queryParams.pageSize"
        :total="total"
        @pagination="handleQuery"
      />
          </el-table-column>
          <el-table-column v-if="tab === 'finish'" align="center" label="任务状态" prop="flowTaskStatus" min-width="70">
            <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 label="操作" align="center" :width="tab === 'finish' ? '88' : '188'">
            <template #default="scope">
              <el-row :gutter="10" class="mb8">
                <el-col :span="1.5" v-if="tab === 'waiting' || tab === 'finish'">
                  <el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
                </el-col>
                <el-col :span="1.5" v-if="tab === 'waiting'">
                  <el-button type="primary" size="small" icon="Setting" @click="handleMeddle(scope.row)">流程干预 </el-button>
                </el-col>
              </el-row>
            </template>
          </el-table-column>
        </el-table>
        <pagination
          v-show="total > 0"
          v-model:page="queryParams.pageNum"
          v-model:limit="queryParams.pageSize"
          :total="total"
          @pagination="handleQuery"
        />
      </el-tabs>
    </el-card>
    <!-- åŠ ç­¾ç»„ä»¶ -->
    <multiInstanceUser ref="multiInstanceUserRef" :title="title" @submit-callback="handleQuery" />
    <!-- é€‰äººç»„ä»¶ -->
    <UserSelect ref="userSelectRef" :multiple="false" @confirm-call-back="submitCallback"></UserSelect>
    <!-- æµç¨‹å˜é‡å¼€å§‹ -->
    <el-dialog v-model="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
      <el-card v-loading="variableLoading" class="box-card">
        <template #header>
          <div class="clearfix">
            <span
              >流程定义名称:<el-tag>{{ processDefinitionName }}</el-tag></span
            >
          </div>
        </template>
        <div v-for="(v, index) in variableList" :key="index">
          <el-form v-if="v.key !== '_FLOWABLE_SKIP_EXPRESSION_ENABLED'" :label-position="'right'" label-width="150px">
            <el-form-item :label="v.key + ':'">
              {{ v.value }}
            </el-form-item>
          </el-form>
        </div>
      </el-card>
    </el-dialog>
    <!-- æµç¨‹å˜é‡ç»“束 -->
    <!-- é€‰äººç»„ä»¶ -->
    <processMeddle ref="processMeddleRef" @submitCallback="getWaitingList"></processMeddle>
    <!-- ç”³è¯·äºº -->
    <UserSelect ref="applyUserSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
  </div>
</template>
<script lang="ts" setup>
import { getPageByAllTaskWait, getPageByAllTaskFinish, updateAssignee, getInstanceVariable } from '@/api/workflow/task';
import MultiInstanceUser from '@/components/Process/multiInstanceUser.vue';
import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect';
import { TaskQuery, TaskVO, VariableVo } from '@/api/workflow/task/types';
import { TaskQuery } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
//审批记录组件
//加签组件
const multiInstanceUserRef = ref<InstanceType<typeof MultiInstanceUser>>();
import processMeddle from '@/components/Process/processMeddle';
import { UserVO } from '@/api/system/user/types';
import { TabsPaneContext } from 'element-plus';
//选人组件
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
//流程干预组件
const processMeddleRef = ref<InstanceType<typeof processMeddle>>();
//选人组件
const applyUserSelectRef = ref<InstanceType<typeof UserSelect>>();
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
// é®ç½©å±‚
const loading = ref(true);
// é€‰ä¸­æ•°ç»„
@@ -163,40 +138,21 @@
// æ¨¡åž‹å®šä¹‰è¡¨æ ¼æ•°æ®
const taskList = ref([]);
const title = ref('');
// æµç¨‹å˜é‡æ˜¯å¦æ˜¾ç¤º
const variableVisible = ref(false);
const variableLoading = ref(true);
// æµç¨‹å˜é‡
const variableList = ref<VariableVo>({
  key: '',
  value: ''
});
//流程定义名称
const processDefinitionName = ref();
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<TaskQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  processDefinitionName: undefined,
  processDefinitionKey: undefined
  nodeName: undefined,
  flowName: undefined,
  flowCode: undefined,
  createByIds: []
});
const tab = ref('waiting');
//加签
const addMultiInstanceUser = (row: TaskVO) => {
  if (multiInstanceUserRef.value) {
    title.value = '加签人员';
    multiInstanceUserRef.value.getAddMultiInstanceList(row.id, []);
  }
};
//减签
const deleteMultiInstanceUser = (row: TaskVO) => {
  if (multiInstanceUserRef.value) {
    title.value = '减签人员';
    multiInstanceUserRef.value.getDeleteMultiInstanceList(row.id);
  }
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  if ('waiting' === tab.value) {
@@ -210,6 +166,8 @@
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  queryParams.value.createByIds = [];
  userSelectCount.value = 0;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
@@ -218,10 +176,10 @@
  single.value = selection.length !== 1;
  multiple.value = !selection.length;
};
const changeTab = async (data: string) => {
const changeTab = async (data: TabsPaneContext) => {
  taskList.value = [];
  queryParams.value.pageNum = 1;
  if ('waiting' === data) {
  if ('waiting' === data.paneName) {
    getWaitingList();
  } else {
    getFinishList();
@@ -230,7 +188,7 @@
//分页
const getWaitingList = () => {
  loading.value = true;
  getPageByAllTaskWait(queryParams.value).then((resp) => {
  pageByAllTaskWait(queryParams.value).then((resp) => {
    taskList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -238,7 +196,7 @@
};
const getFinishList = () => {
  loading.value = true;
  getPageByAllTaskFinish(queryParams.value).then((resp) => {
  pageByAllTaskFinish(queryParams.value).then((resp) => {
    taskList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -260,26 +218,33 @@
    proxy?.$modal.msgWarning('请选择用户!');
  }
};
//查询流程变量
const handleInstanceVariable = async (row: TaskVO) => {
  variableLoading.value = true;
  variableVisible.value = true;
  processDefinitionName.value = row.processDefinitionName;
  let data = await getInstanceVariable(row.id);
  variableList.value = data.data;
  variableLoading.value = false;
};
/** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
const handleView = (row) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: 'view'
    type: 'view',
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
const handleMeddle = (row) => {
  processMeddleRef.value.open(row.id);
};
//打开申请人选择
const openUserSelect = () => {
  applyUserSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
  userSelectCount.value = 0;
  if (data && data.length > 0) {
    userSelectCount.value = data.length;
    selectUserIds.value = data.map((item) => item.userId);
    queryParams.value.createByIds = selectUserIds.value;
  }
};
onMounted(() => {
  getWaitingList();
});
src/views/workflow/task/myDocument.vue
@@ -10,7 +10,7 @@
            class="mt-2"
            node-key="id"
            :data="categoryOptions"
            :props="{ label: 'categoryName', children: 'children' }"
            :props="{ label: 'label', children: 'children' }"
            :expand-on-click-node="false"
            :filter-node-method="filterNode"
            highlight-current
@@ -24,8 +24,8 @@
          <div v-show="showSearch" class="mb-[10px]">
            <el-card shadow="hover">
              <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
                <el-form-item label="流程定义名称" prop="name">
                  <el-input v-model="queryParams.name" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
                <el-form-item label="流程定义编码" prop="flowCode">
                  <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -38,7 +38,7 @@
        <el-card shadow="hover">
          <template #header>
            <el-row :gutter="10" class="mb8">
              <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
              <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
            </el-row>
          </template>
@@ -46,14 +46,10 @@
            <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 v-if="false" align="center" prop="id" label="id"></el-table-column>
            <el-table-column :show-overflow-tooltip="true" align="center" label="流程定义名称">
              <template #default="scope">
                <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="processDefinitionKey" label="流程定义KEY"></el-table-column>
            <el-table-column align="center" prop="processDefinitionVersion" label="版本号" width="90">
              <template #default="scope"> v{{ scope.row.processDefinitionVersion }}.0</template>
            <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="version" label="版本号" width="90">
              <template #default="scope"> v{{ scope.row.version }}.0</template>
            </el-table-column>
            <el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
              <template #default="scope">
@@ -63,33 +59,30 @@
            </el-table-column>
            <el-table-column align="center" label="流程状态" min-width="70">
              <template #default="scope">
                <dict-tag :options="wf_business_status" :value="scope.row.businessStatus"></dict-tag>
                <dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
              </template>
            </el-table-column>
            <el-table-column align="center" prop="startTime" label="启动时间" width="160"></el-table-column>
            <el-table-column v-if="tab === 'finish'" align="center" prop="endTime" label="结束时间" width="160"></el-table-column>
            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
            <el-table-column align="center" prop="createTime" label="启动时间" width="160"></el-table-column>
            <el-table-column label="操作" align="center" width="162">
              <template #default="scope">
                <el-tooltip
                  v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'"
                  content="修改"
                  placement="top"
                >
                  <el-button link type="primary" icon="Edit" @click="handleOpen(scope.row, 'update')"></el-button>
                </el-tooltip>
                <el-tooltip
                  v-if="scope.row.businessStatus === 'draft' || scope.row.businessStatus === 'cancel' || scope.row.businessStatus === 'back'"
                  content="删除"
                  placement="top"
                >
                  <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
                </el-tooltip>
                <el-tooltip placement="top" content="查看">
                  <el-button link type="primary" icon="View" @click="handleOpen(scope.row, 'view')"></el-button>
                </el-tooltip>
                <el-tooltip v-if="scope.row.businessStatus === 'waiting'" content="撤销" placement="top">
                  <el-button link type="primary" icon="Notification" @click="handleCancelProcessApply(scope.row.businessKey)"></el-button>
                </el-tooltip>
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5" v-if="scope.row.flowStatus === 'draft' || scope.row.flowStatus === 'cancel' || scope.row.flowStatus === 'back'">
                    <el-button type="primary" size="small" icon="Edit" @click="handleOpen(scope.row, 'update')">编辑</el-button>
                  </el-col>
                  <el-col :span="1.5" v-if="scope.row.flowStatus === 'draft' || scope.row.flowStatus === 'cancel' || scope.row.flowStatus === 'back'">
                    <el-button type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
                  </el-col>
                </el-row>
                <el-row :gutter="10" class="mb8">
                  <el-col :span="1.5">
                    <el-button type="primary" size="small" icon="View" @click="handleOpen(scope.row, 'view')">查看</el-button>
                  </el-col>
                  <el-col :span="1.5" v-if="scope.row.flowStatus === 'waiting'">
                    <el-button type="primary" size="small" icon="Notification" @click="handleCancelProcessApply(scope.row.businessId)"
                      >撤销</el-button
                    >
                  </el-col>
                </el-row>
              </template>
            </el-table-column>
          </el-table>
@@ -109,10 +102,10 @@
</template>
<script lang="ts" setup>
import { getPageByCurrent, deleteRunAndHisInstance, cancelProcessApply } from '@/api/workflow/processInstance';
import { listCategory } from '@/api/workflow/category';
import { CategoryVO } from '@/api/workflow/category/types';
import { ProcessInstanceQuery, ProcessInstanceVO } from '@/api/workflow/processInstance/types';
import { pageByCurrent, deleteByInstanceIds, cancelProcessApply } 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';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -123,7 +116,8 @@
// é®ç½©å±‚
const loading = ref(true);
// é€‰ä¸­æ•°ç»„
const businessKeys = ref<Array<any>>([]);
const businessIds = ref<Array<number | string>>([]);
const instanceIds = ref<Array<number | string>>([]);
// éžå•个禁用
const single = ref(true);
// éžå¤šä¸ªç¦ç”¨
@@ -133,24 +127,18 @@
// æ€»æ¡æ•°
const total = ref(0);
// æ¨¡åž‹å®šä¹‰è¡¨æ ¼æ•°æ®
const processInstanceList = ref<ProcessInstanceVO[]>([]);
const processInstanceList = ref<FlowInstanceVO[]>([]);
const categoryOptions = ref<CategoryOption[]>([]);
const categoryOptions = ref<CategoryTreeVO[]>([]);
const categoryName = ref('');
interface CategoryOption {
  categoryCode: string;
  categoryName: string;
  children?: CategoryOption[];
}
const tab = ref('running');
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<ProcessInstanceQuery>({
const queryParams = ref<FlowInstanceQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  categoryCode: undefined
  flowCode: undefined,
  category: undefined
});
onMounted(() => {
@@ -159,10 +147,10 @@
});
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: CategoryVO) => {
  queryParams.value.categoryCode = data.categoryCode;
  if (data.categoryCode === 'ALL') {
    queryParams.value.categoryCode = '';
const handleNodeClick = (data: CategoryTreeVO) => {
  queryParams.value.category = data.id;
  if (data.id === '0') {
    queryParams.value.category = '';
  }
  handleQuery();
};
@@ -183,11 +171,8 @@
/** æŸ¥è¯¢æµç¨‹åˆ†ç±»ä¸‹æ‹‰æ ‘结构 */
const getTreeselect = async () => {
  const res = await listCategory();
  categoryOptions.value = [];
  const data: CategoryOption = { categoryCode: 'ALL', categoryName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<CategoryOption>(res.data, 'id', 'parentId');
  categoryOptions.value.push(data);
  const res = await categoryTree();
  categoryOptions.value = res.data;
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -197,21 +182,22 @@
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  queryParams.value.categoryCode = '';
  queryParams.value.category = '';
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: ProcessInstanceVO[]) => {
  businessKeys.value = selection.map((item: any) => item.businessKey);
const handleSelectionChange = (selection: FlowInstanceVO[]) => {
  businessIds.value = selection.map((item: any) => item.businessId);
  instanceIds.value = selection.map((item: FlowInstanceVO) => item.id);
  single.value = selection.length !== 1;
  multiple.value = !selection.length;
};
//分页
const getList = () => {
  loading.value = true;
  getPageByCurrent(queryParams.value).then((resp) => {
  pageByCurrent(queryParams.value).then((resp) => {
    processInstanceList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -219,23 +205,27 @@
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: ProcessInstanceVO) => {
  const businessKey = row.businessKey || businessKeys.value;
  await proxy?.$modal.confirm('是否确认删除业务id为【' + businessKey + '】的数据项?');
const handleDelete = async (row: FlowInstanceVO) => {
  const instanceIdList = row.id || instanceIds.value;
  await proxy?.$modal.confirm('是否确认删除?');
  loading.value = true;
  if ('running' === tab.value) {
    await deleteRunAndHisInstance(businessKey).finally(() => (loading.value = false));
    await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
    getList();
  }
  proxy?.$modal.msgSuccess('删除成功');
};
/** æ’¤é”€æŒ‰é’®æ“ä½œ */
const handleCancelProcessApply = async (businessKey: string) => {
const handleCancelProcessApply = async (businessId: string) => {
  await proxy?.$modal.confirm('是否确认撤销当前单据?');
  loading.value = true;
  if ('running' === tab.value) {
    await cancelProcessApply(businessKey).finally(() => (loading.value = false));
    let data = {
      businessId: businessId,
      message: '申请人撤销流程!'
    };
    await cancelProcessApply(data).finally(() => (loading.value = false));
    getList();
  }
  proxy?.$modal.msgSuccess('撤销成功');
@@ -244,11 +234,11 @@
//办理
const handleOpen = async (row, type) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: type
    type: type,
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
src/views/workflow/task/taskCopyList.vue
@@ -4,14 +4,14 @@
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
            <el-form-item label="任务名称" prop="name">
              <el-input v-model="queryParams.name" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            <el-form-item label="任务名称" prop="nodeName">
              <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义名称" label-width="100" prop="processDefinitionName">
              <el-input v-model="queryParams.processDefinitionName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            <el-form-item label="流程定义名称" label-width="100" prop="flowName">
              <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义KEY" label-width="100" prop="processDefinitionKey">
              <el-input v-model="queryParams.processDefinitionKey" placeholder="请输入流程定义KEY" @keyup.enter="handleQuery" />
            <el-form-item label="流程定义编码" label-width="100" prop="flowCode">
              <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -24,37 +24,22 @@
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
        </el-row>
      </template>
      <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" align="center" label="流程定义名称">
          <template #default="scope">
            <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
          </template>
        <el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
        <el-table-column align="center" prop="flowCode" label="流程定义KEY"></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="processDefinitionKey" label="流程定义KEY"></el-table-column>
        <el-table-column align="center" prop="name" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="assigneeName" label="办理人">
          <template #default="scope">
            <template v-if="scope.row.participantVo && scope.row.assignee === null">
              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
                {{ item }}
              </el-tag>
            </template>
            <template v-else>
              <el-tag type="success">
                {{ scope.row.assigneeName || '无' }}
              </el-tag>
            </template>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
        <el-table-column align="center" label="流程状态" min-width="70">
          <template #default="scope">
            <dict-tag :options="wf_business_status" :value="scope.row.businessStatus"></dict-tag>
            <dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" width="200">
@@ -75,7 +60,7 @@
</template>
<script lang="ts" setup>
import { getPageByTaskCopy } from '@/api/workflow/task';
import { pageByTaskCopy } from '@/api/workflow/task';
import { TaskQuery } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
@@ -101,9 +86,9 @@
const queryParams = ref<TaskQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  processDefinitionName: undefined,
  processDefinitionKey: undefined
  nodeName: undefined,
  flowName: undefined,
  flowCode: undefined
});
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
@@ -125,7 +110,7 @@
//分页
const getTaskCopyList = () => {
  loading.value = true;
  getPageByTaskCopy(queryParams.value).then((resp) => {
  pageByTaskCopy(queryParams.value).then((resp) => {
    taskList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
@@ -135,11 +120,11 @@
/** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
const handleView = (row) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: 'view'
    type: 'view',
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
src/views/workflow/task/taskFinish.vue
@@ -4,14 +4,19 @@
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
            <el-form-item label="任务名称" prop="name">
              <el-input v-model="queryParams.name" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            <el-form-item>
              <el-badge :value="userSelectCount" :max="10" class="item">
                <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
              </el-badge>
            </el-form-item>
            <el-form-item label="流程定义名称" label-width="100" prop="processDefinitionName">
              <el-input v-model="queryParams.processDefinitionName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            <el-form-item label="任务名称" prop="nodeName">
              <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义KEY" label-width="100" prop="processDefinitionKey">
              <el-input v-model="queryParams.processDefinitionKey" placeholder="请输入流程定义KEY" @keyup.enter="handleQuery" />
            <el-form-item label="流程定义名称" label-width="100" prop="flowName">
              <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义编码" label-width="100" prop="flowCode">
              <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -24,28 +29,38 @@
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
        </el-row>
      </template>
      <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" align="center" label="流程定义名称">
          <template #default="scope">
            <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
          </template>
        <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 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="processDefinitionKey" label="流程定义KEY"></el-table-column>
        <el-table-column align="center" prop="name" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="assigneeName" label="办理人">
        <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="approverName" label="办理人">
          <template #default="scope">
            <el-tag type="success">
              {{ scope.row.assigneeName || '无' }}
              {{ scope.row.approveName || '无' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="startTime" label="创建时间" width="160"></el-table-column>
        <el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
          <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">
          <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 label="操作" align="center" width="200">
          <template #default="scope">
            <el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
@@ -60,17 +75,26 @@
        @pagination="handleQuery"
      />
    </el-card>
    <!-- ç”³è¯·äºº -->
    <UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
  </div>
</template>
<script lang="ts" setup>
import { getPageByTaskFinish } from '@/api/workflow/task';
import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
import { pageByTaskFinish } from '@/api/workflow/task';
import { TaskQuery, FlowTaskVO } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
//审批记录组件
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
import UserSelect from '@/components/UserSelect';
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
// é®ç½©å±‚
const loading = ref(true);
// é€‰ä¸­æ•°ç»„
@@ -89,10 +113,15 @@
const queryParams = ref<TaskQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  processDefinitionName: undefined,
  processDefinitionKey: undefined
  nodeName: undefined,
  flowName: undefined,
  flowCode: undefined,
  createByIds: []
});
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getFinishList();
@@ -102,6 +131,8 @@
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  queryParams.value.createByIds = [];
  userSelectCount.value = 0;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
@@ -112,24 +143,36 @@
};
const getFinishList = () => {
  loading.value = true;
  getPageByTaskFinish(queryParams.value).then((resp) => {
  pageByTaskFinish(queryParams.value).then((resp) => {
    taskList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
  });
};
/** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
const handleView = (row: TaskVO) => {
const handleView = (row: FlowTaskVO) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: 'view'
    type: 'view',
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
//打开申请人选择
const openUserSelect = () => {
  userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
  userSelectCount.value = 0;
  if (data && data.length > 0) {
    userSelectCount.value = data.length;
    selectUserIds.value = data.map((item) => item.userId);
    queryParams.value.createByIds = selectUserIds.value;
  }
};
onMounted(() => {
  getFinishList();
});
src/views/workflow/task/taskWaiting.vue
@@ -4,14 +4,19 @@
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form v-show="showSearch" ref="queryFormRef" :model="queryParams" :inline="true">
            <el-form-item label="任务名称" prop="name">
              <el-input v-model="queryParams.name" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            <el-form-item>
              <el-badge :value="userSelectCount" :max="10" class="item">
                <el-button type="primary" @click="openUserSelect">选择申请人</el-button>
              </el-badge>
            </el-form-item>
            <el-form-item label="流程定义名称" label-width="100" prop="processDefinitionName">
              <el-input v-model="queryParams.processDefinitionName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            <el-form-item label="任务名称" prop="nodeName">
              <el-input v-model="queryParams.nodeName" placeholder="请输入任务名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义KEY" label-width="100" prop="processDefinitionKey">
              <el-input v-model="queryParams.processDefinitionKey" placeholder="请输入流程定义KEY" @keyup.enter="handleQuery" />
            <el-form-item label="流程定义名称" label-width="100" prop="flowName">
              <el-input v-model="queryParams.flowName" placeholder="请输入流程定义名称" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="流程定义编码" label-width="100" prop="flowCode">
              <el-input v-model="queryParams.flowCode" placeholder="请输入流程定义编码" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -24,37 +29,32 @@
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <right-toolbar v-model:showSearch="showSearch" @query-table="handleQuery"></right-toolbar>
          <right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
        </el-row>
      </template>
      <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" align="center" label="流程定义名称">
        <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="nodeName" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
        <el-table-column align="center" label="办理人">
          <template #default="scope">
            <span>{{ scope.row.processDefinitionName }}v{{ scope.row.processDefinitionVersion }}.0</span>
          </template>
        </el-table-column>
        <el-table-column align="center" prop="processDefinitionKey" label="流程定义KEY"></el-table-column>
        <el-table-column align="center" prop="name" label="任务名称"></el-table-column>
        <el-table-column align="center" prop="assigneeName" label="办理人">
          <template #default="scope">
            <template v-if="scope.row.participantVo && scope.row.assignee === null">
              <el-tag v-for="(item, index) in scope.row.participantVo.candidateName" :key="index" type="success">
                {{ item }}
            <template v-if="scope.row.assigneeNames">
              <el-tag v-for="(name, index) in scope.row.assigneeNames.split(',')" :key="index" type="success">
                {{ name }}
              </el-tag>
            </template>
            <template v-else>
              <el-tag type="success">
                {{ scope.row.assigneeName || '无' }}
              </el-tag>
              <el-tag type="success"> æ— </el-tag>
            </template>
          </template>
        </el-table-column>
        <el-table-column align="center" label="流程状态" min-width="70">
        <el-table-column align="center" label="流程状态" prop="flowStatusName" min-width="70">
          <template #default="scope">
            <dict-tag :options="wf_business_status" :value="scope.row.businessStatus"></dict-tag>
            <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>
@@ -72,16 +72,24 @@
        @pagination="handleQuery"
      />
    </el-card>
    <!-- ç”³è¯·äºº -->
    <UserSelect ref="userSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
  </div>
</template>
<script lang="ts" setup>
import { getPageByTaskWait } from '@/api/workflow/task';
import { TaskQuery, TaskVO } from '@/api/workflow/task/types';
import { pageByTaskWait } from '@/api/workflow/task';
import { TaskQuery, FlowTaskVO } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
import UserSelect from '@/components/UserSelect';
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
//提交组件
const queryFormRef = ref<ElFormInstance>();
// é®ç½©å±‚
@@ -98,13 +106,19 @@
const total = ref(0);
// æ¨¡åž‹å®šä¹‰è¡¨æ ¼æ•°æ®
const taskList = ref([]);
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
const userSelectCount = ref(0);
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<TaskQuery>({
  pageNum: 1,
  pageSize: 10,
  name: undefined,
  processDefinitionName: undefined,
  processDefinitionKey: undefined
  nodeName: undefined,
  flowName: undefined,
  flowCode: undefined,
  createByIds: []
});
onMounted(() => {
  getWaitingList();
@@ -118,6 +132,8 @@
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  queryParams.value.pageSize = 10;
  queryParams.value.createByIds = [];
  userSelectCount.value = 0;
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
@@ -129,21 +145,34 @@
//分页
const getWaitingList = () => {
  loading.value = true;
  getPageByTaskWait(queryParams.value).then((resp) => {
  pageByTaskWait(queryParams.value).then((resp) => {
    taskList.value = resp.rows;
    total.value = resp.total;
    loading.value = false;
  });
};
//办理
const handleOpen = async (row: TaskVO) => {
const handleOpen = async (row: FlowTaskVO) => {
  const routerJumpVo = reactive<RouterJumpVo>({
    wfDefinitionConfigVo: row.wfDefinitionConfigVo,
    wfNodeConfigVo: row.wfNodeConfigVo,
    businessKey: row.businessKey,
    businessId: row.businessId,
    taskId: row.id,
    type: 'approval'
    type: 'approval',
    formCustom: row.formCustom,
    formPath: row.formPath
  });
  workflowCommon.routerJump(routerJumpVo, proxy);
};
//打开申请人选择
const openUserSelect = () => {
  userSelectRef.value.open();
};
//确认选择申请人
const userSelectCallBack = (data: UserVO[]) => {
  userSelectCount.value = 0;
  if (data && data.length > 0) {
    userSelectCount.value = data.length;
    selectUserIds.value = data.map((item) => item.userId);
    queryParams.value.createByIds = selectUserIds.value;
  }
};
</script>
tsconfig.json
@@ -2,7 +2,7 @@
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
//    "useDefineForClassFields": true,
    //    "useDefineForClassFields": true,
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "preserve",
vite.config.ts
@@ -64,16 +64,6 @@
        'echarts',
        'vue-i18n',
        '@vueup/vue-quill',
        'bpmn-js/lib/Viewer',
        'bpmn-js/lib/Modeler.js',
        'bpmn-js-properties-panel',
        'min-dash',
        'diagram-js/lib/navigation/movecanvas',
        'diagram-js/lib/navigation/zoomscroll',
        'bpmn-js/lib/features/palette/PaletteProvider',
        'bpmn-js/lib/features/context-pad/ContextPadProvider',
        'diagram-js/lib/draw/BaseRenderer',
        'tiny-svg',
        'image-conversion',
        'element-plus/es/components/**/css'
      ]
vite/plugins/i18n.ts
ÎļþÒÑɾ³ý
vite/plugins/index.ts
@@ -6,7 +6,6 @@
import createSvgIconsPlugin from './svg-icon';
import createCompression from './compression';
import createSetupExtend from './setup-extend';
import createI18n from './i18n';
import path from 'path';
export default (viteEnv: any, isBuild = false): [] => {
@@ -19,6 +18,5 @@
  vitePlugins.push(createIcons());
  vitePlugins.push(createSvgIconsPlugin(path, isBuild));
  vitePlugins.push(createSetupExtend());
  vitePlugins.push(createI18n(path));
  return vitePlugins;
};