Browse Source

将powerJob迁移至光耀平台

SunZehao 7 months ago
parent
commit
20d72dd8a8
38 changed files with 6553 additions and 1 deletions
  1. 2 0
      .env.dev
  2. 3 0
      .env.local
  3. 3 0
      .env.prod
  4. 3 0
      .env.test
  5. 4 0
      package.json
  6. 50 0
      src/api/model/container/index.ts
  7. 29 0
      src/api/model/homeIndex/index.ts
  8. 163 0
      src/api/model/taskManagement/index.ts
  9. 113 0
      src/api/model/workflowManagement/index.ts
  10. 1 0
      src/assets/powerJob/skip.svg
  11. 1 0
      src/assets/powerJob/start.svg
  12. 5 0
      src/main.ts
  13. 72 1
      src/router/modules/remaining.ts
  14. 43 0
      src/utils/common.ts
  15. 8 0
      src/views/Login/components/LoginForm.vue
  16. 59 0
      src/views/calculationAnalysis/calculationManagement/JSEditor.vue
  17. 72 0
      src/views/calculationAnalysis/calculationManagement/MonacoEditor.vue
  18. 518 0
      src/views/calculationAnalysis/calculationManagement/PowerWorkflow.vue
  19. 47 0
      src/views/calculationAnalysis/calculationManagement/TimeExpressionValidator.vue
  20. 839 0
      src/views/calculationAnalysis/calculationManagement/WorkflowEditor.vue
  21. 283 0
      src/views/calculationAnalysis/calculationManagement/WorkflowManager.vue
  22. 417 0
      src/views/calculationAnalysis/calculationManagement/workflowManageDatajson.json
  23. 327 0
      src/views/calculationAnalysis/container/ContainerManager.vue
  24. 72 0
      src/views/calculationAnalysis/container/ContainerTemplate.vue
  25. 39 0
      src/views/calculationAnalysis/container/dataJson.json
  26. 33 0
      src/views/calculationAnalysis/homeIndex/dataJson.json
  27. 215 0
      src/views/calculationAnalysis/homeIndex/index.vue
  28. 358 0
      src/views/calculationAnalysis/taskManagement/InstanceManager.vue
  29. 92 0
      src/views/calculationAnalysis/taskManagement/common/DailyTimeIntervalForm.vue
  30. 87 0
      src/views/calculationAnalysis/taskManagement/common/Exporter.vue
  31. 415 0
      src/views/calculationAnalysis/taskManagement/common/InstanceDetail.vue
  32. 39 0
      src/views/calculationAnalysis/taskManagement/common/TimeExpressionValidator.vue
  33. 225 0
      src/views/calculationAnalysis/taskManagement/common/WFInstanceManager.vue
  34. 370 0
      src/views/calculationAnalysis/taskManagement/common/WorkflowInstanceDetail.vue
  35. 168 0
      src/views/calculationAnalysis/taskManagement/common/wfinstanceManageDatajson.json
  36. 404 0
      src/views/calculationAnalysis/taskManagement/dataJson.json
  37. 926 0
      src/views/calculationAnalysis/taskManagement/index.vue
  38. 48 0
      src/views/calculationAnalysis/taskManagement/instanceDataJson.json

+ 2 - 0
.env.dev

@@ -7,6 +7,8 @@ VITE_DEV=true
 # VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
 VITE_BASE_URL='http://123.60.223.250:48080'
 
+# 模型路径
+VITE_APP_MODEL_URL='http://123.60.223.250:7700'
 
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=client

+ 3 - 0
.env.local

@@ -6,6 +6,9 @@ VITE_DEV=true
 # 请求路径
 VITE_BASE_URL='http://123.60.223.250:48080'
 
+# 模型路径
+VITE_APP_MODEL_URL='http://123.60.223.250:7700'
+
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
 VITE_UPLOAD_TYPE=server
 # 上传路径

+ 3 - 0
.env.prod

@@ -6,6 +6,9 @@ VITE_DEV=false
 # 请求路径
 VITE_BASE_URL='http://localhost:48080'
 
+# 模型路径
+VITE_APP_MODEL_URL='http://123.60.223.250:7700'
+
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
 # 上传路径

+ 3 - 0
.env.test

@@ -6,6 +6,9 @@ VITE_DEV=false
 # 请求路径
 VITE_BASE_URL='http://localhost:48080'
 
+# 模型路径
+VITE_APP_MODEL_URL='http://123.60.223.250:7700'
+
 # 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
 VITE_UPLOAD_TYPE=server
 # 上传路径

+ 4 - 0
package.json

@@ -29,6 +29,7 @@
     "@form-create/designer": "^3.1.3",
     "@form-create/element-ui": "^3.1.24",
     "@iconify/iconify": "^3.1.1",
+    "@monaco-editor/loader": "^1.4.0",
     "@panzhiyue/leaflet-canvaslabel": "^1.2.0",
     "@videojs-player/vue": "^1.0.0",
     "@vueuse/core": "^10.9.0",
@@ -59,6 +60,7 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
+    "power-workflow": "^0.0.17",
     "qrcode": "^1.5.3",
     "qs": "^6.12.0",
     "steady-xml": "^0.1.0",
@@ -67,6 +69,8 @@
     "vue": "3.4.21",
     "vue-dompurify-html": "^4.1.4",
     "vue-i18n": "9.10.2",
+    "vue-json-viewer": "^2.2.22",
+    "vue-monaco-editor": "^0.0.19",
     "vue-router": "^4.3.0",
     "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0",

+ 50 - 0
src/api/model/container/index.ts

@@ -0,0 +1,50 @@
+import { service } from '@/config/axios/service'
+
+// 请求 Worker 列表
+export function getcontainerdownloadContainerTemplate (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      responseType: 'blob',
+      url: '/container/downloadContainerTemplate',
+      method: 'post',
+      data: params
+  })
+}
+
+export function getcontainersave (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/container/save',
+      method: 'post',
+      data: params
+  })
+}
+
+export function getcontainerlist (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/container/list',
+      method: 'get',
+      params
+  })
+}
+
+export function getcontainerdelete (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/container/delete',
+      method: 'get',
+      params
+  })
+}
+
+export function getcontainerlistDeployedWorker (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/container/listDeployedWorker',
+      method: 'get',
+      params
+  })
+}
+
+

+ 29 - 0
src/api/model/homeIndex/index.ts

@@ -0,0 +1,29 @@
+import { service } from '@/config/axios/service'
+
+// 请求 Worker 列表
+export function getHometableList (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/system/listWorker',
+      method: 'get',
+      params
+  })
+}
+// 请求 Overview
+export function getHomeOverviewData (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/system/overview',
+      method: 'get',
+      params
+  })
+}
+
+export function getAppInfo (params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/appInfo/assert',
+      method: 'post',
+      data: params
+  })
+}

+ 163 - 0
src/api/model/taskManagement/index.ts

@@ -0,0 +1,163 @@
+import { service } from '@/config/axios/service'
+
+// 请求 Task 列表
+export function getTasktableList(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/list',
+      method: 'post',
+      data: params
+  })
+}
+
+//运行
+export function runingTask(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/run',
+      method: 'get',
+      params: params
+  })
+}
+
+// 复制任务
+export function getcopyTaskMsg(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/copy',
+      method: 'post',
+      params: params
+  })
+}
+
+//任务导出
+export function exportJob(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/export',
+      method: 'get',
+      params: params
+  })
+}
+export function exportworkflow(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/export',
+      method: 'get',
+      params: params
+  })
+}
+export function jobsave(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/save',
+      method: 'post',
+      data: params
+  })
+}
+// 请求 job 列表
+export function getjobList(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/list',
+      method: 'post',
+      data: params
+  })
+}
+// 请求 job 删除
+export function getDeletejob(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/job/delete',
+      method: 'get',
+      params: params
+  })
+}
+
+// 请求 instance 列表
+export function getinstancetableList(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/list',
+      method: 'post',
+      data: params
+  })
+}
+
+// 请求 instance 重跑
+export function getinstanceretry(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/retry',
+      method: 'get',
+      params
+  })
+}
+
+// 请求 instance 停止
+export function getinstancstop(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/stop',
+      method: 'get',
+      params
+  })
+}
+
+// 查看日志
+export function getinstanclog(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/log',
+      method: 'get',
+      params
+  })
+}
+
+// 下载日志
+export function getdownloadlog(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/downloadLogUrl',
+      method: 'get',
+      params
+  })
+}
+
+// 请求 任务实例详情
+export function getinstancedetailPlus(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/instance/detailPlus',
+      method: 'post',
+      data: params
+  })
+}
+
+// 请求 任务实例详情
+export function getwfInstancelist(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/wfInstance/list',
+      method: 'post',
+      data: params
+  })
+}
+//停止工作流
+export function getwfInstancestop(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/wfInstance/stop',
+      method: 'get',
+      params
+  })
+}
+//重试工作流
+export function getwfInstanceretry(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/wfInstance/retry',
+      method: 'get',
+      params
+  })
+}

+ 113 - 0
src/api/model/workflowManagement/index.ts

@@ -0,0 +1,113 @@
+import { service } from '@/config/axios/service'
+
+// 请求 workflow 列表
+export function getworkflowtableList(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/list',
+      method: 'post',
+      data: params
+  })
+}
+
+export function getworkflowable(type, params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/'+ type,
+      method: 'get',
+      params: params
+  })
+}
+
+export function getworkflowrun(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/run',
+      method: 'get',
+      params: params
+  })
+}
+// 删除工作流
+export function getdeleteworkflow(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/delete',
+      method: 'get',
+      params: params
+  })
+}
+
+//复制工作流
+export function getworkflowcopy(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/copy',
+      method: 'post',
+      params
+  })
+}
+
+export function getworkflowfetch(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/fetch',
+      method: 'get',
+      params
+  })
+}
+
+// 保存工作流
+export function getworkflowsave(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/save',
+      method: 'post',
+      data: params
+  })
+}
+export function getworkflowsaveNode(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/workflow/saveNode',
+      method: 'post',
+      data: params
+  })
+}
+
+//用户信息
+export function getuserMessage() {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/user/list',
+      method: 'get'
+  })
+}
+
+//获取工作流详情数据
+export function getwfInstanceinfo(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/wfInstance/info',
+      method: 'get',
+      params
+  })
+}
+
+//标记成功
+export function getwfInstancemarkNodeAsSuccess(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/wfInstance/markNodeAsSuccess',
+      method: 'get',
+      params
+  })
+}
+
+export function getvalidatetimeExpression(params) {
+  return service({
+      baseURL: import.meta.env.VITE_APP_MODEL_URL,
+      url: '/validate/timeExpression',
+      method: 'get',
+      params
+  })
+}

File diff suppressed because it is too large
+ 1 - 0
src/assets/powerJob/skip.svg


File diff suppressed because it is too large
+ 1 - 0
src/assets/powerJob/start.svg


+ 5 - 0
src/main.ts

@@ -19,8 +19,10 @@ import { setupElementPlus } from '@/plugins/elementPlus'
 // 引入 form-create
 import { setupFormCreate } from '@/plugins/formCreate'
 
+
 // 引入全局样式
 import '@/styles/index.scss'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 
 // 引入动画
 import '@/plugins/animate.css'
@@ -51,6 +53,9 @@ import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐
 // 创建实例
 const setupAll = async () => {
   const app = createApp(App)
+  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+    app.component(key, component)
+  }
 
   await setupI18n(app)
 

+ 72 - 1
src/router/modules/remaining.ts

@@ -127,7 +127,78 @@ const remainingRouter: AppRouteRecordRaw[] = [
       }
     ]
   },
-
+  {
+    path: '/taskManagement',
+    component: Layout,
+    name: 'taskManagementRun',
+    meta: {
+      hidden: true
+    },
+    children: [
+      {
+        path: 'runmsg',
+        component: () => import('@/views/calculationAnalysis/taskManagement/InstanceManager.vue'),
+        name: 'InstanceManager',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:edit',
+          title: '运行记录',
+          activeMenu: 'calculationAnalysis/taskManagement/index'
+        }
+      },
+      {
+        path: 'wfinstance',
+        component: () => import('@/views/calculationAnalysis/taskManagement/common/WFInstanceManager.vue'),
+        name: 'WFInstanceManager',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:edit',
+          title: '工作流实例',
+          activeMenu: 'calculationAnalysis/taskManagement/index'
+        }
+      },
+      {
+        path: 'WorkflowInstanceDetail',
+        component: () => import('@/views/calculationAnalysis/taskManagement/common/WorkflowInstanceDetail.vue'),
+        name: 'WorkflowInstanceDetail',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:edit',
+          title: '工作流详情',
+          activeMenu: 'calculationAnalysis/calculationManagement/WorkflowManager'
+        }
+      }
+    ]
+  },
+  {
+    path: '/calculationManagement',
+    component: Layout,
+    name: 'calculationManagementRun',
+    meta: {
+      hidden: true
+    },
+    children: [
+      {
+        path: 'workflowEditor',
+        component: () => import('@/views/calculationAnalysis/calculationManagement/WorkflowEditor.vue'),
+        name: 'workflowEditor',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          icon: 'ep:edit',
+          title: '工作流编辑器',
+          activeMenu: 'calculationAnalysis/calculationManagement/WorkflowManager'
+        }
+      }
+    ]
+  },
   {
     path: '/codegen',
     component: Layout,

+ 43 - 0
src/utils/common.ts

@@ -0,0 +1,43 @@
+
+// 公共函数,涉及到 i18n ,放进 common.js 报错,暂时先放在这里吧
+export function translateInstanceStatus (status) : String {
+    switch (status) {
+        case 1: return '等待派发';
+        case 2: return '等待Worker接收';
+        case 3: return '运行中';
+        case 4: return '失败';
+        case 5: return '成功';
+        case 9: return '手动取消';
+        case 10: return '手动停止';
+        default: return "unknown";
+    }
+};
+
+export function translateWfInstanceStatus (status) : String {
+    switch (status) {
+        case 1: return '等待调度';
+        case 2: return '运行中';
+        case 3: return '失败';
+        case 4: return '成功';
+        case 10: return '手动停止';
+        default: return "unknown";
+    }
+};
+
+export function getDateTime (date) : String {
+  const y = date.getFullYear();
+  let m = date.getMonth() + 1;
+  m = m < 10 ? ('0' + m) : m;
+  let d = date.getDate();
+  d = d < 10 ? ('0' + d) : d;
+  let h = date.getHours();
+  h = h < 10 ? ('0' + h) : h;
+  let minute = date.getMinutes();
+  minute = minute < 10 ? ('0' + minute) : minute;
+  let second=date.getSeconds();  
+  second=second < 10 ? ('0' + second) : second;  
+  let timeF = ''
+  timeF = y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second
+  return timeF
+}
+

+ 8 - 0
src/views/Login/components/LoginForm.vue

@@ -155,6 +155,8 @@ import { usePermissionStore } from '@/store/modules/permission'
 import * as LoginApi from '@/api/login'
 import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
 
+import { getAppInfo } from '@/api/model/homeIndex/index'
+
 defineOptions({ name: 'LoginForm' })
 
 const { t } = useI18n()
@@ -255,6 +257,12 @@ const handleLogin = async (params) => {
     if (!res) {
       return
     }
+    //获取powerjob使用的appid
+    const powerRes = await getAppInfo({
+      appName: 'powerjob-worker-sample',
+      password: '123456'
+    })
+    window.localStorage.setItem('powerAppId', powerRes.data)
     loading.value = ElLoading.service({
       lock: true,
       text: '正在加载系统中...',

+ 59 - 0
src/views/calculationAnalysis/calculationManagement/JSEditor.vue

@@ -0,0 +1,59 @@
+<template>
+  <div class="code-edit">
+    <MonacoEditor
+      v-model:value="editCode"
+      :key="randomKey"
+      theme="vs"
+      :height="300"
+      :options="options"
+      @mounted="editMounted"
+    />
+  </div>
+  <!-- @editCodeChange="editCodeChange" -->
+</template>
+<script setup>
+import { onMounted, ref, watch, defineProps } from 'vue'
+import MonacoEditor from './MonacoEditor.vue'
+const props = defineProps({
+  code: {
+    type: Number,
+    default: 0
+  }
+})
+/** 代码组件 */
+const editCode = ref(null)
+const editor = ref(null)
+const options = ref({
+  selectOnLineNumbers: false
+})
+const randomKey = ref(1231231)
+const editMounted = (editor) => {
+  editor.value = editor
+}
+const emit = defineEmits(['onCodeChange'])
+const editCodeChange = () => {
+  emit('onCodeChange', editor.value.getValue())
+}
+onMounted(() => {
+  editCode.value = props.code
+})
+</script>
+<style>
+.code-edit {
+  border: 1px solid #f0f0f0;
+}
+.code-edit .margin-view-overlays.monaco-editor-background {
+  width: 0px !important;
+}
+.code-edit .margin-view-overlays.monaco-editor-background .view-line .line-numbers {
+  width: 24px !important;
+  left: 0px !important;
+}
+.code-edit .margin-view-overlays.monaco-editor-background .glyph-margin {
+  width: 0px !important;
+}
+.code-edit .monaco-scrollable-element.editor-scrollable {
+  left: 32px !important;
+}
+</style>
+

+ 72 - 0
src/views/calculationAnalysis/calculationManagement/MonacoEditor.vue

@@ -0,0 +1,72 @@
+<script setup>
+import { ref, onMounted, onBeforeUnmount, watch, defineProps, defineEmits } from 'vue'
+import loader from '@monaco-editor/loader'
+
+const props = defineProps({
+  value: String,
+  language: {
+    type: String,
+    default: 'java'
+  },
+  theme: {
+    type: String,
+    default: 'vs-dark'
+  }
+})
+
+const emits = defineEmits(['editCodeChange'])
+
+const editorContainer = ref(null)
+let editorInstance = null
+
+onMounted(() => {
+  loader.init().then((monaco) => {
+    editorInstance = monaco.editor.create(editorContainer.value, {
+      value: props.value || '',
+      language: props.language,
+      theme: props.theme
+    })
+
+    editorInstance.onDidChangeModelContent(() => {
+      emits('editCodeChange', editorInstance.getValue())
+    })
+  })
+})
+
+onBeforeUnmount(() => {
+  if (editorInstance) {
+    editorInstance.dispose()
+  }
+})
+
+watch(
+  () => props.language,
+  (newLanguage) => {
+    if (editorInstance) {
+      loader.init().then((monaco) => {
+        monaco.editor.setModelLanguage(editorInstance.getModel(), newLanguage)
+      })
+    }
+  }
+)
+
+watch(
+  () => props.value,
+  (newValue) => {
+    if (editorInstance && editorInstance.getValue() !== newValue) {
+      editorInstance.setValue(newValue)
+    }
+  }
+)
+</script>
+ 
+<template>
+  <div ref="editorContainer" class="editor-container"></div>
+</template>
+ 
+<style>
+.editor-container {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 518 - 0
src/views/calculationAnalysis/calculationManagement/PowerWorkflow.vue

@@ -0,0 +1,518 @@
+<template>
+  <div class="power-job" ref="power-job">
+    <div class="power-job-header">
+      <div class="job-tools">
+        <div v-if="onClickImportNode" @click="onClickImportNode">
+          <el-tooltip content="导入任务" placement="top" effect="light">
+            <!-- <i class="el-icon-document-add"></i> -->
+            <el-icon><DocumentAdd /></el-icon>
+          </el-tooltip>
+        </div>
+        <div @click="onClickImportSpecialNode({ type: 2 })" v-if="mode !== 'view'">
+          <el-tooltip content="判断节点" placement="top" effect="light">
+            <!-- <i class="iconfont icon-panduanti"></i> -->
+            <el-icon><ScaleToOriginal /></el-icon>
+          </el-tooltip>
+        </div>
+        <div @click="onClickImportSpecialNode({ type: 3 })" v-if="mode !== 'view'">
+          <el-tooltip content="子流程节点" placement="top" effect="light">
+            <!-- <i class="iconfont icon-workflow_"></i> -->
+            <el-icon><SetUp /></el-icon>
+          </el-tooltip>
+        </div>
+        <slot name="tool"></slot>
+        <div @click="handleZoomOut">
+          <el-tooltip content="缩小" placement="top" effect="light">
+            <!-- <i class="el-icon-zoom-out"></i> -->
+            <el-icon><ZoomOut /></el-icon>
+          </el-tooltip>
+        </div>
+        <div @click="handleZoomIn">
+          <el-tooltip content="放大" placement="top" effect="light">
+            <!-- <i class="el-icon-zoom-in"></i> -->
+            <el-icon><ZoomIn /></el-icon>
+          </el-tooltip>
+        </div>
+        <div @click="handleAutoFit">
+          <el-tooltip content="自适应" placement="top" effect="light">
+            <!-- <i class="el-icon-aim"></i> -->
+            <el-icon><Crop /></el-icon>
+          </el-tooltip>
+        </div>
+        <div @click="fullScreen">
+          <el-tooltip content="全屏" placement="top" effect="light">
+            <!-- <i class="el-icon-full-screen"></i> -->
+            <el-icon><FullScreen /></el-icon>
+          </el-tooltip>
+        </div>
+      </div>
+    </div>
+    <div class="power-job-body">
+      <!-- :style="{ width: `${size.width}px` }" -->
+      <div
+        class="power-power-flow"
+        :style="selectNode !== null ? { width: '62vw' } : { width: '87.5vw' }"
+        ref="powerDag"
+      ></div>
+      <div class="power-job-detail" v-if="selectNode !== null">
+        <slot></slot>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import startsvg from '@/assets/powerJob/start.svg'
+import skipsvg from '@/assets/powerJob/skip.svg'
+import { useRouter } from 'vue-router'
+import { onMounted, ref, watch, getCurrentInstance, defineProps, defineEmits } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import Workflow from 'power-workflow'
+
+const defaultSize = ref({
+  width: (document.body.clientWidth - 254) * 0.7,
+  height: 700
+})
+
+const dagSize = ref({
+  default: {
+    width: (defaultWidthInc, radio) => (document.body.clientWidth - defaultWidthInc) * radio,
+    height: () => document.body.clientHeight
+  },
+  fiexd: {
+    width: (defaultWidthInc, rightWidth) =>
+      document.body.clientWidth - defaultWidthInc - rightWidth,
+    height: () => document.body.clientHeight
+  }
+})
+const size = ref({
+  width: document.body.clientWidth - 254,
+  height: defaultSize.value.height
+})
+const powerFlow = ref(null)
+const selectNode = ref(null)
+const zoom = ref(1)
+
+const props = defineProps({
+  onClickImportNode: Function,
+  defaultWidthInc: {
+    type: Number,
+    default: 0
+  },
+  nodes: {
+    type: Array,
+    default: () => []
+  },
+  edges: {
+    type: Array,
+    default: () => []
+  },
+  rightFixed: {
+    type: Number,
+    default: 0
+  },
+  fullInc: {
+    type: String,
+    default: ''
+  },
+  mode: {
+    type: String,
+    default: ''
+  },
+  interceptSelectedNode: Function,
+  onClickImportSpecialNode: Function
+})
+
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['onSelectedNode', 'onClearSelectNode', 'getDag'])
+/** 初始化图 */
+const initFlow = () => {
+  // 整理节点数据
+  let { nodes, edges } = makeNodes()
+  const powerFlowNew = new Workflow({
+    container: proxy.$refs.powerDag,
+    width: size.value.width,
+    height: size.value.height,
+    initNodes: nodes,
+    initEdges: edges,
+    // layout: 'vertical',
+
+    layout: {
+      type: 'dagre',
+      // align: 'UR', // 可选
+      nodesep: 40, // 可选
+      // ranksep: 60, // 可选
+      rankdir: 'LR',
+      controlPoints: true, // 可选
+      ranksepFunc: (node) => {
+        // console.log(node);
+        if (node.nodeType === 2) {
+          return 55
+        }
+        return 60
+      }
+    },
+    edgeCallback: edgeEnd.value
+  })
+  powerFlowNew.graph.setMode('edit')
+  // powerFlow.graph.fitCenter(20);
+  powerFlow.value = powerFlowNew
+
+  powerFlow.value.graph.on('onSelectNode', (item) => {
+    // if (!interceptSelectedNode(item)) {
+    //   emit('onSelectedNode', item)
+    //   return
+    // } else {
+    //   selectNode.value = item
+    //   emit('onSelectedNode', item)
+    // }
+    selectNode.value = item
+    // size.value.width = document.body.clientWidth - 754
+    emit('onSelectedNode', item)
+  })
+
+  powerFlow.value.graph.on('onClearSelectNode', () => {
+    selectNode.value = null
+    emit('onClearSelectNode', null)
+  })
+
+  if (props.mode) powerFlow.value.graph.setMode(props.mode)
+
+  emit('getDag', powerFlow.value, {
+    nodes,
+    edges,
+    resetNodes: resetNodes.value
+  })
+}
+
+/** 重置节点 */
+const resetNodes = () => {
+  let { nodes, edges } = makeNodes()
+  powerFlow.value.graph.data({ nodes, edges })
+  powerFlow.value.graph.render()
+}
+
+/** 判断线是否可以 */
+const edgeEnd = (sourceNode) => {
+  // console.log(sourceNode);
+  // console.log(targetNode);
+  // console.log(sourceNode.get('currentShape'))
+  const data = isEdge(sourceNode)
+  if (!data) return false
+  return {
+    ...data
+  }
+}
+
+/** 判断线上是否添加文字以及是否可添加线 */
+const isEdge = (node) => {
+  const nodeType = node.get('currentShape')
+  if (nodeType !== 'max-diamond-node') {
+    return {}
+  }
+
+  const edges = node.getOutEdges()
+  if (edges.length > 1) {
+    ElMessage('最多只能有两条边哦')
+    return false
+  }
+  console.log(edges)
+  if (edges.length === 0) {
+    return {
+      label: 'Y'
+    }
+  }
+
+  const item = edges[0]
+  const model = item.get('model')
+  return {
+    label: model.label === 'Y' ? 'N' : 'Y'
+  }
+}
+
+/** 整理节点信息 */
+const makeNodes = () => {
+  let width = 0
+  props.nodes.forEach((item) => {
+    let len = item.nodeName.length * 9
+    let curtWidth = 0
+    if (len > 220) {
+      curtWidth = 220
+    } else if (len < 180) {
+      curtWidth = 180
+    } else {
+      curtWidth = len
+    }
+    if (curtWidth > width) {
+      width = curtWidth
+    }
+  })
+  let status = {
+    3: {
+      color: '',
+      text: '运行中'
+    },
+    4: {
+      color: '#FFADA4',
+      text: '失败'
+    },
+    5: {
+      color: '#C3FFD2',
+      text: '成功'
+    },
+    10: {
+      color: '',
+      text: '手动停止'
+    },
+    9: {
+      text: '已取消',
+      color: '#E2E2E2',
+      stroke: '#A5A5A5'
+    }
+  }
+  let nodes = props.nodes.map((item) => {
+    let statusValue = {}
+    let statusStyle = {}
+    if (item.nodeType === 2) {
+      return {
+        id: `${item.nodeId}`,
+        type: 'max-diamond-node',
+        text: item.nodeName ? item.nodeName : '判断',
+        size: [15, 80],
+        style: {
+          sideLength: 80,
+          textStyle: {
+            // fill: "#FFFFFF",
+          }
+        },
+        ...item
+      }
+    }
+    if (item.status) {
+      statusValue = {
+        taskStatus: status[item.status]
+          ? status[item.status].text
+          : this.$t('message.waitingUpstream'),
+        taskStatusValue: item.status
+      }
+      if (status[item.status] && status[item.status].color) {
+        statusStyle = {
+          fill: status[item.status].color
+        }
+        if (status[item.status].stroke) {
+          statusStyle.stroke = status[item.status].stroke
+        }
+      }
+    }
+
+    return {
+      id: `${item.nodeId}`,
+      type: item.nodeType === 1 ? 'flow-node' : 'flow-child-node',
+      size: [width, 70],
+      leftText: item.jobId,
+      titleText: item.nodeName,
+      icon1: item.enable ? startsvg : '',
+      icon2: item.skipWhenFailed ? skipsvg : '',
+      // taskStatus: text,
+      ...statusValue,
+      style: statusStyle,
+      rightText: item.instanceId ? item.instanceId : null,
+      ...item
+    }
+  })
+  let edges = props.edges.map((item) => {
+    const property = {}
+    if (item.property) {
+      property.label = item.property === 'true' ? 'Y' : 'N'
+    }
+    return {
+      source: `${item.from}`,
+      target: `${item.to}`,
+      type: 'cvte-polyline',
+      ...property
+    }
+  })
+  return { nodes, edges }
+}
+
+/** 引入判断节点 */
+const importJudgeNode = () => {
+  onClickImportSpecialNode() && onClickImportSpecialNode({ type: 2 })
+}
+
+/** 引入工作流节点 */
+const importWorkflowNode = () => {
+  props.onClickImportSpecialNode && props.onClickImportSpecialNode({ type: 3 })
+}
+
+/** 监控全屏 */
+const onFullScreenListener = () => {
+  let isFull =
+    document.fullscreenElement ||
+    document.msFullscreenElement ||
+    document.mozFullScreenElement ||
+    document.webkitFullscreenElement ||
+    false
+  if (isFull === undefined) isFull = false
+
+  let incValue = 1
+  if (selectNode.value) {
+    incValue = 0.7
+  }
+
+  let width = 0
+  let height = 0
+
+  let strage = 'default'
+
+  if (props.rightFixed) {
+    strage = 'fiexd'
+    incValue = props.rightFixed
+    if (!selectNode.value) {
+      incValue = 0
+    }
+  }
+
+  if (isFull) {
+    // if(!this.selectNode) {
+    //   width = dagSize[strage].width(this.defaultWidthInc, incValue);
+    // }
+    width = dagSize[strage].width(0, incValue)
+    height = dagSize[strage].height()
+    size.value = {
+      width,
+      height
+    }
+    powerFlow.value.graph.changeSize(size.value.width, size.value.height)
+  } else {
+    width = dagSize[strage].width(props.defaultWidthInc, incValue)
+    height = dagSize[strage].height()
+    size.value = {
+      width,
+      height: defaultSize.value.height
+    }
+    powerFlow.value.graph.changeSize(size.value.width, size.value.height)
+  }
+}
+
+/** 缩小 */
+const handleZoomOut = () => {
+  let zoom = powerFlow.value.graph.getZoom()
+  zoom -= 0.1
+  powerFlow.value.graph.zoomTo(zoom)
+}
+
+/** 放大 */
+const handleZoomIn = () => {
+  let zoom = powerFlow.value.graph.getZoom()
+  zoom += 0.1
+  powerFlow.value.graph.zoomTo(zoom)
+}
+
+/** 自适应 */
+const handleAutoFit = () => {
+  powerFlow.value.graph.layout()
+  powerFlow.value.graph.fitView(20)
+}
+
+/** 全屏 */
+const fullScreen = () => {
+  const el = document.getElementById(props.fullInc) || proxy.$refs['power-job']
+  const rfs =
+    el.requestFullScreen ||
+    el.webkitRequestFullScreen ||
+    el.mozRequestFullScreen ||
+    el.msRequestFullScreen
+  if (typeof rfs != 'undefined' && rfs) {
+    rfs.call(el)
+    return
+  }
+}
+const route = useRouter()
+onMounted(() => {
+  size.value.width = props.defaultWidthInc
+    ? document.body.clientWidth - props.defaultWidthInc
+    : document.body.clientWidth - 254
+  let modify = route.currentRoute.value.query.modify
+  let insId = route.currentRoute.value.query.wfInstanceId ? true : false
+  if (modify === 'false' || insId) {
+    initFlow()
+  }
+  window.addEventListener('resize', onFullScreenListener.value)
+})
+watch(
+  () => props.nodes,
+  (newVal, oldVal) => {
+    initFlow()
+  }
+)
+watch(
+  () => 'selectNode',
+  (newVal, oldVal) => {
+    onFullScreenListener()
+  }
+)
+</script>    
+<style scoped>
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+.iconfont {
+  font-size: 18px !important;
+}
+.power-power-flow {
+  border: 1px solid #d0d0d0;
+  border-top: 0px;
+  /* box-shadow: 0 10px 10px 1px #c0c0c0; */
+  box-sizing: border-box;
+  border-radius: 10px;
+  border-top-left-radius: 0px;
+  border-top-right-radius: 0px;
+  /* width: 700px; */
+}
+
+.job-tools {
+  width: 100%;
+  box-sizing: border-box;
+  border: 1px solid #d0d0d0;
+  /* box-shadow: 0 0 10px 1px #c0c0c0; */
+  border-radius: 10px;
+  margin-right: 10px;
+  border-bottom-left-radius: 0px;
+  border-bottom-right-radius: 0px;
+  display: flex;
+  padding: 8px 10px;
+}
+.job-tools > div {
+  box-sizing: border-box;
+  height: 30px;
+  width: 30px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+.job-tools div:hover {
+  border: 1px solid #f0f0f0;
+}
+.job-tools > div + div {
+  margin-left: 24px;
+}
+.job-tools i {
+  font-size: 20px;
+  color: #3d3e3e;
+}
+
+.power-job {
+  background: #fff;
+}
+.power-job-body {
+  display: flex;
+}
+.power-job-detail {
+  flex: 1;
+  border-right: 1px solid #e0e0e0;
+  border-bottom: 1px solid #e0e0e0;
+}
+</style>

+ 47 - 0
src/views/calculationAnalysis/calculationManagement/TimeExpressionValidator.vue

@@ -0,0 +1,47 @@
+<template>
+  <div>
+    <el-card class="box-card">
+      <div v-for="res in nextNTriggerTime" :key="res" class="text item">
+        {{ res }}
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch, defineProps } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getvalidatetimeExpression } from '@/api/model/workflowManagement'
+// 数据传递
+const props = defineProps({
+  timeExpressionType: {
+    type: String,
+    default: undefined
+  },
+  timeExpression: {
+    type: String,
+    default: undefined
+  }
+})
+const nextNTriggerTime = ref([])
+
+const checkTimeExpression = async () => {
+  let params = {
+    timeExpressionType: props.timeExpressionType,
+    timeExpression: timeExpressionCh.value
+  }
+  const res = await getvalidatetimeExpression(params)
+  nextNTriggerTime.value = res.data
+}
+const timeExpressionCh = ref(null)
+onMounted(() => {
+  console.log('type:' + props.timeExpressionType)
+  console.log('expression:' + props.timeExpression)
+  timeExpressionCh.value = encodeURIComponent(props.timeExpression)
+  console.log('expressionAfterEncodeURIComponent: ' + props.timeExpression)
+  checkTimeExpression()
+})
+</script>
+
+<style scoped>
+</style>

+ 839 - 0
src/views/calculationAnalysis/calculationManagement/WorkflowEditor.vue

@@ -0,0 +1,839 @@
+<template>
+  <div>
+    <el-row style="margin-left: 0px; margin-bottom: 20px; margin-right: 25px">
+      <el-col :span="1">
+        <el-button type="primary" @click="back">返回</el-button>
+      </el-col>
+      <el-col :span="1" :offset="22">
+        <el-button type="success" :loading="saveLoading" @click="saveWorkflow">保存</el-button>
+      </el-col>
+    </el-row>
+
+    <el-row>
+      <el-form ref="form" :model="workflowInfo" label-width="100px" style="width: 100%">
+        <el-form-item label="工作流名称">
+          <el-input v-model="workflowInfo.wfName" />
+        </el-form-item>
+        <el-form-item label="工作流描述">
+          <el-input v-model="workflowInfo.wfDescription" />
+        </el-form-item>
+
+        <el-form-item label="定时信息">
+          <el-row>
+            <el-col :span="6">
+              <el-select v-model="workflowInfo.timeExpressionType" placeholder="时间表达式类型">
+                <el-option
+                  v-for="item in timeExpressionTypeOptions"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="12">
+              <el-input
+                v-model="workflowInfo.timeExpression"
+                placeholder="CRON 填写 CRON 表达式,API 无需填写"
+              />
+            </el-col>
+            <el-col :span="4">
+              <el-button type="text" @click="onClickValidateTimeExpression">校验定时参数</el-button>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="生命周期">
+          <el-date-picker
+            v-model="workflowInfo.lifeCycle"
+            type="datetimerange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            format="YYYY-MM-DD HH:mm:ss"
+            date-format="YYYY-MM-DD"
+            time-format="hh:mm:ss"
+            @change="changedatepicker"
+          />
+        </el-form-item>
+
+        <el-form-item label="最大实例数">
+          <el-input-number v-model="workflowInfo.maxWfInstanceNum" />
+        </el-form-item>
+
+        <el-form-item label="报警配置">
+          <el-select
+            v-model="workflowInfo.notifyUserIds"
+            multiple
+            filterable
+            placeholder="选择报警通知人员"
+          >
+            <el-option
+              v-for="user in userList"
+              :key="user.id"
+              :label="user.username"
+              :value="user.id"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+    </el-row>
+
+    <el-row>
+      <div class="power-flow">
+        <div class="power-dag" id="fullInc">
+          <!-- 
+             -->
+          <PowerWorkflow
+            :onClickImportSpecialNode="onClickImportSpecialNode"
+            :onClickImportNode="onClickImportNode"
+            :selectNode="selectNode"
+            :nodes="peworkflowDAG.nodes"
+            :edges="peworkflowDAG.edges"
+            @on-selected-node="handleSelectedNode"
+            @get-dag="getDagsv"
+            :defaultWidthInc="234"
+            fullInc="fullInc"
+          >
+            <div class="job-panl" v-if="selectNode !== null">
+              <el-form ref="form" :model="nodeInfo">
+                <el-form-item label="任务名称" v-if="nodeInfo.type != '2'">
+                  <!-- :style="{ width: 'calc(100% - 90px)' }" -->
+                  <el-select
+                    v-model="nodeInfo.jobId"
+                    filterable
+                    remote
+                    reserve-keyword
+                    placeholder="请输入关键词"
+                    :remote-method="remoteTaskData"
+                    :loading="taskLoading"
+                    style="width: 410px"
+                    @focus="handleWaitFocus"
+                    @change="handleWaitTaskChange"
+                  >
+                    <el-option
+                      v-for="item in waitTaskList"
+                      :key="item.id"
+                      :label="item.jobName || item.wfName"
+                      :value="item.id"
+                    />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="节点名称">
+                  <el-input
+                    v-model="nodeInfo.nodeName"
+                    @input="handleNodeName"
+                    :style="{ width: 'calc(100% - 90px)' }"
+                  />
+                </el-form-item>
+                <el-form-item label="节点参数" v-if="nodeInfo.type != '2'">
+                  <el-input v-model="nodeInfo.nodeParams" :style="{ width: 'calc(100% - 90px)' }" />
+                </el-form-item>
+                <el-form-item label="是否启用" v-if="nodeInfo.type != '2'">
+                  <el-switch v-model="nodeInfo.enable" />
+                  <img
+                    class="job-panl-icon"
+                    v-if="nodeInfo.enable"
+                    src="@/assets/powerJob/start.svg"
+                    height="18"
+                    width="18"
+                    alt
+                  />
+                </el-form-item>
+                <el-form-item label="失败跳过" v-if="nodeInfo.type != '2'">
+                  <el-switch v-model="nodeInfo.skipWhenFailed" />
+                  <img
+                    class="job-panl-icon"
+                    v-if="nodeInfo.skipWhenFailed"
+                    src="../../assets/skip.svg"
+                    height="18"
+                    width="18"
+                    alt
+                  />
+                </el-form-item>
+              </el-form>
+              <div v-if="nodeInfo.type == '2'" class="judge-message-params">
+                <p>节点参数</p>
+                <!-- @onCodeChange="onCodeChange" -->
+                <JSEditor
+                  :code="nodeInfo.nodeParams"
+                  @on-code-change="onCodeChange"
+                  key="nodeParams"
+                />
+              </div>
+
+              <div class="job-panl-btn">
+                <el-button type="success" @click="handleNodeSave">保存</el-button>
+              </div>
+            </div>
+          </PowerWorkflow>
+          <el-drawer
+            title="请选择需要导入工作流的任务"
+            v-model="importDrawerVisible"
+            direction="rtl"
+            size="60%"
+          >
+            <div class="power-import-body">
+              <el-row>
+                <el-form :inline="true" :model="jobQueryContent" class="el-form--inline">
+                  <el-form-item label="任务 ID">
+                    <el-input v-model="jobQueryContent.jobId" placeholder="任务 ID" />
+                  </el-form-item>
+                  <el-form-item label="关键字">
+                    <el-input v-model="jobQueryContent.keyword" placeholder="关键字" />
+                  </el-form-item>
+                  <el-form-item>
+                    <el-button type="primary" @click="listJobInfos">查询</el-button>
+                    <el-button type="cancel" @click="onClickReset">重置</el-button>
+                    <el-button type="cancel" @click="onBulkImport">批量导入</el-button>
+                  </el-form-item>
+                </el-form>
+              </el-row>
+              <el-table
+                class="power-import-table"
+                :data="jobInfoPageResult.data"
+                @selection-change="handleSelectionChange"
+              >
+                <el-table-column type="selection" width="55" />
+                <el-table-column property="id" label="任务 ID" />
+                <el-table-column property="jobName" label="任务名称" />
+                <el-table-column label="操作">
+                  <template #default="scope">
+                    <el-button size="medium" @click="importTask([scope.row])">导入</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+              <el-row>
+                <el-pagination
+                  layout="prev, pager, next"
+                  :total="jobInfoPageResult.totalItems"
+                  :page-size="jobInfoPageResult.pageSize"
+                  @current-change="onClickChangePage"
+                />
+              </el-row>
+            </div>
+          </el-drawer>
+        </div>
+      </div>
+    </el-row>
+    <el-dialog v-model="timeExpressionValidatorVisible" v-if="timeExpressionValidatorVisible">
+      <TimeExpressionValidator
+        :time-expression="workflowInfo.timeExpression"
+        :time-expression-type="workflowInfo.timeExpressionType"
+      />
+    </el-dialog>
+    <el-drawer title="工作流节点引入" v-model="workflowVisible" direction="rtl" size="60%">
+      <workflow-manager :isWorkflow="true" @on-import-node="onImportChildWorkflowNode" />
+    </el-drawer>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch, defineComponent } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useRouter } from 'vue-router'
+import JSEditor from './JSEditor.vue'
+import TimeExpressionValidator from './TimeExpressionValidator.vue'
+import PowerWorkflow from './PowerWorkflow.vue'
+import WorkflowManager from './WorkflowManager.vue'
+import { getjobList, getTasktableList } from '@/api/model/taskManagement'
+import { getuserMessage } from '@/api/model/workflowManagement'
+import startsvg from '@/assets/powerJob/start.svg'
+import skipsvg from '@/assets/powerJob/skip.svg'
+import * as common from '@/utils/common'
+import {
+  getworkflowfetch,
+  getworkflowsave,
+  getworkflowsaveNode,
+  getworkflowtableList
+} from '@/api/model/workflowManagement'
+// import { concat } from 'node_modules/_@types_lodash@4.17.1@@types/lodash'
+const powerappId = window.localStorage.getItem('powerAppId')
+const { push } = useRouter() // 路由跳转
+const workflowInfo = ref({
+  id: '',
+  appId: powerappId,
+  enable: true,
+  maxWfInstanceNum: 1,
+  notifyUserIds: [],
+  timeExpression: undefined,
+  timeExpressionType: undefined,
+  wfDescription: undefined,
+  wfName: undefined,
+  lifeCycle: []
+})
+const nodeInfoChange = (icon, index) => {
+  return function (value) {
+    if (!this.selectNode) return
+    const group = this.selectNode.getContainer()
+    const current = group.getChildByIndex(index)
+    if (value) {
+      current.attr({ img: icon })
+    } else {
+      current.attr({ img: '' })
+    }
+  }
+}
+
+/** 节点类型映射 */
+const nodeType = ref({
+  1: (item) => {
+    return {
+      type: 'flow-node',
+      size: [240, 70],
+      leftText: item.jobId,
+      titleText: item.nodeName,
+      icon1: item.enable ? startsvg : '',
+      icon2: item.skipWhenFailed ? skipsvg : ''
+    }
+  },
+  2: (item) => {
+    return {
+      type: 'max-diamond-node',
+      text: !item.nodeName ? '判断' : item.nodeName,
+      style: {
+        sideLength: 80,
+        textStyle: {
+          // fill: "#FFFFFF",
+        }
+        // fill: "#FE9201",
+        // stroke: "#D45547",
+      }
+    }
+  },
+  3: (item) => {
+    return {
+      type: 'flow-child-node',
+      size: [240, 70],
+      leftText: item.jobId,
+      titleText: item.nodeName,
+      icon1: item.enable ? startsvg : '',
+      icon2: item.skipWhenFailed ? skipsvg : ''
+    }
+  }
+})
+const nodeInfo = ref({
+  id: null,
+  jobId: null,
+  nodeName: '',
+  nodeParams: '',
+  enable: true,
+  skipWhenFailed: true
+})
+const timeExpressionTypeOptions = ref([
+  { key: 'API', label: 'API' },
+  { key: 'CRON', label: 'CRON' }
+])
+const userList = ref([])
+
+// 导入任务相关
+const importDrawerVisible = ref(false)
+const jobQueryContent = ref({
+  appId: powerappId,
+  index: 0,
+  pageSize: 8,
+  jobId: undefined,
+  keyword: undefined
+})
+const jobInfoPageResult = ref({
+  pageSize: 20,
+  totalItems: 0,
+  data: []
+})
+
+// 事件(1:新增起点,2:新增终点,3:删除节点;4:删除边)
+const event = ref(undefined)
+const from = ref(undefined)
+
+// 时间表达式校验窗口
+const timeExpressionValidatorVisible = ref(false)
+/** DAG信息 */
+const peworkflowDAG = ref({
+  nodes: [],
+  edges: []
+})
+/** 保存按钮loading */
+const saveLoading = ref(false)
+/** 流程图实例 */
+const powerFlow = ref(null)
+/** 选中的数据 */
+const multipleSelection = ref([])
+/** 当前的节点信息 */
+const taskList = ref([])
+/** 当前选中的节点 */
+const selectNode = ref(null)
+/** 重置节点方法 */
+const resetNodes = ref(null)
+/** 待选任务列表 */
+const waitTaskList = ref([])
+/** 任务搜索loading */
+const taskLoading = ref(false)
+/** 任务节流 */
+const taskTimeout = ref(null)
+/** 工作流引入显隐控制 */
+const workflowVisible = ref(false)
+const route = useRouter()
+// 返回上一页
+const back = () => {
+  route.back()
+}
+// 点击重置按钮
+const onClickReset = () => {
+  jobQueryContent.value.keyword = undefined
+  jobQueryContent.value.jobId = undefined
+  listJobInfos()
+}
+// 列出符合当前搜索条件的任务
+const listJobInfos = async () => {
+  const res = await getjobList(jobQueryContent.value)
+  jobInfoPageResult.value = res.data
+}
+// 点击 换页
+const onClickChangePage = (index) => {
+  // 后端从0开始,前端从1开始
+  jobQueryContent.value.index = index - 1
+  listJobInfos()
+}
+const onClickImportNode = () => {
+  listJobInfos()
+  importDrawerVisible.value = true
+}
+/** 引入其他类型节点, 判断,工作流 */
+const onClickImportSpecialNode = (data) => {
+  const { type } = data
+  if (type === 3) {
+    workflowVisible.value = true
+  } else {
+    importTask([
+      {
+        appId: workflowInfo.value.appId,
+        jobParams: 'true',
+        type: type,
+        workflowId: workflowInfo.value.id,
+        jobName: ''
+      }
+    ])
+  }
+}
+/** 引入嵌套工作流节点 */
+const onImportChildWorkflowNode = (data) => {
+  importTask([
+    {
+      appId: workflowInfo.value.appId,
+      nodeName: data.wfName,
+      enable: data.enable,
+      id: data.id,
+      nodeParams: '',
+      type: 3,
+      workflowId: workflowInfo.value.id,
+      jobName: data.wfName
+    }
+  ])
+}
+const onClickValidateTimeExpression = () => {
+  timeExpressionValidatorVisible.value = true
+}
+/** 选中节点 */
+const handleSelectedNode = (item) => {
+  selectNode.value = item
+
+  // 从节点列表找到节点
+  let index = getNodeIndexById(item.get('model').nodeId)
+
+  let node = taskList.value[index]
+  // if (node.type === "condition") return false;
+  remoteTaskData(null, node.jobId)
+  nodeInfo.value = {
+    type: node.nodeType,
+    jobId: node.jobId,
+    nodeName: node.nodeName ? node.nodeName : node.nodeType == 2 ? '判断' : node.nodeName,
+    nodeParams: node.nodeParams,
+    enable: node.enable,
+    skipWhenFailed: node.skipWhenFailed,
+    id: item.get('model').nodeId || item.get('model').id
+  }
+}
+/** 多选节点 */
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val
+}
+/** 修改节点名称 */
+const handleNodeName = (value) => {
+  const nodeItem = powerFlow.value.graph.get('selectedItem')
+  const group = nodeItem.getContainer()
+  const current = group.getChildByIndex(2)
+  current.attr('text', value)
+}
+/** 获取工作流程图实例 */
+const getDagsv = (powerFlowda, { resetNodesdata }) => {
+  powerFlow.value = powerFlowda
+  resetNodes.value = resetNodesdata
+}
+/** 根据nodeId找任务节点索引 */
+const getNodeIndexById = (nodeId) => {
+  return taskList.value.findIndex((item) => item.nodeId == nodeId)
+}
+/** 获取工作流信息 */
+const getWorkflowInfo = async (fit) => {
+  let params = {
+    workflowId: workflowInfo.value.id,
+    appId: workflowInfo.value.appId
+  }
+  const res = await getworkflowfetch(params)
+  // workflowInfo.value = { ...workflowInfo.value, ...res.data }
+  if (res.data.peworkflowDAG) {
+    taskList.value = res.data.peworkflowDAG.nodes
+    peworkflowDAG.value = res.data.peworkflowDAG
+    nextTick(() => {
+      resetNodes.value
+      if (fit) {
+        // this.powerFlow.graph.fitView(20);
+        // 改为layout适配会对节点少的时候友好一点
+        powerFlow.value.graph.layout()
+      }
+    })
+  }
+}
+/** 保存工作流全局信息 */
+const saveWorkflow = async () => {
+  // 改为不需要dag信息
+  const flowData = powerFlow.value.graph.save()
+  console.log(flowData)
+  let dagInfo = {
+    nodes: flowData.nodes.map((item) => ({ nodeId: item.id })),
+    edges: flowData.edges.map((item) => {
+      const property = {}
+      if (item.label) {
+        property.property = item.label === 'Y' ? 'true' : 'false'
+      }
+      return {
+        from: item.source,
+        to: item.target,
+        ...property
+      }
+    })
+  }
+  let params = {
+    ...workflowInfo.value,
+    dag: dagInfo
+  }
+  const lifeCycle = workflowInfo.value.lifeCycle
+  if (lifeCycle && Array.isArray(lifeCycle)) {
+    const start = new Date(lifeCycle[0]).getTime()
+    const end = new Date(lifeCycle[1]).getTime()
+    params.lifeCycle = {
+      start,
+      end
+    }
+  }
+  const res = await getworkflowsave(params)
+  ElMessage.success('成功')
+  if (!workflowInfo.value.id) workflowInfo.value.id = res
+}
+/** 导入任务节点数据 */
+const importTask = async (taskListArr) => {
+  if (taskListArr.length === 0) {
+    return
+  }
+
+  let type = ''
+
+  let data = taskListArr.map((item) => {
+    type = item.type ? Number(item.type) : 1
+    return {
+      appId: item.appId,
+      enable: item.enable,
+      skipWhenFailed: item.skipWhenFailed,
+      nodeName: item.jobName,
+      jobId: item.id,
+      nodeParams: item.jobParams,
+      workflowId: workflowInfo.value.id,
+      // type: "JOB"
+      type: type
+    }
+  })
+  let res = await getworkflowsaveNode(data)
+  console.log(res)
+  // 先移动视口一个节点点的位置
+  // 获取缩放比例
+  const zoom = powerFlow.value.graph.getZoom()
+  powerFlow.value.graph.translate(260 * zoom, 0)
+  const viewPointEnd = powerFlow.value.graph.getPointByCanvas(0, 0)
+  res.data.forEach((item, index) => {
+    const nodeText = nodeType.value[type](item)
+    powerFlow.value.graph.add('node', {
+      ...item,
+
+      id: `${item.id}`,
+      nodeId: `${item.id}`,
+      nodeType: `${item.type}`,
+      // type: nodeType[item.type],
+      size: [240, 70],
+      x: viewPointEnd.x + 20,
+      y: viewPointEnd.y + 70 * index + 20 + index * 10,
+      ...nodeText
+      // leftText: item.jobId,
+      // titleText: item.nodeName,
+      // icon1: item.enable ? require("../../assets/start.svg") : "",
+      // icon2: item.skipWhenFailed ? require("../../assets/skip.svg") : "",
+    })
+  })
+  let resData = res.data.map((item) => ({
+    ...item,
+    nodeType: item.type,
+    nodeParams: item.nodeParams,
+    nodeId: item.id
+  }))
+  let taskListAr = resData.concat(...taskList.value)
+  taskList.value = taskListAr
+}
+/** 保存单个节点 */
+const handleNodeSave = async (value = {}) => {
+  let data = [
+    {
+      ...nodeInfo.value,
+      appId: workflowInfo.value.appId,
+      workflowId: workflowInfo.value.id,
+      ...value
+    }
+  ]
+  await getworkflowsaveNode(data)
+
+  let index = getNodeIndexById(nodeInfo.value.id)
+
+  taskList.value[index] = {
+    ...taskList.value[index],
+    nodeName: nodeInfo.value.nodeName,
+    nodeParams: nodeInfo.value.nodeParams,
+    enable: nodeInfo.value.enable,
+    skipWhenFailed: nodeInfo.value.skipWhenFailed
+  }
+
+  ElMessage.success('成功')
+}
+/** 批量导入工作流 */
+const onBulkImport = async () => {
+  if (multipleSelection.value.length === 0) {
+    ElMessage.warning('请至少选中一条数据')
+    return
+  }
+  await importTask(multipleSelection.value)
+}
+/** 远程加载任务列表数据 */
+const remoteTaskData = async (value, jobId) => {
+  // clearTimeout(taskTimeout.value)
+  // taskTimeout.value = setTimeout(async () => {
+  //   taskLoading.value = true
+  //   const res = null
+  //   let params = {
+  //     ...this.jobQueryContent,
+  //     index: 0,
+  //     keyword: value,
+  //     jobId: jobId
+  //   }
+  //   if (this.nodeInfo.type === 3) {
+  //     res = await getworkflowtableList(params)
+  //   } else {
+  //     res = await getTasktableList(params)
+  //   }
+  //   waitTaskList.value = res.data
+  //   taskLoading.value = false
+  // }, 100)
+  clearTimeout(taskTimeout.value)
+  taskTimeout.value = setTimeout(async () => {
+    taskLoading.value = true
+    if (nodeInfo.value.type === 3) {
+      const res = await getworkflowtableList({
+        ...jobQueryContent.value,
+        index: 0,
+        keyword: value,
+        jobId: jobId
+      })
+      waitTaskList.value = res.data.data
+    } else {
+      const res = await getTasktableList({
+        ...jobQueryContent.value,
+        index: 0,
+        keyword: value,
+        jobId: jobId
+      })
+      waitTaskList.value = res.data.data
+    }
+    taskLoading.value = false
+  })
+}
+/** 选中任务时 */
+const handleWaitTaskChange = (value) => {
+  // 找到节点信息
+  let current = waitTaskList.value.find((item) => item.id === value)
+
+  let currentShape = selectNode.value.getContainer().getChildByIndex(1)
+
+  currentShape.attr({ text: current.id })
+  let index = getNodeIndexById(selectNode.value.get('model').nodeId)
+  powerFlow.value.graph.updateItem(selectNode.value, {
+    leftText: current.id,
+    jobId: current.id
+  })
+  taskList.value[index] = {
+    ...taskList.value[index],
+    jobId: current.id
+  }
+  nodeInfo.value.jobId = value
+}
+/** 节点外点击时单独处理 */
+const handleWaitFocus = () => {
+  powerFlow.value.graph.set('noKeyDown', true)
+}
+/** 判断节点参数改变 */
+const onCodeChange = (code) => {
+  nodeInfo.value.nodeParams = code
+}
+const changedatepicker = (value) => {
+  console.log('datepicker====>>>>', value)
+}
+onMounted(async () => {
+  const res = await getuserMessage()
+  userList.value = res.data
+  // 读取传递数据,如果是修改,需要先将数据绘制上去
+  let modify = route.currentRoute.value.query.modify
+  if (modify !== 'false') {
+    let queryData = JSON.parse(decodeURIComponent(route.currentRoute.value.query.workflowInfo))
+    if (queryData.lifeCycle) {
+      const { start, end } = queryData.lifeCycle
+      // queryData.lifeCycle = [common.getDateTime(new Date(start)), common.getDateTime(new Date(end))]
+      queryData.lifeCycle = [new Date(start), new Date(end)]
+    } else {
+      queryData.lifeCycle = null
+    }
+    workflowInfo.value = queryData
+    // workflowInfo.value = JSON.parse(decodeURIComponent(route.currentRoute.value.query.workflowInfo))
+    // if (workflowInfo.value.lifeCycle) {
+    //   const { start, end } = workflowInfo.value.lifeCycle
+    //   workflowInfo.value.lifeCycle = [
+    //     common.getDateTime(new Date(start)),
+    //     common.getDateTime(new Date(end))
+    //   ]
+    // } else {
+    //   workflowInfo.value.lifeCycle = null
+    // }
+    workflowInfo.value.appId = powerappId
+    getWorkflowInfo(true)
+  }
+})
+watch(
+  () => 'nodeInfo.enable',
+  (newVal, oldVal) => {
+    nodeInfoChange(startsvg, 3)
+  }
+)
+watch(
+  () => 'nodeInfo.skipWhenFailed',
+  (newVal, oldVal) => {
+    nodeInfoChange(skipsvg, 4)
+  }
+)
+</script>
+
+<style scoped>
+.el-input {
+  width: 80%;
+}
+.job-panl-icon {
+  vertical-align: middle;
+}
+.title {
+  display: inline-block;
+  margin: 5px 0;
+  font-size: 16px;
+  font-weight: bold;
+}
+.power-dag {
+  display: flex;
+}
+.job-panl {
+  /* border: 1px solid red; */
+  /* flex: 1; */
+  position: relative;
+  border-radius: 10px;
+  box-shadow: 0 10px 10px 1px #c0c0c0;
+  border-top-right-radius: 0px;
+  box-sizing: border-box;
+  margin: 0 10px;
+  padding: 10px;
+  height: 100%;
+}
+.job-panl-btn {
+  /* position: absolute;
+        bottom: 0; */
+  display: flex;
+  justify-content: flex-end;
+  width: 100%;
+  /* margin-right: 12px; */
+  box-sizing: border-box;
+  padding: 12px;
+  border-top: 1px solid #f0f0f0;
+}
+.job-tools {
+  width: calc(100% - 10px);
+  box-sizing: border-box;
+  border: 1px solid #d0d0d0;
+  /* box-shadow: 0 0 10px 1px #c0c0c0; */
+  border-radius: 10px;
+  margin-right: 10px;
+  border-bottom-left-radius: 0px;
+  border-bottom-right-radius: 0px;
+  display: flex;
+  padding: 8px 10px;
+}
+.job-tools > div {
+  box-sizing: border-box;
+  height: 30px;
+  width: 30px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+.job-tools div:hover {
+  border: 1px solid #f0f0f0;
+}
+.job-tools > div + div {
+  margin-left: 24px;
+}
+.job-tools i {
+  font-size: 20px;
+  /* color: #aaaaaa; */
+}
+.power-import-body {
+  padding: 0px 20px;
+}
+/* .el-drawer__body {
+  padding: 0 20px;
+} */
+</style>
+<!-- can't use scope, or dag will be the black block, maybe this is the bug of d3.js -->
+<style>
+.power-flow {
+  background: #fff;
+}
+
+.node rect {
+  stroke: #999;
+  fill: #fff;
+  stroke-width: 1.5px;
+}
+
+.edgePath path {
+  stroke: #333;
+  stroke-width: 1px;
+}
+.power-import-table .el-table-column--selection > .cell {
+  padding-left: 15px;
+}
+.judge-message-params {
+  font-size: 14px;
+  color: #606266;
+}
+.judge-message-params p {
+  margin-bottom: 4px;
+}
+</style>

+ 283 - 0
src/views/calculationAnalysis/calculationManagement/WorkflowManager.vue

@@ -0,0 +1,283 @@
+<template>
+  <div id="workflow_manager">
+    <!--第一行,条件搜索栏-->
+    <el-row :gutter="20">
+      <!-- 左侧搜索栏,占地面积 20/24 -->
+      <el-col :span="20">
+        <el-form :inline="true" :model="workflowQueryContent" class="el-form--inline">
+          <el-form-item label="工作流 ID">
+            <el-input v-model="workflowQueryContent.workflowId" placeholder="工作流 ID" />
+          </el-form-item>
+          <el-form-item label="关键字">
+            <el-input v-model="workflowQueryContent.keyword" placeholder="关键字" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="listWorkflow">查询</el-button>
+            <el-button type="cancel" @click="onClickReset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+
+      <!-- 右侧新增任务按钮,占地面积 4/24 -->
+      <el-col :span="4" v-if="!isWorkflow">
+        <div style="float: right; padding-right: 10px">
+          <el-button type="primary" @click="onClickNewWorkflow">新建工作流</el-button>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!--第二行,工作流数据表格-->
+    <el-row>
+      <el-table
+        :data="workflowPageResult.data"
+        style="width: 100%"
+        :type="isWorkflow ? 'selection' : null"
+      >
+        <el-table-column
+          :show-overflow-tooltip="true"
+          prop="id"
+          label="工作流 ID"
+          width="120"
+          align="center"
+        />
+        <el-table-column :show-overflow-tooltip="true" prop="wfName" label="工作流名称" />
+        <el-table-column :show-overflow-tooltip="true" label="定时信息">
+          <template #default="scope">
+            {{ scope.row.timeExpressionType }} {{ scope.row.timeExpression }}
+          </template>
+        </el-table-column>
+        <el-table-column :show-overflow-tooltip="true" label="状态" width="80" v-if="!isWorkflow">
+          <template #default="scope">
+            <el-switch
+              v-model="scope.row.enable"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              @change="switchWorkflow(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column :show-overflow-tooltip="true" label="操作" :width="isWorkflow ? 100 : 300">
+          <template #default="scope">
+            <el-row v-if="!isWorkflow">
+              <el-col :span="6">
+                <el-button size="mini" @click="onClickModifyWorkflow(scope.row)">编辑</el-button>
+              </el-col>
+              <el-col :span="6">
+                <el-button size="mini" @click="onClickCopy(scope.row)" :loading="copyLoading"
+                  >复制</el-button
+                >
+              </el-col>
+
+              <el-col :span="6">
+                <el-button size="mini" type="danger" @click="onClickDeleteWorkflow(scope.row)"
+                  >删除</el-button
+                >
+              </el-col>
+              <el-col :span="6">
+                <el-dropdown trigger="hover">
+                  <el-button size="mini" @click="onClickRunWorkflow(scope.row)">运行</el-button>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item>
+                        <el-button size="mini" type="text" @click="onClickRunByParameter(scope.row)"
+                          >参数运行</el-button
+                        >
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </el-col>
+            </el-row>
+            <el-row v-if="isWorkflow">
+              <el-col :span="6">
+                <el-button size="mini" @click="onImportNode(scope.row)">引入</el-button>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+
+    <!-- 第三行,分页插件 -->
+    <el-row>
+      <el-pagination
+        layout="prev, pager, next"
+        :total="workflowPageResult.totalItems"
+        :page-size="workflowPageResult.pageSize"
+        @current-change="onClickChangePage"
+        :hide-on-single-page="true"
+      />
+    </el-row>
+    <el-dialog title="参数运行" v-model="temporaryRowData" width="50%">
+      <el-input type="textarea" :rows="4" placeholder="填写参数" v-model="runParameter" />
+      <span default="footer" class="dialog-footer">
+        <el-button @click="onClickRunCancel">取消</el-button>
+        <el-button
+          type="primary"
+          @click="onClickRunWorkflow(temporaryRowData)"
+          :loading="runLoading"
+          >运行</el-button
+        >
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  getworkflowtableList,
+  getworkflowable,
+  getworkflowrun,
+  getdeleteworkflow,
+  getworkflowcopy
+} from '@/api/model/workflowManagement'
+import dataJson from './workflowManageDatajson.json'
+const powerappId = window.localStorage.getItem('powerAppId')
+const { push } = useRouter() // 路由跳转
+
+const props = defineProps({
+  isWorkflow: {
+    type: Boolean,
+    default: false
+  }
+})
+// 查询条件
+const workflowQueryContent = ref({
+  appId: powerappId,
+  index: 0,
+  pageSize: 10,
+  workflowId: undefined,
+  keyword: undefined
+})
+// 工作流查询结果
+const workflowPageResult = ref({
+  pageSize: 10,
+  totalItems: 0,
+  data: []
+})
+// 复制loading
+const copyLoading = ref(false)
+// 新建工作流对象
+const workflowObj = ref({})
+const temporaryRowData = ref(null)
+// 运行参数
+const runParameter = ref(null)
+// 运行loading
+const runLoading = ref(false)
+
+// 查询工作流
+const listWorkflow = async () => {
+  workflowPageResult.value = dataJson.data
+  const res = await getworkflowtableList(workflowQueryContent.value)
+  workflowPageResult.value = res.data
+}
+/** 引入嵌套工作流节点 */
+const emit = defineEmits(['onImportNode'])
+const onImportNode = (data) => {
+  emit('onImportNode', data)
+}
+// 点击重置
+const onClickReset = () => {
+  workflowQueryContent.value.workflowId = undefined
+  workflowQueryContent.value.keyword = undefined
+}
+// 开关工作流
+const switchWorkflow = async (data) => {
+  let path = data.enable ? 'enable' : 'disable'
+  let params = {
+    appId: powerappId,
+    workflowId: data.id
+  }
+  const res = await getworkflowable(path, params)
+  listWorkflow()
+}
+// 编辑工作流
+const onClickModifyWorkflow = (data) => {
+  push({
+    name: 'workflowEditor',
+    query: {
+      modify: true,
+      workflowInfo: encodeURIComponent(JSON.stringify(data))
+    }
+  })
+}
+// 立即运行工作流
+const onClickRunWorkflow = async (data) => {
+  let params = {
+    appId: powerappId,
+    workflowId: data.id
+  }
+  if (temporaryRowData.value && runParameter.value) {
+    params.initParams = encodeURIComponent(runParameter.value)
+  }
+  runLoading.value = true
+  const res = await getworkflowrun(params)
+  temporaryRowData.value = null
+  runLoading.value = false
+}
+// 参数运行
+const onClickRunByParameter = (data) => {
+  temporaryRowData.value = data
+}
+// 取消参数运行
+const onClickRunCancel = () => {
+  temporaryRowData.value = null
+  runParameter.value = null
+}
+// 删除工作流
+const onClickDeleteWorkflow = async (data) => {
+  let params = {
+    appId: powerappId,
+    workflowId: data.id
+  }
+  const res = await getdeleteworkflow(params)
+  ElMessage.success('成功')
+  listWorkflow()
+}
+
+// 新建工作流
+const onClickNewWorkflow = () => {
+  push({
+    name: 'workflowEditor',
+    query: {
+      modify: false
+    }
+  })
+}
+// 点击换页
+const onClickChangePage = (index) => {
+  // 后端从0开始,前端从1开始
+  workflowQueryContent.value.index = index - 1
+  listWorkflow()
+}
+/** 复制工作流 */
+const onClickCopy = async (data) => {
+  copyLoading.value = true
+  let params = {
+    workflowId: data.id,
+    appId: workflowQueryContent.value.appId
+  }
+  const res = await getworkflowcopy(params)
+  push({
+    name: 'workflowEditor',
+    query: {
+      modify: true,
+      workflowInfo: {
+        ...data,
+        id: res
+      }
+    }
+  })
+  copyLoading.value = false
+  ElMessage.success('成功')
+}
+
+onMounted(() => {
+  listWorkflow()
+})
+</script>
+
+<style scoped>
+</style>

+ 417 - 0
src/views/calculationAnalysis/calculationManagement/workflowManageDatajson.json

@@ -0,0 +1,417 @@
+{
+  "success": true,
+  "data": {
+      "index": 0,
+      "pageSize": 10,
+      "totalPages": 1,
+      "totalItems": 8,
+      "data": [
+          {
+              "id": 10,
+              "wfName": "ceshi123_COPY",
+              "wfDescription": "ceshi123",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": {
+                  "start": 1722960000000,
+                  "end": 1723132800000
+              },
+              "gmtCreate": "2024-08-27T02:35:56.594+00:00",
+              "gmtModified": "2024-08-27T03:52:24.033+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 91,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 92,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 96,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "id": 7,
+              "wfName": "ceshi",
+              "wfDescription": "ceshi ",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": {
+                  "start": 1724812162000,
+                  "end": 1724947200000
+              },
+              "gmtCreate": "2024-08-26T07:56:54.876+00:00",
+              "gmtModified": "2024-08-27T02:29:49.127+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 70,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 71,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "id": 5,
+              "wfName": "fower_cal",
+              "wfDescription": "计算",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": null,
+              "gmtCreate": "2024-06-25T19:48:03.036+00:00",
+              "gmtModified": "2024-06-25T19:48:03.036+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 45,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 46,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 47,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": [
+                      {
+                          "from": 45,
+                          "to": 46,
+                          "property": null,
+                          "enable": null
+                      },
+                      {
+                          "from": 46,
+                          "to": 47,
+                          "property": null,
+                          "enable": null
+                      }
+                  ]
+              }
+          },
+          {
+              "id": 4,
+              "wfName": "fower_recal",
+              "wfDescription": "重算工作流",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": null,
+              "gmtCreate": "2024-06-25T19:43:42.617+00:00",
+              "gmtModified": "2024-08-02T08:58:42.810+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 42,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 43,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 44,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": [
+                      {
+                          "from": 42,
+                          "to": 43,
+                          "property": null,
+                          "enable": null
+                      },
+                      {
+                          "from": 43,
+                          "to": 44,
+                          "property": null,
+                          "enable": null
+                      }
+                  ]
+              }
+          },
+          {
+              "id": 3,
+              "wfName": "fower_assign",
+              "wfDescription": "常量指标",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": null,
+              "gmtCreate": "2024-06-25T19:35:57.697+00:00",
+              "gmtModified": "2024-06-25T19:36:50.103+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 36,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "id": 2,
+              "wfName": "fower_custom",
+              "wfDescription": "自定义指标统计",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": null,
+              "gmtCreate": "2024-06-25T19:34:18.342+00:00",
+              "gmtModified": "2024-06-25T19:40:51.708+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 32,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 33,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      },
+                      {
+                          "nodeId": 34,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "id": 1,
+              "wfName": "fower_basic",
+              "wfDescription": "基础测点统计",
+              "appId": 5,
+              "timeExpressionType": "API",
+              "timeExpression": "",
+              "maxWfInstanceNum": 1,
+              "enable": true,
+              "notifyUserIds": null,
+              "lifeCycle": null,
+              "gmtCreate": "2024-05-27T03:34:34.598+00:00",
+              "gmtModified": "2024-06-25T19:34:33.993+00:00",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 31,
+                          "nodeType": 1,
+                          "jobId": null,
+                          "nodeName": null,
+                          "instanceId": null,
+                          "nodeParams": null,
+                          "status": null,
+                          "result": null,
+                          "enable": null,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": null,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          }
+      ]
+  },
+  "message": null
+}

+ 327 - 0
src/views/calculationAnalysis/container/ContainerManager.vue

@@ -0,0 +1,327 @@
+<template>
+  <div>
+    <el-card class="box-card">
+      <template #header>
+        <div class="clearfix">
+          <span></span>
+          <el-button style="float: right" type="primary" @click="openDia">新增容器</el-button>
+        </div>
+      </template>
+      <div class="wrapper">
+        <div v-for="(item, key) in containerList" :key="key" class="item">
+          <div class="containerText"
+            ><span class="value">容器 ID</span><span class="value">{{ item.id }}</span></div
+          >
+          <div class="containerText"
+            ><span class="value"> 容器名称</span
+            ><span class="value">{{ item.containerName }}</span></div
+          >
+          <div class="containerText"
+            ><span class="value">容器类型</span
+            ><span class="value">{{ item.sourceType }}</span></div
+          >
+          <div class="containerText"
+            ><span class="value">容器版本</span><span class="value">{{ item.version }}</span></div
+          >
+          <div class="containerText"
+            ><span class="value">部署时间</span
+            ><span class="value">{{ item.lastDeployTime }}</span></div
+          >
+          <div class="containerText"
+            ><span class="value">状态</span><span class="value">{{ item.status }}</span></div
+          >
+          <div style="width: 240px; margin: 0 auto">
+            <div class="btnWrap"
+              ><el-button type="primary" @click="arrangeItem(item)">部署</el-button></div
+            >
+            <div class="btnWrap"
+              ><el-button type="primary" @click="editItem(item)">编辑</el-button></div
+            >
+            <div class="btnWrap"
+              ><el-button type="primary" @click="deleteItem(item, key)">删除</el-button></div
+            >
+            <div class="btnWrap"
+              ><el-button type="primary" @click="listOfItem(item)">机器列表</el-button></div
+            >
+          </div>
+        </div>
+      </div>
+    </el-card>
+    <el-dialog
+      title="新增容器"
+      v-model="dialogVisible"
+      width="50%"
+      @close="closeEdit"
+      :before-close="handleClose"
+    >
+      <el-form :model="form" label-width="150px" class="genTable" label-position="left">
+        <el-form-item label="容器名称">
+          <el-input v-model="form.containerName" />
+        </el-form-item>
+        <el-form-item label="容器类型">
+          <el-radio-group v-model="form.sourceType">
+            <el-radio value="Git">Git</el-radio>
+            <el-radio value="FatJar">FatJar</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form
+          v-if="form.sourceType == 'Git'"
+          :model="gitForm"
+          label-width="150px"
+          class="gitTable"
+          label-position="left"
+        >
+          <el-form-item label="Git 仓库地址">
+            <el-input v-model="gitForm.repo" />
+          </el-form-item>
+          <el-form-item label="分支名称">
+            <el-input v-model="gitForm.branch" />
+          </el-form-item>
+          <el-form-item label="用户名">
+            <el-input v-model="gitForm.username" />
+          </el-form-item>
+          <el-form-item label="密码">
+            <el-input v-model="gitForm.password" />
+          </el-form-item>
+        </el-form>
+        <el-form-item v-if="form.sourceType == 'FatJar'">
+          <el-upload
+            class="upload-demo"
+            drag
+            :file-list="fileList"
+            :on-success="onSuccess"
+            :action="`${requestUrl}/container/jarUpload`"
+            multiple
+          >
+            <i class="el-icon-upload"></i>
+            <div class="el-upload__text">Drag the file here, or <em>click on Upload</em></div>
+            <template #tip>
+              <div class="el-upload__tip">{{ $t('message.uploadTips') }}</div>
+            </template>
+          </el-upload>
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            type="primary"
+            @click="onSubmit"
+            :disabled="form.sourceType == 'FatJar' && !sourceInfo"
+            >保存</el-button
+          >
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+    <el-dialog :title="arrangeTitle" v-model="arrangeVisible" @close="closeArrange">
+      <h4 v-for="log in logs" :key="log">{{ log }}</h4>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch, reactive } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  getcontainersave,
+  getcontainerlist,
+  getcontainerdelete,
+  getcontainerlistDeployedWorker
+} from '@/api/model/container'
+import dataJson from './dataJson.json'
+const powerappId = window.localStorage.getItem('powerAppId')
+const form = reactive({
+  sourceType: 'Git',
+  containerName: ''
+})
+const formsourceType = ref('Git')
+const gitForm = ref({
+  repo: '',
+  branch: '',
+  username: '',
+  password: ''
+})
+const sourceInfo = ref('')
+const id = ref('')
+const appId = ref(powerappId)
+const dialogVisible = ref(false)
+const arrangeTitle = ref('')
+const arrangeVisible = ref(false)
+const containerList = ref([])
+const logs = ref([])
+const requestUrl = ref('')
+const fileList = ref([])
+const ws = ref(null)
+
+const openDia = () => {
+  dialogVisible.value = true
+  form.sourceType = 'Git'
+  form.containerName = ''
+}
+const onSubmit = async () => {
+  // 接口参数
+  let data = {
+    appId: appId.value,
+    containerName: form.containerName,
+    status: 'ENABLE',
+    id: id.value,
+    sourceType: form.sourceType
+  }
+  if (form.sourceType == 'Git') {
+    data.sourceInfo = JSON.stringify(gitForm.value)
+  } else {
+    data.sourceInfo = sourceInfo.value
+    data.sourceType = 'FatJar'
+  }
+  const datas = await getcontainersave(data)
+  let params = {
+    appId: powerappId
+  }
+  const res = await getcontainerlist(params)
+  ElMessage.info('成功')
+  // 恢复默认表单
+  dialogVisible.value = false
+  form.containerName = ''
+  gitForm.value = {}
+  sourceInfo.value = ''
+  id.value = ''
+  // 刷新容器表单
+  containerList.value = res.data
+}
+// 文件上传成功后 修改来源信息
+const onSuccess = (response) => {
+  sourceInfo.value = response.data
+}
+const deleteItem = async (item, index) => {
+  let params = {
+    containerId: item.id,
+    appId: powerappId
+  }
+  const res = await getcontainerdelete(params)
+  containerList.value.splice(index, 1)
+  ElMessage.info('成功')
+}
+const editItem = (item) => {
+  if (item.sourceType == 'Git') {
+    form.sourceType = 'Git'
+    gitForm.value = JSON.parse(item.sourceInfo)
+  } else {
+    form.sourceType = 'FatJar'
+  }
+  form.containerName = item.containerName
+  id.value = item.id
+  dialogVisible.value = true
+}
+const arrangeItem = (item) => {
+  let wsBase = requestUrl.value.replace('http', 'ws') + '/container/deploy/'
+  let wsUrl = wsBase + item.id
+  ws.value = new WebSocket(wsUrl)
+
+  ws.value.onopen = () => {
+    arrangeTitle.value = '部署'
+    arrangeVisible.value = true
+    console.log('Connection open ...')
+    ws.value.send('Hello WebSockets!')
+  }
+
+  ws.value.onmessage = (evt) => {
+    logs.value.push(evt.data)
+  }
+
+  ws.value.onclose = () => {
+    console.log('Connection closed.')
+  }
+}
+// 关闭部署页面时 关闭ws避免dialog内的信息有上台机器信息
+const closeArrange = () => {
+  // ws.value.close()
+  arrangeVisible.value = false
+  logs.value = []
+}
+const closeEdit = () => {
+  sourceInfo.value = ''
+  fileList.value = []
+}
+const listOfItem = async (item) => {
+  let params = {
+    containerId: item.id,
+    appId: powerappId
+  }
+  const res = await getcontainerlistDeployedWorker(params)
+  if (res.data) {
+    logs.value = res.data.split('\n')
+    arrangeTitle.value = '机器列表'
+    arrangeVisible.value = true
+  }
+}
+// 兼容 java build in 模式下 baseURL 为 / 的情况(将当前url作为请求路径)
+const calculateRequestUrl = () => {
+  let baseUrl = 'http://123.60.223.250:7700'
+  if (baseUrl === undefined || !baseUrl.includes('http')) {
+    let url = window.location.href
+    let urlSplit = url.split('//') // str1[0]--协议头
+    let ip = urlSplit[1].split('/')[0]
+    requestUrl.value = urlSplit[0] + '//' + ip
+  } else {
+    requestUrl.value = baseUrl
+  }
+}
+onMounted(async () => {
+  calculateRequestUrl()
+  containerList.value = dataJson.data
+  const res = await getcontainerlist({
+    appId: powerappId
+  })
+  if (res.success) {
+    containerList.value = res.data
+  }
+})
+</script>
+
+<style scoped>
+.genTable {
+  padding: 20px;
+  min-width: 500px;
+  width: 500px;
+}
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: '';
+}
+.clearfix:after {
+  clear: both;
+}
+.wrapper {
+  display: flex;
+  flex-wrap: wrap;
+}
+.item {
+  flex: 0 0 340px;
+  margin-right: 20px;
+  margin-bottom: 20px;
+  background-color: #f0f0f0;
+}
+.item button {
+  width: 100px;
+  margin: 0 auto;
+}
+.btnWrap {
+  width: 50%;
+  float: left;
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: center;
+}
+.containerText {
+  margin: 20px;
+  font-size: 16px;
+  box-sizing: border-box;
+}
+.value {
+  display: inline-block;
+  max-width: 200px;
+  overflow: hidden;
+  margin-left: 20px;
+}
+.el-dialog {
+  height: 100vh;
+}
+</style>

+ 72 - 0
src/views/calculationAnalysis/container/ContainerTemplate.vue

@@ -0,0 +1,72 @@
+<template>
+  <el-card class="box-card">
+    <el-form ref="form" :model="form" label-width="150px" class="genTable" label-position="left">
+      <el-form-item label="Group">
+        <el-input v-model="form.group" />
+      </el-form-item>
+      <el-form-item label="Artifact">
+        <el-input v-model="form.artifact" />
+      </el-form-item>
+      <el-form-item label="Name">
+        <el-input v-model="form.name" />
+      </el-form-item>
+      <el-form-item label="Package name">
+        <el-input v-model="form.packageName" />
+      </el-form-item>
+      <el-form-item label="Java Version">
+        <el-radio-group v-model="form.javaVersion">
+          <el-radio label="8" />
+          <el-radio label="11" />
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="onSubmit">Generate</el-button>
+      </el-form-item>
+    </el-form>
+  </el-card>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getcontainerdownloadContainerTemplate } from '@/api/model/container'
+const form = ref({
+  group: '',
+  artifact: '',
+  name: '',
+  packageName: '',
+  javaVersion: ''
+})
+const onSubmit = async () => {
+  console.log('submit!')
+  const res = await getcontainerdownloadContainerTemplate(form.value)
+  const content = res
+  const blob = new Blob([content]) //构造一个blob对象来处理数据
+  const fileName = 'template.zip'
+
+  //对于<a>标签,只有 Firefox 和 Chrome(内核) 支持 download 属性
+  //IE10以上支持blob但是依然不支持download
+  if ('download' in document.createElement('a')) {
+    //支持a标签download的浏览器
+    const link = document.createElement('a') //创建a标签
+    link.download = fileName //a标签添加属性
+    link.style.display = 'none'
+    link.href = URL.createObjectURL(blob)
+    document.body.appendChild(link)
+    link.click() //执行下载
+    URL.revokeObjectURL(link.href) //释放url
+    document.body.removeChild(link) //释放标签
+  } else {
+    //其他浏览器
+    navigator.msSaveBlob(blob, fileName)
+  }
+}
+</script>
+
+<style scoped>
+.genTable {
+  padding: 20px;
+  min-width: 500px;
+  width: 500px;
+}
+</style>

+ 39 - 0
src/views/calculationAnalysis/container/dataJson.json

@@ -0,0 +1,39 @@
+{
+  "success": true,
+  "data": [
+      {
+          "id": 2,
+          "containerName": "ces0828",
+          "sourceType": "Git",
+          "sourceInfo": "{\"repo\":\"\",\"branch\":\"\",\"username\":\"\",\"password\":\"\"}",
+          "version": "init",
+          "status": "ENABLE",
+          "lastDeployTime": "2024-08-28 17:36:39",
+          "gmtCreate": "2024-08-28T09:26:26.395+00:00",
+          "gmtModified": "2024-08-28T09:36:39.429+00:00"
+      },
+      {
+        "id": 3,
+        "containerName": "ces0829",
+        "sourceType": "Git",
+        "sourceInfo": "{\"repo\":\"\",\"branch\":\"\",\"username\":\"\",\"password\":\"\"}",
+        "version": "init",
+        "status": "ENABLE",
+        "lastDeployTime": "2024-08-29 17:36:39",
+        "gmtCreate": "2024-08-29T09:26:26.395+00:00",
+        "gmtModified": "2024-08-29T09:36:39.429+00:00"
+    },
+    {
+      "id": 4,
+      "containerName": "ces08230",
+      "sourceType": "Git",
+      "sourceInfo": "{\"repo\":\"\",\"branch\":\"\",\"username\":\"\",\"password\":\"\"}",
+      "version": "init",
+      "status": "ENABLE",
+      "lastDeployTime": "2024-08-30 17:36:39",
+      "gmtCreate": "2024-08-30T09:26:26.395+00:00",
+      "gmtModified": "2024-08-30T09:36:39.429+00:00"
+  }
+  ],
+  "message": null
+}

+ 33 - 0
src/views/calculationAnalysis/homeIndex/dataJson.json

@@ -0,0 +1,33 @@
+{
+  "success": true,
+  "overviewdata": {
+      "jobCount": 8,
+      "runningInstanceCount": 0,
+      "failedInstanceCount": 0,
+      "timezone": "中国标准时间",
+      "serverTime": "2024-08-22 09:33:12",
+      "serverInfo": {
+          "id": 2,
+          "ip": "192.168.0.47",
+          "bornTime": 1722906402835,
+          "version": "4.3.9"
+      }
+  },
+  "tabledata": [
+        {
+            "address": "192.168.0.47:27777",
+            "cpuLoad": "-1 / 8 cores",
+            "memoryLoad": "17.4%(0.6 / 3.6 GB)",
+            "diskLoad": "27.6%(270 / 979.4 GB)",
+            "protocol": "HTTP",
+            "tag": "N/A",
+            "lastActiveTime": "2024-08-22 09:33:10",
+            "lightTaskTrackerNum": 0,
+            "heavyTaskTrackerNum": 0,
+            "lastOverloadTime": 0,
+            "overloading": false,
+            "status": 1
+        }
+    ],
+  "message": null
+}

+ 215 - 0
src/views/calculationAnalysis/homeIndex/index.vue

@@ -0,0 +1,215 @@
+<template>
+  <div id="home">
+    <!-- 第0行,显示时间信息 -->
+    <el-row :gutter="24">
+      <el-col :span="6">
+        <el-card shadow="always" style="text-align: center">
+          <div>应用名称</div>
+          <div> powerjob-worker-sample </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="always" style="text-align: center">
+          <div>
+            <a href="https://github.com/PowerJob/PowerJob" target="_blank">项目地址</a>
+          </div>
+          <div>
+            <a href="https://github.com/PowerJob/PowerJob/wiki" target="_blank">文档地址</a>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="always">
+          <div> 调度服务器时区:{{ systemInfo.timezone }} </div>
+          <div> 调度服务器时间:{{ systemInfo.serverTime }} </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card shadow="always">
+          <div> 本地时区:Asia/Shanghai </div>
+          <div> 本地时间:2024-08-21 10:21:54 </div>
+        </el-card>
+      </el-col>
+    </el-row>
+    <el-row :gutter="24">
+      <el-col :span="6">
+        <div class="wrap">
+          <div class="grid-content">
+            <div class="text mTitle">任务总数</div>
+            <div class="text mText">{{ systemInfo.jobCount }}</div>
+          </div>
+          <el-icon>
+            <Icon icon="ep:orange" />
+          </el-icon>
+        </div>
+      </el-col>
+      <el-col :span="6">
+        <div class="wrap">
+          <div class="grid-content">
+            <div class="text mTitle">当前运行实例数</div>
+            <div class="text mText">{{ systemInfo.runningInstanceCount }}</div>
+          </div>
+          <el-icon>
+            <Icon icon="ep:orange" />
+          </el-icon>
+        </div>
+      </el-col>
+      <el-col :span="6">
+        <div class="wrap">
+          <div class="grid-content">
+            <div class="text mTitle">近期失败任务数</div>
+            <div class="text mText">{{ systemInfo.failedInstanceCount }}</div>
+          </div>
+          <el-icon>
+            <Icon icon="ep:orange" />
+          </el-icon>
+        </div>
+      </el-col>
+      <el-col :span="6">
+        <div class="wrap">
+          <div class="grid-content">
+            <div class="text mTitle">集群机器数</div>
+            <div class="text mText">{{ activeWorkerCount }}</div>
+          </div>
+          <el-icon>
+            <Icon icon="ep:orange" />
+          </el-icon>
+        </div>
+      </el-col>
+    </el-row>
+    <el-row>
+      <el-col :span="24" style="padding: 0 10px">
+        <!-- 只要在el-table元素中定义了height属性,即可实现固定表头的表格,而不需要额外的代码 -->
+        <!-- 可以通过指定 Table 组件的 row-class-name 属性来为 Table 中的某一行添加 class,表明该行处于某种状 -->
+        <el-table
+          :data="workerList"
+          style="width: 100%"
+          height="60vh"
+          :row-class-name="workerTableRowClassName"
+        >
+          <el-table-column prop="address" label="机器地址" />
+          <el-table-column prop="cpuLoad" label="cpu占用" />
+          <el-table-column prop="memoryLoad" label="内存占用" />
+          <el-table-column prop="diskLoad" label="磁盘占用" />
+          <el-table-column prop="tag" label="tag" />
+          <el-table-column prop="lastActiveTime" label="上次上线时间" />
+        </el-table>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { getHometableList, getHomeOverviewData } from '@/api/model/homeIndex'
+import { onMounted, ref, onActivated, watch } from 'vue'
+import dataJson from './dataJson.json'
+const workerList = ref([])
+const activeWorkerCount = ref('N/A')
+const powerappId = window.localStorage.getItem('powerAppId')
+const systemInfo = ref({
+  jobCount: 'N/A',
+  runningInstanceCount: 'N/A',
+  failedInstanceCount: 'N/A',
+  serverTime: 'UNKNOWN',
+  timezone: 'UNKNOWN'
+})
+
+const init = () => {
+  changeTableData(dataJson.tabledata)
+  overviewChangeData(dataJson.overviewdata)
+  tableData()
+  overViewData()
+}
+const tableData = async () => {
+  let params = {
+    appId: powerappId
+  }
+  const res = await getHometableList(params)
+  changeTableData(res.data)
+}
+const changeTableData = (res) => {
+  res.sort((a, b) => a.status - b.status)
+  workerList.value = res
+  let num = 0
+  workerList.value.forEach((w) => {
+    if (w.status !== 9999) {
+      num++
+    }
+  })
+  activeWorkerCount.value = num
+}
+const overViewData = async () => {
+  let params = {
+    appId: powerappId
+  }
+  const res = await getHomeOverviewData(params)
+  overviewChangeData(res.data)
+}
+const overviewChangeData = (res) => {
+  systemInfo.value = res
+}
+const workerTableRowClassName = ({ row }) => {
+  switch (row.status) {
+    case 1:
+      return 'success-row'
+    case 2:
+      return 'warning-row'
+    case 9999:
+      return 'offline-row'
+    default:
+      return 'error-row'
+  }
+}
+onMounted(() => {
+  init()
+})
+</script>
+
+<style scoped>
+/* 头部信息 */
+.wrap {
+  background: #fff;
+  display: flex;
+  text-align: center;
+  justify-content: space-around;
+  align-items: center;
+  margin: 10px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
+  font-size: 1.5rem;
+  font-weight: bolder;
+  height: 131px;
+}
+.mTitle {
+  font-size: 16px;
+  color: #0f0f0fad;
+  margin-bottom: 8px;
+}
+.mText {
+  font-size: 18px;
+  color: #0f0f0fff;
+  margin-bottom: 8px;
+}
+
+.el-card {
+  margin: 10px;
+}
+</style>
+
+<!-- 全局属性,解决 element-ui 的 row-class-name 不生效问题 -->
+<style>
+.el-table .warning-row {
+  color: darkgoldenrod;
+}
+
+.el-table .success-row {
+  color: green;
+}
+
+.el-table .error-row {
+  color: red;
+}
+
+.el-table .offline-row {
+  color: darkgray;
+}
+</style>

+ 358 - 0
src/views/calculationAnalysis/taskManagement/InstanceManager.vue

@@ -0,0 +1,358 @@
+<template>
+  <div id="instance_manager">
+    <!-- 第一行,搜索区 -->
+    <el-row>
+      <el-col :span="22">
+        <el-form :inline="true" :model="instanceQueryContent" class="el-form--inline">
+          <el-form-item label="任务ID">
+            <el-input v-model="instanceQueryContent.jobId" placeholder="任务ID" />
+          </el-form-item>
+          <el-form-item label="任务实例 ID">
+            <el-input v-model="instanceQueryContent.instanceId" placeholder="任务实例 ID" />
+          </el-form-item>
+          <el-form-item v-if="instanceQueryContent.type === 'WORKFLOW'" label="工作流实例 ID">
+            <el-input v-model="instanceQueryContent.wfInstanceId" placeholder="工作流实例 ID" />
+          </el-form-item>
+          <el-form-item label="状态">
+            <el-select
+              v-model="instanceQueryContent.status"
+              style="width: 200px"
+              placeholder="状态"
+            >
+              <el-option
+                v-for="item in instanceStatusOptions"
+                :key="item.key"
+                :label="item.label"
+                :value="item.key"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="primary" @click="listInstanceInfos">查询</el-button>
+            <el-button type="cancel" @click="onClickRest">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+      <el-col :span="2">
+        <div style="float: right; padding-right: 10px">
+          <el-button type="primary" @click="listInstanceInfos">刷新</el-button>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 第二行,切换器 -->
+    <el-tabs type="card" v-model="instanceQueryContent.type" @tab-click="tabInstanceInfos">
+      <el-tab-pane label="普通任务实例" name="NORMAL" />
+      <el-tab-pane label="工作流任务实例" name="WORKFLOW" />
+    </el-tabs>
+
+    <!-- 第三行,表单 -->
+    <el-row>
+      <el-table
+        :data="instancePageResult.data"
+        style="width: 100%"
+        :row-class-name="instanceTableRowClassName"
+      >
+        <el-table-column
+          :show-overflow-tooltip="true"
+          prop="jobId"
+          label="任务ID"
+          width="80"
+          align="center"
+        />
+        <el-table-column :show-overflow-tooltip="true" prop="jobName" label="任务名称" />
+        <el-table-column
+          v-if="instanceQueryContent.type === 'WORKFLOW'"
+          :show-overflow-tooltip="true"
+          prop="wfInstanceId"
+          label="工作流实例 ID"
+          width="155"
+        />
+        <el-table-column :show-overflow-tooltip="true" prop="instanceId" label="任务实例 ID" />
+        <el-table-column prop="status" label="状态" width="160">
+          <template #default="scope">{{ fetchStatus(scope.row.status) }}</template>
+        </el-table-column>
+        <el-table-column prop="actualTriggerTime" label="触发时间" width="150" />
+        <el-table-column prop="finishedTime" label="结束时间" width="150" />
+
+        <el-table-column label="操作" width="285">
+          <template #default="scope">
+            <el-row style="justify-content: space-between">
+              <ei-col :span="6">
+                <el-button size="mini" type="primary" @click="onClickShowDetail(scope.row)"
+                  >详情</el-button
+                >
+              </ei-col>
+              <ei-col :span="6">
+                <el-button size="mini" type="success" @click="onClickShowLog(scope.row)"
+                  >日志</el-button
+                >
+              </ei-col>
+              <ei-col :span="6">
+                <el-button size="mini" type="warning" @click="onClickRetryJob(scope.row)"
+                  >重试</el-button
+                >
+              </ei-col>
+              <ei-col :span="6">
+                <el-button size="mini" type="danger" @click="onClickStop(scope.row)"
+                  >停止</el-button
+                >
+              </ei-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+
+    <!-- 第四行,分页插件 -->
+    <el-row>
+      <el-col :span="24">
+        <el-pagination
+          :total="instancePageResult.totalItems"
+          :page-size="instancePageResult.pageSize"
+          @current-change="onClickChangeInstancePage"
+          layout="prev, pager, next"
+        />
+      </el-col>
+    </el-row>
+
+    <!--  任务实例详情弹出框 -->
+    <el-dialog v-model="instanceDetailVisible" width="80%">
+      <div class="power-instance-detail-log">
+        <InstanceDetail :instance-id="currentInstanceId" :resultAll="true" />
+      </div>
+    </el-dialog>
+
+    <!-- 任务运行日志弹出框 -->
+    <el-dialog v-model="instanceLogVisible" width="80%">
+      <el-row>
+        <el-col :span="24" class="power-instance-log-download" style="margin-bottom: 20px">
+          <el-button
+            type="primary"
+            size="mini"
+            @click="onclickDownloadLog()"
+            icon="el-icon-download"
+            >下载</el-button
+          >
+        </el-col>
+      </el-row>
+      <div class="power-instance-log-dialog">
+        <el-row>
+          <el-col :span="24">
+            <h4 style="white-space: pre-line">{{ paginableInstanceLog.data }}</h4>
+          </el-col>
+        </el-row>
+      </div>
+      <el-row>
+        <el-col :span="24">
+          <el-pagination
+            :page-count="paginableInstanceLog.totalPages"
+            @current-change="onClickChangeLogPage"
+            layout="prev, pager, next"
+          />
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'InstanceManager' })
+import InstanceDetail from './common/InstanceDetail.vue'
+import { onMounted, ref, onActivated, watch } from 'vue'
+import {
+  getinstancetableList,
+  getinstanceretry,
+  getinstancstop,
+  getinstanclog,
+  getdownloadlog
+} from '@/api/model/taskManagement'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import * as common from '@/utils/common'
+import dataJson from './instanceDataJson.json'
+const powerappId = window.localStorage.getItem('powerAppId')
+const instanceQueryContent = ref({
+  appId: powerappId,
+  index: 0,
+  pageSize: 10,
+  instanceId: undefined,
+  wfInstanceId: undefined,
+  status: '',
+  jobId: undefined,
+  type: 'NORMAL'
+})
+// 任务实例状态选择
+const instanceStatusOptions = ref([
+  { key: '', label: '全部' },
+  { key: 'WAITING_DISPATCH', label: '等待派发' },
+  {
+    key: 'WAITING_WORKER_RECEIVE',
+    label: '等待Worker接收'
+  },
+  { key: 'RUNNING', label: '运行中' },
+  { key: 'FAILED', label: '失败' },
+  { key: 'SUCCEED', label: '成功' },
+  { key: 'CANCELED', label: '手动取消' },
+  { key: 'STOPPED', label: '手动停止' }
+])
+
+// 获取状态
+const fetchStatus = (s) => {
+  return common.translateInstanceStatus(s)
+}
+
+// 实例查询结果
+const instancePageResult = ref({
+  pageSize: 10,
+  totalItems: 0,
+  data: []
+})
+
+// 查询任务实例信息
+const listInstanceInfos = async () => {
+  changeData(dataJson)
+  const res = await getinstancetableList(instanceQueryContent.value)
+  changeData(res)
+}
+const changeData = (res) => {
+  instancePageResult.value = res.data
+}
+
+// 点击重置按钮
+const onClickRest = () => {
+  instanceQueryContent.value.jobId = undefined
+  instanceQueryContent.value.instanceId = undefined
+  instanceQueryContent.value.wfInstanceId = undefined
+  instanceQueryContent.value.status = ''
+  listInstanceInfos()
+}
+
+const tabInstanceInfos = (val) => {
+  changeData(dataJson)
+  instanceQueryContent.value.type = val.props.name
+  listInstanceInfos()
+}
+
+// 点击重跑
+const onClickRetryJob = async (data) => {
+  let params = {
+    instanceId: data.instanceId,
+    appId: powerappId
+  }
+  const res = await getinstanceretry(params)
+  ElMessage.success('成功')
+  listInstanceInfos()
+}
+// 点击停止实例
+const onClickStop = (data) => {
+  let params = {
+    instanceId: data.instanceId,
+    appId: powerappId
+  }
+  const res = getinstancstop(params)
+  ElMessage.success(this.$t('message.success'))
+  // 重新加载列表
+  listInstanceInfos()
+}
+// 日志查询对象
+const logQueryContent = ref({
+  instanceId: undefined,
+  index: 0
+})
+// 查看在线日志
+const onClickShowLog = (data) => {
+  logQueryContent.value.instanceId = data.instanceId
+  logQueryContent.value.index = 0
+  queryLog()
+}
+// 日志弹出框是否可见
+const instanceLogVisible = ref(false)
+// 日志对象
+const paginableInstanceLog = ref({
+  index: 0,
+  totalPages: 0,
+  data: ''
+})
+// 点击查询详情
+const instanceDetailVisible = ref(false)
+const currentInstanceId = ref(undefined)
+const onClickShowDetail = (data) => {
+  instanceDetailVisible.value = true
+  currentInstanceId.value = data.instanceId
+}
+// 查看日志
+const queryLog = async () => {
+  let params = {
+    instanceId: logQueryContent.value.instanceId,
+    index: logQueryContent.value.index,
+    appId: powerappId
+  }
+  const res = await getinstanclog(params)
+  paginableInstanceLog.value = res
+  instanceLogVisible.value = true
+}
+
+// 下载日志
+const onclickDownloadLog = async () => {
+  let params = {
+    instanceId: logQueryContent.value.instanceId,
+    appId: powerappId
+  }
+  const res = await getdownloadlog(params)
+  window.open(res)
+}
+// 换页
+const onClickChangeInstancePage = (index) => {
+  // 后端从0开始,前端从1开始
+  instanceQueryContent.value.index = index - 1
+  listInstanceInfos()
+}
+const instanceTableRowClassName = ({ row }) => {
+  switch (row.status) {
+    // 失败
+    case 4:
+      return 'error-row'
+    // 成功
+    case 5:
+      return 'success-row'
+    case 9:
+    case 10:
+      return 'warning-row'
+  }
+}
+// 查看其它页的在线日志
+const onClickChangeLogPage = (index) => {
+  logQueryContent.value.index = index - 1
+  queryLog()
+}
+const route = useRouter()
+onMounted(() => {
+  let jobId = route.currentRoute.value.query.jobId
+  if (jobId !== undefined) {
+    instanceQueryContent.value.jobId = jobId * 1
+  }
+  listInstanceInfos()
+})
+</script>
+
+<style scoped>
+.title {
+  display: inline-block;
+  margin: 5px 0;
+  font-size: 16px;
+  font-weight: bold;
+}
+.power-instance-log-download {
+  display: flex;
+  justify-content: flex-end;
+}
+.power-instance-log-dialog {
+  max-height: 400px;
+  overflow-y: scroll;
+}
+.power-instance-detail-log {
+  max-height: 500px;
+  overflow-y: scroll;
+}
+</style>

+ 92 - 0
src/views/calculationAnalysis/taskManagement/common/DailyTimeIntervalForm.vue

@@ -0,0 +1,92 @@
+<template>
+  <div>
+    <el-form ref="form" :model="dailyTimeIntervalExpress">
+      <el-form-item :label="$t('message.interval')">
+          <el-col :span="6">
+              <el-input v-model="dailyTimeIntervalExpress.interval"></el-input>
+          </el-col>
+      </el-form-item>
+
+      <el-form-item :label="$t('message.timeRange')">
+        <el-col :span="6">
+          <el-time-picker
+              v-model="dailyTimeIntervalExpress.startTimeOfDay"
+              :placeholder="$t('message.startTime')"
+              value-format="HH:mm:ss"
+              :picker-options="{
+                  format:'HH:mm:ss'
+              }"
+          >
+          </el-time-picker>
+        </el-col>
+        <el-col class="line" :span="2">-</el-col>
+        <el-col :span="6">
+          <el-time-picker
+              v-model="dailyTimeIntervalExpress.endTimeOfDay"
+              :placeholder="$t('message.endTime')"
+              value-format="HH:mm:ss"
+              :picker-options="{
+                  format:'HH:mm:ss'
+              }"
+          >
+          </el-time-picker>
+        </el-col>
+      </el-form-item>
+
+        <el-form-item :label="$t('message.weekRange')">
+            <el-checkbox-group v-model="dailyTimeIntervalExpress.daysOfWeek">
+                <el-checkbox v-for="c in weekDaysConstant" :label="c.key" :key="c.key">{{c.label}}</el-checkbox>
+            </el-checkbox-group>
+        </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" @click="onSubmit">{{$t('message.save')}}</el-button>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "DailyTimeIntervalForm",
+  // 数据传递
+  props: ["timeExpression"],
+  data() {
+    return {
+      dailyTimeIntervalExpress: {
+        interval: undefined,
+        startTimeOfDay: undefined,
+        endTimeOfDay: undefined,
+        intervalUnit: 'SECONDS',
+        daysOfWeek: []
+      },
+      weekDaysConstant: [
+          {key:1, label:'Monday'},
+          {key:2, label:'Tuesday'},
+          {key:3, label:'Wednesday'},
+          {key:4, label:'Thursday'},
+          {key:5, label:'Friday'},
+          {key:6, label:'Saturday'},
+          {key:7, label:'Sunday'},
+      ]
+    }
+  },
+  methods: {
+    onSubmit() {
+      //使用 $emit派发事件
+      this.$emit("contentChanged", JSON.stringify(this.dailyTimeIntervalExpress));
+    }
+  }
+  ,mounted() {
+      console.log("dailyTimeIntervalExpress:" + this.timeExpression);
+      if (this.timeExpression !== undefined && this.timeExpression !== null) {
+          this.dailyTimeIntervalExpress = JSON.parse(this.timeExpression);
+      }
+
+  }
+}
+</script>
+
+<style scoped>
+
+</style>

+ 87 - 0
src/views/calculationAnalysis/taskManagement/common/Exporter.vue

@@ -0,0 +1,87 @@
+<template>
+  <div>
+    <el-input
+      type="textarea"
+      placeholder="请输入内容"
+      v-model="jsonContent"
+      :autosize="{ minRows: 8, maxRows: 256 }"
+      :disabled="mode === 'EXPORT'"
+    />
+
+    <el-button type="info" @click="onClickCancelButton">取消</el-button>
+    <el-button type="primary" @click="onClickConfirmButton">确定</el-button>
+  </div>
+</template>
+
+<script setup>
+import { exportworkflow, exportJob, jobsave } from '@/api/model/taskManagement'
+import { onMounted, ref, onActivated, watch } from 'vue'
+const props = defineProps({
+  type: {
+    type: String,
+    default: ''
+  },
+  mode: {
+    type: String,
+    default: ''
+  },
+  targetId: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const jsonContent = ref(undefined)
+
+const emits = defineEmits(['finished'])
+const notifyParent = () => {
+  emits('finished', 'ok')
+}
+const fetchExportInfo = async (type, targetId) => {
+  let res = ''
+  if (type === 'WORKFLOW') {
+    let params = {
+      workflowId: targetId
+    }
+    res = await exportworkflow(params)
+  } else {
+    let params = {
+      jobId: targetId
+    }
+    res = await exportJob(params)
+  }
+  jsonContent.value = JSON.stringify(res.data)
+}
+const input = async () => {
+  if (jsonContent.value === undefined || jsonContent.value.length === 0) {
+    return
+  }
+  const res = await jobsave(JSON.parse(jsonContent.value))
+}
+
+const onClickCancelButton = () => {
+  notifyParent()
+}
+
+const onClickConfirmButton = () => {
+  if (props.mode === 'INPUT') {
+    input()
+  }
+  notifyParent()
+}
+
+onMounted(() => {
+  console.log(
+    '[Exporter] mounted Exporter with params, type=%s, mode=%s, targetId=%s',
+    props.type,
+    props.mode,
+    props.targetId
+  )
+  if (props.mode === 'EXPORT') {
+    fetchExportInfo(props.type, props.targetId)
+  }
+})
+</script>
+
+<style scoped>
+</style>

+ 415 - 0
src/views/calculationAnalysis/taskManagement/common/InstanceDetail.vue

@@ -0,0 +1,415 @@
+<template>
+  <div class="power-job-panl">
+    <el-row>
+      <div class="power-job-button">
+        <el-button type="primary" @click="fetchInstanceDetail">刷新</el-button>
+        <el-button @click="handleToDetail">详情</el-button>
+      </div>
+    </el-row>
+    <div class="power-job-info" :style="{ width: fixedWidth ? fixedWidth : '100%' }">
+      <el-card>
+        <el-row class="job-detail-text" v-if="instanceDetail.nodeType != 2">
+          <el-col :span="24">
+            <span class="power-job-text">任务实例ID:</span>
+            <span class="title">{{ instanceId }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="job-detail-text">
+          <el-col :span="24">
+            <span
+              class="power-job-text"
+              :style="{ width: instanceDetail.nodeType == 2 ? '64px' : '' }"
+              >状态:</span
+            >
+            <span class="title">
+              {{ fetchStatus(instanceDetail.status) }}
+            </span>
+          </el-col>
+        </el-row>
+        <el-row
+          class="job-detail-text"
+          v-if="instanceDetail.nodeType != 2 && instanceDetail.nodeType != 3"
+        >
+          <el-col :span="24">
+            <span class="power-job-text">运行次数:</span>
+            <span class="title">{{ instanceDetail.runningTimes }}</span>
+          </el-col>
+        </el-row>
+        <el-row
+          class="job-detail-text"
+          v-if="instanceDetail.nodeType != 2 && instanceDetail.nodeType != 3"
+        >
+          <el-col :span="24">
+            <span class="power-job-text">TaskTracker 地址:</span>
+            <span class="title">{{ instanceDetail.taskTrackerAddress }}</span>
+          </el-col>
+        </el-row>
+        <el-row
+          class="job-detail-text"
+          v-if="instanceDetail.nodeType != 2 && instanceDetail.nodeType != 3"
+        >
+          <el-col :span="24">
+            <span class="power-job-text">预计执行时间:</span>
+            <span class="title">{{ instanceDetail.expectedTriggerTime }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="job-detail-text">
+          <el-col :span="24">
+            <span
+              class="power-job-text"
+              :style="{ width: instanceDetail.nodeType == 2 ? '64px' : '' }"
+              >开始时间:</span
+            >
+            <span class="title">{{
+              instanceDetail.actualTriggerTime || instanceDetail.startTime
+            }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="job-detail-text">
+          <el-col :span="24">
+            <span
+              class="power-job-text"
+              :style="{ width: instanceDetail.nodeType == 2 ? '64px' : '' }"
+              >结束时间:</span
+            >
+            <span class="title">{{ instanceDetail.finishedTime }}</span>
+          </el-col>
+        </el-row>
+        <el-row
+          class="job-detail-text"
+          v-if="instanceDetail.nodeType != 2 && instanceDetail.nodeType != 3"
+        >
+          <el-col :span="24">
+            <span class="power-job-text">节点参数:</span>
+            <span class="title">{{
+              instanceDetail.jobParams ? instanceDetail.jobParams : instanceDetail.nodeParams
+            }}</span>
+          </el-col>
+        </el-row>
+        <el-row
+          class="job-detail-text"
+          v-if="instanceDetail.nodeType != 2 && instanceDetail.nodeType != 3"
+        >
+          <el-col :span="24">
+            <span class="power-job-text">任务实例参数:</span>
+            <span class="title">{{ instanceDetail.instanceParams }}</span>
+          </el-col>
+        </el-row>
+        <el-row class="job-detail-text">
+          <el-col :span="24">
+            <div
+              :class="{
+                'power-job-result': true,
+                'power-job-result-detail': resultAll
+              }"
+            >
+              <span
+                class="power-job-text"
+                :style="{ width: instanceDetail.nodeType == 2 ? '64px' : '' }"
+                >任务结果:</span
+              >
+              <el-popover width="400" placement="right" trigger="click" v-if="!resultAll">
+                <div class="power-job-content-slot">
+                  {{ instanceDetail.result }}
+                </div>
+                <template #reference>
+                  <span
+                    class="power-job-content"
+                    :style="{
+                      width: fixedWidth ? `${fixedWidth - 200}px` : '400px'
+                    }"
+                    >{{ instanceDetail.result }}</span
+                  >
+                </template>
+
+                <!-- <i class="el-icon-chat-dot-square result" slot="reference"></i> -->
+              </el-popover>
+              <span v-if="resultAll" class="title">{{ instanceDetail.result }}</span>
+            </div>
+          </el-col>
+        </el-row>
+        <slot></slot>
+        <el-row
+          class="job-detail-text"
+          id="taskDetail"
+          v-if="instanceDetail.taskDetail && instanceDetail.nodeType != 2"
+        >
+          <span class="power-job-text">Task 统计:</span>
+          <span class="title">{{ instanceDetail.taskDetail }}</span>
+        </el-row>
+      </el-card>
+    </div>
+
+    <!-- <el-divider content-position="center" v-if="instanceDetail.subInstanceDetails"
+      >最近 10 条秒级任务历史记录</el-divider
+    >
+    <div
+      class="power-job-info"
+      v-if="instanceDetail.subInstanceDetails"
+      :style="{ width: fixedWidth ? fixedWidth : '100%' }"
+    >
+      <el-card>
+        <el-row>
+          <el-table :data="instanceDetail.subInstanceDetails" style="width: 100%">
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="subInstanceId"
+              :label="$t('message.subInstanceId')"
+              width="120"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="startTime"
+              :label="$t('message.startTime')"
+              width="160"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="finishedTime"
+              :label="$t('message.finishedTime')"
+              width="160"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              :label="$t('message.status')"
+              width="140"
+            >
+              <template #default="scope">{{
+                common.translateInstanceStatus(scope.row.status)
+              }}</template>
+            </el-table-column>
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="result"
+              :label="$t('message.result')"
+            />
+          </el-table>
+        </el-row>
+      </el-card>
+    </div> -->
+
+    <!-- MR任务 -->
+    <el-divider content-position="center" v-if="showQueriedTaskDetailInfoList"
+      >Task 查询结果记录</el-divider
+    >
+
+    <div
+      class="power-job-info"
+      v-if="showQueriedTaskDetailInfoList"
+      :style="{ width: fixedWidth ? fixedWidth : '100%' }"
+    >
+      <el-row>
+        <el-col :span="20">
+          <el-input v-model="queryInstanceDetailRequest.customQuery">
+            <template #prepend>select * from task_info where</template>
+            <template #append>limit 10</template>
+          </el-input>
+        </el-col>
+        <el-col :span="4">
+          <el-button type="primary" @click="fetchInstanceDetail">{{
+            $t('message.query')
+          }}</el-button>
+        </el-col>
+      </el-row>
+      <el-card>
+        <el-row>
+          <el-table :data="instanceDetail.queriedTaskDetailInfoList" style="width: 100%">
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="taskId"
+              label="taskId"
+              width="80"
+            />
+            <el-table-column :show-overflow-tooltip="true" prop="taskName" label="taskName" />
+
+            <el-table-column :show-overflow-tooltip="true" prop="taskContent" label="taskContent" />
+
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="processorAddress"
+              label="processorAddress"
+            />
+
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="failedCnt"
+              :label="$t('message.failedCnt')"
+              width="80"
+            />
+
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="statusStr"
+              :label="$t('message.status')"
+              width="80"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="createdTimeStr"
+              :label="$t('message.createdTime')"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="lastModifiedTimeStr"
+              :label="$t('message.lastModifiedTime')"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="lastReportTimeStr"
+              :label="$t('message.lastReportTime')"
+            />
+            <el-table-column
+              :show-overflow-tooltip="true"
+              prop="result"
+              :label="$t('message.result')"
+            />
+          </el-table>
+        </el-row>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getinstancedetailPlus } from '@/api/model/taskManagement'
+import * as common from '@/utils/common'
+const props = defineProps({
+  resultAll: {
+    type: Boolean,
+    default: false
+  },
+  fixedWidth: {
+    type: String,
+    default: ''
+  },
+  instanceId: {
+    type: Number,
+    default: 0
+  },
+  nodeDetail: {
+    type: Object,
+    // default: () => ({})
+    default: undefined
+  }
+})
+const instanceDetail = ref({
+  queriedTaskDetailInfoList: undefined
+})
+// 是否展示 queriedTaskDetailInfoList,只单向赋值为 true ,解决改变查询条件空数据后隐藏组件的问题
+const showQueriedTaskDetailInfoList = ref(false)
+
+const queryInstanceDetailRequest = ref({
+  instanceId: props.instanceId,
+  customQuery: 'status in (5, 6) order by last_modified_time desc'
+})
+// 获取状态
+const fetchStatus = (s) => {
+  return common.translateInstanceStatus(s)
+}
+const fetchInstanceDetail = async () => {
+  if (props.nodeDetail) {
+    instanceDetail.value = props.nodeDetail
+  } else {
+    const res = await getinstancedetailPlus(queryInstanceDetailRequest.value)
+    instanceDetail.value = res.data
+    if (instanceDetail.value) {
+      if (instanceDetail.value.queriedTaskDetailInfoList.length !== 0) {
+        showQueriedTaskDetailInfoList.value = true
+      }
+    }
+  }
+}
+const { push } = useRouter() // 路由跳转
+/** 查看详情 */
+const handleToDetail = () => {
+  push({
+    name: 'WFInstanceManager',
+    query: {
+      wfInstanceId: props.nodeDetail?.instanceId
+    }
+  })
+}
+
+watch(
+  () => props.instanceId,
+  (newVal, oldVal) => {
+    fetchInstanceDetail()
+  },
+  {
+    deep: true
+  }
+)
+
+onMounted(() => {
+  console.log('using InstanceId: ' + props.instanceId)
+  fetchInstanceDetail()
+})
+</script>
+
+<style scoped>
+*,
+*::after,
+*::before {
+  box-sizing: border-box;
+}
+.title {
+  display: inline-block;
+  /* margin: 5px 0; */
+  font-size: 14px;
+  font-weight: bold;
+  flex: 1;
+}
+.power-job-button {
+  display: flex;
+  width: 100%;
+  justify-content: flex-end;
+  padding-top: 5px;
+  padding-right: 5px;
+}
+.power-job-info {
+  padding: 5px 5px;
+}
+.power-job-text {
+  display: inline-block;
+  width: 148px;
+  text-align: right;
+  margin-right: 4px;
+  font-size: 14px;
+  word-break: break-all;
+  /* flex-basis: 148px; */
+}
+.result:hover {
+  transition: 0.5s;
+  color: #52aeff;
+}
+.power-job-result {
+  display: flex;
+  align-items: center;
+}
+.power-job-result.power-job-result-detail {
+  align-items: flex-start;
+}
+.power-job-content-slot {
+  max-height: 300px;
+  overflow-y: scroll;
+}
+.power-job-content {
+  /* width: 205px; */
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: inline-block;
+  font-size: 14px;
+  /* line-height: 20px; */
+}
+</style>
+<style>
+.el-tooltip__popper.is-dark {
+  max-width: 50%;
+  word-wrap: break-word;
+  white-space: normal;
+  word-break: break-all;
+}
+</style>

+ 39 - 0
src/views/calculationAnalysis/taskManagement/common/TimeExpressionValidator.vue

@@ -0,0 +1,39 @@
+<template>
+    <div>
+        <el-card class="box-card">
+            <div v-for="res in nextNTriggerTime" :key="res" class="text item">
+                {{ res }}
+            </div>
+        </el-card>
+    </div>
+</template>
+
+<script>
+    export default {
+        name: "TimeExpressionValidator",
+        // 数据传递
+        props: ["timeExpressionType", "timeExpression"],
+        data() {
+            return {
+                nextNTriggerTime: [],
+            }
+        },
+        methods: {
+            checkTimeExpression() {
+                let that = this;
+                let url = "/validate/timeExpression?timeExpressionType=" + this.timeExpressionType + "&timeExpression=" + this.timeExpression;
+                this.axios.get(url).then(res => that.nextNTriggerTime = res);
+            }
+        },mounted() {
+            console.log("type:" + this.timeExpressionType);
+            console.log("expression:" + this.timeExpression);
+            this.timeExpression = encodeURIComponent(this.timeExpression);
+            console.log("expressionAfterEncodeURIComponent: " + this.timeExpression);
+            this.checkTimeExpression();
+        }
+    }
+</script>
+
+<style scoped>
+
+</style>

+ 225 - 0
src/views/calculationAnalysis/taskManagement/common/WFInstanceManager.vue

@@ -0,0 +1,225 @@
+<template>
+  <div id="wf_instance_manager">
+    <!-- 第一行,搜索区 -->
+    <el-row>
+      <el-col :span="20">
+        <el-form :inline="true" :model="wfInstanceQueryContent" class="el-form--inline">
+          <el-form-item label="工作流 ID">
+            <el-input v-model="wfInstanceQueryContent.workflowId" placeholder="工作流 ID" />
+          </el-form-item>
+
+          <el-form-item label="工作流实例 ID">
+            <el-input v-model="wfInstanceQueryContent.wfInstanceId" placeholder="工作流实例 ID" />
+          </el-form-item>
+
+          <el-form-item label="状态">
+            <el-select
+              v-model="wfInstanceQueryContent.status"
+              placeholder="状态"
+              style="width: 200px"
+            >
+              <el-option
+                v-for="item in wfInstanceStatusOptions"
+                :key="item.key"
+                :label="item.label"
+                :value="item.key"
+              />
+            </el-select>
+          </el-form-item>
+
+          <el-form-item>
+            <el-button type="primary" @click="listWfInstances">查询</el-button>
+            <el-button type="cancel" @click="onClickRest">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+      <el-col :span="4">
+        <div style="float: right; padding-right: 10px">
+          <el-button type="primary" @click="listWfInstances">刷新</el-button>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 第二行,表单 -->
+    <el-row>
+      <el-table
+        :data="wfInstancePageResult.data"
+        style="width: 100%"
+        :row-class-name="wfInstanceTableRowClassName"
+      >
+        <el-table-column
+          :show-overflow-tooltip="true"
+          prop="workflowId"
+          label="工作流ID"
+          width="110"
+        />
+        <el-table-column :show-overflow-tooltip="true" prop="workflowName" label="工作流名称" />
+        <el-table-column :show-overflow-tooltip="true" prop="wfInstanceId" label="工作流实例 ID" />
+        <el-table-column :show-overflow-tooltip="true" prop="status" label="状态" width="160">
+          <template #default="scope">
+            {{ fetchWFStatus(scope.row.status) }}
+          </template>
+        </el-table-column>
+        <el-table-column :show-overflow-tooltip="true" prop="actualTriggerTime" label="触发时间" />
+        <el-table-column :show-overflow-tooltip="true" prop="finishedTime" label="结束时间" />
+
+        <el-table-column :show-overflow-tooltip="true" label="操作" width="225">
+          <template #default="scope">
+            <el-row>
+              <el-col :span="8">
+                <el-button type="primary" size="mini" @click="onClickShowDetail(scope.row)"
+                  >详情</el-button
+                >
+              </el-col>
+              <el-col :span="8">
+                <el-button type="danger" size="mini" @click="onClickStop(scope.row)"
+                  >停止</el-button
+                >
+              </el-col>
+              <el-col :span="8">
+                <el-button type="warning" size="mini" @click="restart(scope.row)">重试</el-button>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+
+    <!-- 第三行,分页插件 -->
+    <el-row>
+      <el-col :span="24">
+        <el-pagination
+          :total="wfInstancePageResult.totalItems"
+          :page-size="wfInstancePageResult.pageSize"
+          @current-change="onClickChangeInstancePage"
+          layout="prev, pager, next"
+        />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import {
+  getwfInstancelist,
+  getwfInstancestop,
+  getwfInstanceretry
+} from '@/api/model/taskManagement'
+import * as common from '@/utils/common'
+import dataJson from './wfinstanceManageDatajson.json'
+const powerappId = window.localStorage.getItem('powerAppId')
+// 查询条件
+const wfInstanceQueryContent = ref({
+  appId: powerappId,
+  index: 0,
+  pageSize: 10,
+  wfInstanceId: undefined,
+  workflowId: undefined,
+  status: ''
+})
+// 查询结果
+const wfInstancePageResult = ref({
+  pageSize: 10,
+  totalItems: 0,
+  data: []
+})
+// 工作流实例状态选择
+const wfInstanceStatusOptions = ref([
+  { key: '', label: '全部' },
+  { key: 'WAITING', label: '等待派发' },
+  { key: 'RUNNING', label: '运行中' },
+  { key: 'FAILED', label: '失败' },
+  { key: 'SUCCEED', label: '成功' },
+  { key: 'STOPPED', label: '手动停止' }
+])
+
+const fetchWFStatus = (s) => {
+  return common.translateWfInstanceStatus(s)
+}
+const listWfInstances = async () => {
+  wfInstancePageResult.value = dataJson.data
+  const res = await getwfInstancelist(wfInstanceQueryContent.value)
+  wfInstancePageResult.value = res.data
+}
+// 重置搜索条件
+const onClickRest = () => {
+  wfInstanceQueryContent.value.wfInstanceId = undefined
+  wfInstanceQueryContent.value.workflowId = undefined
+  wfInstanceQueryContent.value.status = ''
+  listWfInstances()
+}
+const { push } = useRouter() // 路由跳转
+// 查看工作流详情
+const onClickShowDetail = (data) => {
+  push({
+    name: 'WorkflowInstanceDetail',
+    query: {
+      wfInstanceId: data.wfInstanceId
+    }
+  })
+}
+// 停止工作流
+const onClickStop = async (data) => {
+  let params = {
+    wfInstanceId: data.wfInstanceId,
+    appId: powerappId
+  }
+  const res = await getwfInstancestop(params)
+  ElMessage.success('成功')
+  // 重新加载列表
+  listWfInstances()
+}
+// 换页
+const onClickChangeInstancePage = (index) => {
+  // 后端从0开始,前端从1开始
+  wfInstanceQueryContent.value.index = index - 1
+  listWfInstances()
+}
+// 表单颜色
+const wfInstanceTableRowClassName = ({ row }) => {
+  switch (row.status) {
+    // 失败
+    case 3:
+      return 'error-row'
+    // 成功
+    case 4:
+      return 'success-row'
+    case 10:
+      return 'warning-row'
+  }
+}
+// 重试
+const restart = async (row) => {
+  let params = {
+    appId: wfInstanceQueryContent.value.appId,
+    wfInstanceId: row.wfInstanceId
+  }
+  const res = await getwfInstanceretry(params)
+  listWfInstances()
+}
+
+onMounted(() => {
+  listWfInstances()
+})
+</script>
+
+<style>
+text {
+  font-weight: 300;
+  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  font-size: 14px;
+}
+
+.node rect {
+  stroke: #999;
+  fill: #fff;
+  stroke-width: 1.5px;
+}
+
+.edgePath path {
+  stroke: #333;
+  stroke-width: 1px;
+}
+</style>

+ 370 - 0
src/views/calculationAnalysis/taskManagement/common/WorkflowInstanceDetail.vue

@@ -0,0 +1,370 @@
+<template>
+  <div>
+    <el-row>
+      <div class="power-toolbtn">
+        <div>
+          <el-button type="primary" @click="back">返回</el-button>
+        </div>
+        <div>
+          <el-button @click="fetchWfInstanceInfo">刷新</el-button>
+          <el-button type="warning" @click="restart">重试</el-button>
+          <el-button type="danger" @click="stop">停止</el-button>
+        </div>
+      </div>
+    </el-row>
+
+    <el-row class="power-work-info-item">
+      <el-col :span="24">
+        状态:
+        <span class="title">{{ common.translateWfInstanceStatus(wfInstanceDetail.status) }}</span>
+      </el-col>
+    </el-row>
+
+    <el-row class="power-work-info-item">
+      <el-col :span="8">
+        工作流 ID:
+        <span class="title">{{ wfInstanceDetail.workflowId }}</span>
+      </el-col>
+      <el-col :span="16">
+        工作流实例 ID:
+        <span class="title">{{ wfInstanceDetail.wfInstanceId }}</span>
+      </el-col>
+    </el-row>
+    <el-row class="power-work-info-item">
+      <el-col :span="8">
+        预计执行时间:
+        <span class="title">{{ wfInstanceDetail.expectedTriggerTime }}</span>
+      </el-col>
+      <el-col :span="8">
+        触发时间:
+        <span class="title">{{ wfInstanceDetail.actualTriggerTime }}</span>
+      </el-col>
+      <el-col :span="8">
+        结束时间:
+        <span class="title">{{ wfInstanceDetail.finishedTime }}</span>
+      </el-col>
+    </el-row>
+    <el-row class="power-work-info-item">
+      <el-col :span="24">
+        启动参数:
+        <span class="title">{{ wfInstanceDetail.wfInitParams }}</span>
+      </el-col>
+    </el-row>
+    <el-row v-if="wfInstanceDetail.wfContext" class="power-work-info-item">
+      <div>
+        <el-col :span="24">
+          上下文:
+          <el-popover width="400" placement="top" trigger="click">
+            <div class="power-work-info-item-content">
+              <!-- <JsonViewer :value="JSON.parse(wfInstanceDetail.wfContext)" /> -->
+              <span>{{ JSON.parse(wfInstanceDetail.wfContext) }}</span>
+            </div>
+            <template #reference>
+              <span class="power-work-info-item-context">{{ wfInstanceDetail.wfContext }}</span>
+            </template>
+          </el-popover>
+        </el-col>
+      </div>
+    </el-row>
+    <el-row class="power-work-info-item">
+      <el-col :span="24">
+        任务结果(tips:点击节点可查看任务实例详情):
+        <span class="title">{{ wfInstanceDetail.result }}</span>
+      </el-col>
+    </el-row>
+    <el-row>
+      <div>
+        <PowerWorkFlow
+          v-if="peworkflowDAG.nodes.length > 0"
+          :rightFixed="421"
+          :nodes="peworkflowDAG.nodes"
+          :edges="peworkflowDAG.edges"
+          :selectNode="selectNode"
+          :defaultWidthInc="245"
+          :interceptSelectedNode="interceptSelectedNode"
+          mode="view"
+          @get-dag="getDag"
+          @on-selected-node="handleSelectedNode"
+          @on-clear-select-node="handleClearSelectNode"
+        >
+          <template #tool>
+            <div
+              @click="markedSuccess"
+              style="display: flex; justify-content: center; align-items: center; margin-left: 24px"
+            >
+              <el-tooltip content="标记成功" placement="top" effect="light">
+                <el-icon style="width: 20px; height: 20px"
+                  ><CircleCheck style="font-size: 20px"
+                /></el-icon>
+              </el-tooltip>
+            </div>
+            <div
+              @click="fetchWfInstanceInfo"
+              style="display: flex; justify-content: center; align-items: center; margin-left: 24px"
+            >
+              <el-tooltip content="刷新" placement="top" effect="light">
+                <el-icon style="width: 20px; height: 20px"
+                  ><Refresh style="font-size: 20px"
+                /></el-icon>
+              </el-tooltip>
+            </div>
+          </template>
+          <InstanceDetail
+            :instance-id="currentInstanceId"
+            :fixedWidth="400"
+            :nodeDetail="nodeDetail"
+          >
+            <el-row class="job-detail-text" v-if="nodeDetail && nodeDetail.nodeType != 2">
+              <el-col :span="24">
+                <span class="power-job-text">{{ $t('message.enable') }}:</span>
+                <span class="title">{{
+                  currentNodeInfo.enable ? $t('message.yes') : $t('message.no')
+                }}</span>
+              </el-col>
+            </el-row>
+            <el-row class="job-detail-text" v-if="nodeDetail && nodeDetail.nodeType != 2">
+              <el-col :span="24">
+                <span class="power-job-text">{{ $t('message.skipWhenFailed') }}:</span>
+                <span class="title">{{
+                  currentNodeInfo.skipWhenFailed ? $t('message.yes') : $t('message.no')
+                }}</span>
+              </el-col>
+            </el-row>
+            <el-row class="job-detail-text" v-if="nodeDetail && nodeDetail.nodeType == 2">
+              <el-col :span="24">
+                <span
+                  class="power-job-text"
+                  :style="{ width: nodeDetail.nodeType == 2 ? '64px' : '' }"
+                  >{{ $t('message.nodeParams') }}:</span
+                >
+                <div :style="{ paddingTop: '10px' }">
+                  <JSEditor
+                    :code="nodeDetail.nodeParams"
+                    key="nodeParams"
+                    :editorOptions="{ readOnly: true }"
+                  />
+                </div>
+              </el-col>
+            </el-row>
+          </InstanceDetail>
+        </PowerWorkFlow>
+      </div>
+    </el-row>
+
+    <!-- <el-dialog v-model="instanceDetailVisible" v-if="instanceDetailVisible">
+      <InstanceDetail :instance-id="currentInstanceId" :nodeDetail="nodeDetail" />
+    </el-dialog> -->
+  </div>
+</template>
+
+<script setup>
+import InstanceDetail from './InstanceDetail.vue'
+import PowerWorkFlow from '../../calculationManagement/PowerWorkflow.vue'
+// import JsonViewer from 'vue-json-viewer'
+import JSEditor from '../../calculationManagement/JSEditor.vue'
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import * as common from '@/utils/common'
+import { getwfInstanceinfo, getwfInstancemarkNodeAsSuccess } from '@/api/model/workflowManagement'
+import { getwfInstanceretry, getwfInstancestop } from '@/api/model/taskManagement'
+
+const wfInstanceDetail = ref({})
+// 任务实例详情
+const currentInstanceId = ref(undefined)
+const instanceDetailVisible = ref(false)
+const powerFlow = ref(null)
+const selectNode = ref(null)
+const powerappId = window.localStorage.getItem('powerAppId')
+/** 当前的节点信息 */
+const currentNodeInfo = ref({})
+const peworkflowDAG = ref({
+  nodes: [],
+  edges: []
+})
+const nodeDetail = ref(null)
+const route = useRouter()
+// 返回上一页
+const back = () => {
+  route.back()
+}
+/** 获取数据 */
+const fetchWfInstanceInfo = async () => {
+  // 从 router 获取 wfInstanceId
+  peworkflowDAG.value = {
+    nodes: [],
+    edges: []
+  }
+  const wfInstanceIdsv = route.currentRoute.value.query.wfInstanceId
+  let params = {
+    appId: powerappId,
+    wfInstanceId: wfInstanceIdsv
+  }
+  const res = await getwfInstanceinfo(params)
+  wfInstanceDetail.value = res.data
+  peworkflowDAG.value = res.data.peworkflowDAG
+}
+
+/** 标记成功 */
+const markedSuccess = async () => {
+  if (!(selectNode.value && selectNode.value.get('model').status == 4)) return
+
+  const data = {
+    appId: powerappId,
+    wfInstanceId: route.currentRoute.value.query.wfInstanceId,
+    nodeId: selectNode.value.get('model').id
+  }
+
+  const res = await getwfInstancemarkNodeAsSuccess({ params: data })
+
+  changeStatusSuccess()
+  ElMessage.success('成功')
+}
+
+/** 重试 */
+const restart = async () => {
+  const data = {
+    appId: powerappId,
+    wfInstanceId: route.currentRoute.value.query.wfInstanceId
+  }
+  const res = await getwfInstanceretry({
+    params: data
+  })
+  fetchWfInstanceInfo()
+}
+
+// 点击停止实例
+const stop = async () => {
+  let params = {
+    wfInstanceId: route.currentRoute.value.query.wfInstanceId,
+    appId: powerappId
+  }
+  const res = await getwfInstancestop(params).then(() => {})
+  ELMessage.success('成功')
+  await fetchWfInstanceInfo()
+}
+
+/** 更改状态为成功 */
+const changeStatusSuccess = () => {
+  const group = selectNode.value.getContainer()
+  /** 主图 */
+  const current0 = group.getChildByIndex(0)
+  /** 状态文字 */
+  const current2 = group.getChildByIndex(3)
+  /** 状态圆 */
+  const current3 = group.getChildByIndex(4)
+  current0.attr('fill', '#C3FFD2')
+  current2.attr('fill', '#00BB2F')
+  current2.attr('text', '成功')
+  current3.attr('fill', '#00BB2F')
+}
+
+/** node 拦截判断 */
+const interceptSelectedNode = (node) => {
+  const model = node.get('model')
+
+  return model.instanceId || model.nodeType == 2
+}
+
+/** 选中 node 回调 */
+const handleSelectedNode = (node) => {
+  const model = node ? node.get('model') : {}
+  const instanceId = model.instanceId
+  const type = model.nodeType
+  console.log(model)
+
+  console.log(instanceId)
+  if (type === 2 || type == 3) {
+    console.log('1111')
+    nodeDetail.value = model
+  } else {
+    if (!instanceId) ElMessage.warning('未生成任务实例,无法查看详情!')
+    nodeDetail.value = null
+  }
+
+  currentInstanceId.value = instanceId
+  selectNode.value = node
+  currentNodeInfo.value = node.get('model')
+}
+/** 清除 node 节点 */
+const handleClearSelectNode = () => {
+  selectNode.value = null
+}
+/** 获取工作流程图实例 */
+const getDag = (powerFlow) => {
+  powerFlow.value = powerFlow
+}
+onMounted(() => {
+  fetchWfInstanceInfo()
+})
+</script>
+
+<style scoped>
+*,
+*::after,
+*::before {
+  box-sizing: border-box;
+}
+/* .el-row {
+        margin: 20px;
+    } */
+
+.title {
+  display: inline-block;
+  /* margin:5px 0; */
+  font-size: 14px;
+  font-weight: bold;
+}
+
+svg {
+  font-size: 16px;
+}
+
+.node rect {
+  stroke: #606266;
+  fill: #fff;
+}
+
+.edgePath path {
+  stroke: #606266;
+  fill: #333;
+  stroke-width: 1.5px;
+}
+
+.power-toolbtn {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+}
+.power-work-info-item {
+  margin: 10px;
+}
+
+.power-work-info-item-content {
+  max-height: 300px;
+  overflow-y: scroll;
+}
+
+.power-work-info-item-context {
+  max-width: 600px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: inline-block;
+}
+</style>
+
+<style>
+.jv-container .jv-code {
+  padding: 8px;
+}
+.power-job-text {
+  display: inline-block;
+  width: 148px;
+  text-align: right;
+  margin-right: 4px;
+  font-size: 14px;
+}
+.job-detail-text {
+  padding: 5px 0;
+}
+</style>

+ 168 - 0
src/views/calculationAnalysis/taskManagement/common/wfinstanceManageDatajson.json

@@ -0,0 +1,168 @@
+{
+  "success": true,
+  "data": {
+      "index": 0,
+      "pageSize": 10,
+      "totalPages": 1,
+      "totalItems": 3,
+      "data": [
+          {
+              "wfInstanceId": "709087209684205824",
+              "workflowId": "10",
+              "workflowName": "ceshi123_COPY",
+              "status": 4,
+              "wfInitParams": null,
+              "wfContext": "{}",
+              "result": "middle job failed",
+              "expectedTriggerTime": "2024-08-28 16:59:24",
+              "actualTriggerTime": "2024-08-28 16:59:24",
+              "finishedTime": "2024-08-28 16:59:25",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 91,
+                          "nodeType": 1,
+                          "jobId": 8,
+                          "nodeName": "CUSTOM_DXKLYXS测试",
+                          "instanceId": "709087210057498880",
+                          "nodeParams": "{\n    \"formula\": \"(ZJTS*RLXS-SBGZXS-SBJXXS)/(ZJTS*RLXS)\",\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"CUSTOM\",\n    \"result\": \"DXKLYXS\"\n}",
+                          "status": 4,
+                          "result": "no worker available",
+                          "enable": true,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": "2024-08-28 16:59:25"
+                      },
+                      {
+                          "nodeId": 92,
+                          "nodeType": 1,
+                          "jobId": 4,
+                          "nodeName": "BEGIN",
+                          "instanceId": "709087210112024832",
+                          "nodeParams": "",
+                          "status": 4,
+                          "result": "no worker available",
+                          "enable": true,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": "2024-08-28 16:59:25"
+                      },
+                      {
+                          "nodeId": 96,
+                          "nodeType": 3,
+                          "jobId": 8,
+                          "nodeName": "ceshi123",
+                          "instanceId": "709087210216882432",
+                          "nodeParams": null,
+                          "status": 4,
+                          "result": "middle job failed",
+                          "enable": true,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": "2024-08-28 16:59:25",
+                          "finishedTime": "2024-08-28 16:59:25"
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "wfInstanceId": "709087210216882432",
+              "workflowId": "8",
+              "workflowName": "ceshi123",
+              "status": 3,
+              "wfInitParams": "{}",
+              "wfContext": "{}",
+              "result": "middle job failed",
+              "expectedTriggerTime": "2024-08-28 16:59:25",
+              "actualTriggerTime": "2024-08-28 16:59:25",
+              "finishedTime": "2024-08-28 16:59:25",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 86,
+                          "nodeType": 1,
+                          "jobId": 2,
+                          "nodeName": "CUSTOM_DXKLYXS",
+                          "instanceId": "709087211286429952",
+                          "nodeParams": "{\n    \"formula\": \"(ZJTS*RLXS-SBGZXS-SBJXXS)/(ZJTS*RLXS)\",\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"CUSTOM\",\n    \"result\": \"DXKLYXS\"\n}",
+                          "status": 4,
+                          "result": "no worker available",
+                          "enable": true,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": "2024-08-28 16:59:25"
+                      },
+                      {
+                          "nodeId": 87,
+                          "nodeType": 1,
+                          "jobId": 4,
+                          "nodeName": "BEGIN",
+                          "instanceId": null,
+                          "nodeParams": "",
+                          "status": 5,
+                          "result": "disable node",
+                          "enable": false,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          },
+          {
+              "wfInstanceId": "708629096845803776",
+              "workflowId": "10",
+              "workflowName": "ceshi123_COPY",
+              "status": 10,
+              "wfInitParams": null,
+              "wfContext": "{}",
+              "result": "middle job failed",
+              "expectedTriggerTime": "2024-08-27 10:39:02",
+              "actualTriggerTime": "2024-08-27 10:39:02",
+              "finishedTime": "2024-08-28 16:58:26",
+              "peworkflowDAG": {
+                  "nodes": [
+                      {
+                          "nodeId": 91,
+                          "nodeType": 1,
+                          "jobId": 2,
+                          "nodeName": "CUSTOM_DXKLYXS",
+                          "instanceId": "709086965240168704",
+                          "nodeParams": "{\n    \"formula\": \"(ZJTS*RLXS-SBGZXS-SBJXXS)/(ZJTS*RLXS)\",\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"CUSTOM\",\n    \"result\": \"DXKLYXS\"\n}",
+                          "status": 4,
+                          "result": "no worker available",
+                          "enable": true,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": "2024-08-28 16:58:26"
+                      },
+                      {
+                          "nodeId": 92,
+                          "nodeType": 1,
+                          "jobId": 4,
+                          "nodeName": "BEGIN",
+                          "instanceId": null,
+                          "nodeParams": "",
+                          "status": 5,
+                          "result": "disable node",
+                          "enable": false,
+                          "disableByControlNode": null,
+                          "skipWhenFailed": false,
+                          "startTime": null,
+                          "finishedTime": null
+                      }
+                  ],
+                  "edges": []
+              }
+          }
+      ]
+  },
+  "message": null
+}

+ 404 - 0
src/views/calculationAnalysis/taskManagement/dataJson.json

@@ -0,0 +1,404 @@
+{
+  "success": true,
+  "data": {
+      "index": 0,
+      "pageSize": 10,
+      "totalPages": 1,
+      "totalItems": 8,
+      "data": [
+          {
+              "id": 2,
+              "jobName": "CUSTOM_DXKLYXS",
+              "jobDescription": "等效可利用系数",
+              "appId": 5,
+              "jobParams": "{\n    \"formula\": \"(ZJTS*RLXS-SBGZXS-SBJXXS)/(ZJTS*RLXS)\",\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"CUSTOM\",\n    \"result\": \"DXKLYXS\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessorDemo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": true,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-05-27T03:27:10.501+00:00",
+              "gmtModified": "2024-08-09T02:51:23.946+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 3,
+              "jobName": "CUSTOM_SBKLYL",
+              "jobDescription": "设备可利用系数",
+              "appId": 5,
+              "jobParams": "{\n    \"formula\": \"(ZJTS*RLXS-SBGZXS)/(ZJTS*RLXS)\",\n    \"range\": \"\",\n    \"type\": \"RG,CP,WP,PJ,LN\",\n    \"result\": \"DXKLYXS\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessor2Demo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-05-27T03:39:32.442+00:00",
+              "gmtModified": "2024-06-25T19:29:02.545+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 4,
+              "jobName": "BEGIN",
+              "jobDescription": "开始",
+              "appId": 5,
+              "jobParams": "",
+              "timeExpressionType": "API",
+              "timeExpression": "",
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessor3Demo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-05-27T06:14:21.163+00:00",
+              "gmtModified": "2024-06-25T19:39:25.713+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 5,
+              "jobName": "CUSTOM_ZHCYDL",
+              "jobDescription": "综合厂用电率",
+              "appId": 5,
+              "jobParams": "{\n    \"formula\": \"(FDL-SWDL-GWDL)/FDL\",\n    \"range\": \"\",\n    \"type\": \"RG,CP,WP,PJ,LN\",\n    \"result\": \"ZHCYDL\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessor4Demo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-05-27T06:14:55.368+00:00",
+              "gmtModified": "2024-06-25T19:29:17.498+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 6,
+              "jobName": "ASSIGN_AQTS",
+              "jobDescription": "安全天数",
+              "appId": 5,
+              "jobParams": "{\n    \"points\": \"point1,point2\",\n    \"range\": \"RG,CP,WP,PJ,LN\",\n    \"type\": \"ASSIGN\",\n    \"valaue\": \"100\",\n    \"result\": \"AQTS\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessorDemo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-06-25T19:12:45.137+00:00",
+              "gmtModified": "2024-07-01T01:52:04.673+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 7,
+              "jobName": "BASIC_RFDL",
+              "jobDescription": "日发电量",
+              "appId": 5,
+              "jobParams": "{\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"BASIC\",\n    \"method\": \"1\",\n    \"result\": \"RFDL\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessorDemo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-06-25T19:13:31.482+00:00",
+              "gmtModified": "2024-08-14T01:12:45.660+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 8,
+              "jobName": "RECAL",
+              "jobDescription": "重算",
+              "appId": 5,
+              "jobParams": "{\n    \"beginTime\": \"2024-06-26 00:00:00\",\n    \"intevl\": \"interval\",\n    \"times\": \"20\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessor3Demo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-06-25T19:39:03.369+00:00",
+              "gmtModified": "2024-06-25T19:45:54.878+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          },
+          {
+              "id": 9,
+              "jobName": "日发电量2",
+              "jobDescription": "日发电量2",
+              "appId": 5,
+              "jobParams": "{\n    \"range\": \"RG,CP,WP,PJ,LN,WT\",\n    \"type\": \"BASIC\",\n    \"method\": \"1\",\n    \"result\": \"RFDL\"\n}",
+              "timeExpressionType": "API",
+              "timeExpression": null,
+              "executeType": "STANDALONE",
+              "processorType": "BUILT_IN",
+              "processorInfo": "com.gyee.generation.processors.StandaloneProcessorDemo",
+              "maxInstanceNum": 0,
+              "concurrency": 5,
+              "instanceTimeLimit": 0,
+              "instanceRetryNum": 0,
+              "taskRetryNum": 1,
+              "enable": false,
+              "nextTriggerTime": null,
+              "nextTriggerTimeStr": "N/A",
+              "minCpuCores": 0.0,
+              "minMemorySpace": 0.0,
+              "minDiskSpace": 0.0,
+              "gmtCreate": "2024-08-14T01:13:33.404+00:00",
+              "gmtModified": "2024-08-14T01:13:52.973+00:00",
+              "designatedWorkers": "",
+              "maxWorkerCount": 0,
+              "notifyUserIds": [],
+              "extra": null,
+              "dispatchStrategy": "HEALTH_FIRST",
+              "dispatchStrategyConfig": null,
+              "lifeCycle": {
+                  "start": null,
+                  "end": null
+              },
+              "alarmConfig": {
+                  "alertThreshold": 0,
+                  "statisticWindowLen": 0,
+                  "silenceWindowLen": 0
+              },
+              "tag": null,
+              "logConfig": {
+                  "type": 1,
+                  "level": 2,
+                  "loggerName": null
+              },
+              "advancedRuntimeConfig": {
+                  "taskTrackerBehavior": null
+              }
+          }
+      ]
+  },
+  "message": null
+}

+ 926 - 0
src/views/calculationAnalysis/taskManagement/index.vue

@@ -0,0 +1,926 @@
+<template>
+  <div class="taskManagement" id="job_manager">
+    <!--第一行,条件搜索栏(row布局:gutter代表栅格间隔,span代表占用格数)-->
+    <el-row :gutter="20">
+      <!-- 左侧搜索栏,占地面积 16/24 -->
+      <el-col :span="20">
+        <el-form :inline="true" :model="jobQueryContent" class="el-form--inline">
+          <el-form-item label="任务ID">
+            <el-input v-model="jobQueryContent.jobId" placeholder="任务ID" />
+          </el-form-item>
+          <el-form-item label="关键字">
+            <el-input v-model="jobQueryContent.keyword" placeholder="关键字" />
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="taskListData">查询</el-button>
+            <el-button type="cancel" @click="onClickReset">重置</el-button>
+          </el-form-item>
+        </el-form>
+      </el-col>
+
+      <!-- 右侧新增任务按钮,占地面积 4/24 -->
+      <el-col :span="2">
+        <div style="float: right">
+          <el-button type="success" @click="onClickJobInputButton">导入任务</el-button>
+        </div>
+      </el-col>
+      <el-col :span="2">
+        <div style="float: left; padding-right: 10px">
+          <el-button type="primary" @click="onClickNewJob">新建任务</el-button>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!--第二行,任务数据表格-->
+    <el-row>
+      <el-table :data="jobInfoPageResult.data" style="width: 100%" height="70vh">
+        <el-table-column prop="id" label="任务ID" width="80" align="center" />
+        <el-table-column prop="jobName" label="任务名称" />
+        <el-table-column label="定时信息">
+          <template #default="scope">
+            {{ scope.row.timeExpressionType }} {{ scope.row.timeExpression }}
+          </template>
+        </el-table-column>
+        <el-table-column label="执行类型">
+          <template #default="scope">
+            {{ translateExecuteType(scope.row.executeType) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="处理器类型">
+          <template #default="scope">
+            {{ translateProcessorType(scope.row.processorType) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="80">
+          <template #default="scope">
+            <el-switch
+              v-model="scope.row.enable"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              @change="changeJobStatus(scope.row)"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150">
+          <template #default="scope">
+            <el-row>
+              <el-col :span="8">
+                <el-button size="mini" type="text" @click="onClickModify(scope.row)"
+                  >编辑</el-button
+                >
+              </el-col>
+              <el-col :span="8">
+                <el-button size="mini" type="text" @click="onClickRun(scope.row)">运行</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-dropdown trigger="click">
+                  <span
+                    class="el-dropdown-link"
+                    style="position: relative; top: 9px; color: #409eff; cursor: pointer"
+                  >
+                    更多
+                  </span>
+                  <template #dropdown>
+                    <el-dropdown-menu>
+                      <el-dropdown-item>
+                        <el-button size="mini" type="text" @click="onClickRunByParameter(scope.row)"
+                          >参数运行</el-button
+                        >
+                      </el-dropdown-item>
+                      <el-dropdown-item>
+                        <el-button size="mini" type="text" @click="onClickRunHistory(scope.row)"
+                          >运行记录</el-button
+                        >
+                      </el-dropdown-item>
+                      <el-dropdown-item>
+                        <el-button size="mini" type="text" @click="onClickCopyJob(scope.row)"
+                          >复制</el-button
+                        >
+                      </el-dropdown-item>
+                      <el-dropdown-item>
+                        <el-button
+                          size="mini"
+                          type="text"
+                          @click="onClickJobExportButton(scope.row)"
+                          >导出</el-button
+                        >
+                      </el-dropdown-item>
+                      <el-dropdown-item>
+                        <el-button size="mini" type="text" @click="onClickDeleteJob(scope.row)"
+                          >删除</el-button
+                        >
+                      </el-dropdown-item>
+                    </el-dropdown-menu>
+                  </template>
+                </el-dropdown>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-row>
+
+    <!-- 第三行,分页插件 -->
+    <el-row style="margin-top: 10px">
+      <el-pagination
+        layout="prev, pager, next"
+        :total="jobInfoPageResult.totalItems"
+        :page-size="jobInfoPageResult.pageSize"
+        @current-change="onClickChangePage"
+        :hide-on-single-page="false"
+      />
+    </el-row>
+
+    <el-dialog title="新建任务" class="taskDialog" v-model="modifiedJobFormVisible" width="80%">
+      <el-form :model="modifiedJobForm" label-width="120px">
+        <el-form-item label="任务名称">
+          <el-input v-model="modifiedJobForm.jobName" />
+        </el-form-item>
+        <el-form-item label="任务描述">
+          <el-input v-model="modifiedJobForm.jobDescription" />
+        </el-form-item>
+        <el-form-item label="任务参数">
+          <el-input v-model="modifiedJobForm.jobParams" type="textarea" :rows="5" />
+        </el-form-item>
+        <el-form-item label="定时信息">
+          <el-row style="width: 100%">
+            <el-col :span="8">
+              <el-select v-model="modifiedJobForm.timeExpressionType" placeholder="时间表达式类型">
+                <el-option
+                  v-for="item in timeExpressionTypeOptions"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="2">
+              <el-input
+                v-model="modifiedJobForm.timeExpression"
+                placeholder="CRON 填写 CRON 表达式,秒级任务填写整数,API 无需填写"
+                v-if="
+                  ['CRON', 'FIXED_DELAY', 'FIXED_RATE'].includes(modifiedJobForm.timeExpressionType)
+                "
+              />
+              <el-button
+                type="primary"
+                @click="onClickEditTimeExpression"
+                v-if="['DAILY_TIME_INTERVAL'].includes(modifiedJobForm.timeExpressionType)"
+                >点击编辑</el-button
+              >
+            </el-col>
+            <el-col :span="4">
+              <el-button
+                type="text"
+                @click="onClickValidateTimeExpression"
+                style="padding-left: 10px"
+                >校验定时参数</el-button
+              >
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="生命周期">
+          <el-row>
+            <el-col :span="9">
+              <el-date-picker
+                v-model="modifiedJobForm.lifeCycle"
+                type="datetimerange"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="timestamp"
+              />
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="执行配置">
+          <el-row>
+            <el-col :span="5">
+              <el-select v-model="modifiedJobForm.executeType" placeholder="执行类型">
+                <el-option
+                  v-for="item in executeTypeOptions"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+
+            <el-col :span="6">
+              <el-select v-model="modifiedJobForm.processorType" placeholder="处理器类型">
+                <el-option
+                  v-for="item in processorTypeOptions"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+
+            <el-col :span="13">
+              <el-input
+                v-model="modifiedJobForm.processorInfo"
+                :placeholder="verifyPlaceholder(modifiedJobForm.processorType)"
+              />
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="运行时配置">
+          <el-row>
+            <el-col :span="4">
+              <el-select v-model="modifiedJobForm.dispatchStrategy" placeholder="分发策略">
+                <el-option
+                  v-for="item in dispatchStrategy"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+
+            <el-col :span="5">
+              <el-input
+                v-if="modifiedJobForm.dispatchStrategy == 'SPECIFY'"
+                placeholder="分发策略配置"
+                v-model="modifiedJobForm.dispatchStrategyConfig"
+                class="ruleContent"
+              >
+                <template #prepend>分发策略配置</template>
+              </el-input>
+            </el-col>
+
+            <el-col :span="5">
+              <el-input
+                placeholder="最大实例数"
+                v-model="modifiedJobForm.maxInstanceNum"
+                class="ruleContent"
+              >
+                <template #prepend>最大实例数</template>
+              </el-input>
+            </el-col>
+            <el-col :span="5">
+              <el-input
+                placeholder="单机线程并发度"
+                v-model="modifiedJobForm.concurrency"
+                class="ruleContent"
+              >
+                <template #prepend>单机线程并发度</template>
+              </el-input>
+            </el-col>
+            <el-col :span="5">
+              <el-input
+                placeholder="运行时间限制(毫秒)"
+                v-model="modifiedJobForm.instanceTimeLimit"
+                class="ruleContent"
+              >
+                <template #prepend>运行时间限制(毫秒)</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="重试配置">
+          <el-row>
+            <el-col :span="12">
+              <el-input
+                placeholder="Instance 重试次数"
+                v-model="modifiedJobForm.instanceRetryNum"
+                class="ruleContent"
+              >
+                <template #prepend>Instance 重试次数</template>
+              </el-input>
+            </el-col>
+            <el-col :span="12">
+              <el-input
+                placeholder="Task 重试次数"
+                v-model="modifiedJobForm.taskRetryNum"
+                class="ruleContent"
+              >
+                <template #prepend>Task 重试次数</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="机器配置">
+          <el-row>
+            <el-col :span="8">
+              <el-input
+                placeholder="最低 CPU 核心数"
+                v-model="modifiedJobForm.minCpuCores"
+                class="ruleContent"
+              >
+                <template #prepend>最低 CPU 核心数</template>
+              </el-input>
+            </el-col>
+            <el-col :span="8">
+              <el-input
+                placeholder="最低内存(GB)"
+                v-model="modifiedJobForm.minMemorySpace"
+                class="ruleContent"
+              >
+                <template #prepend>最低内存(GB)</template>
+              </el-input>
+            </el-col>
+            <el-col :span="8">
+              <el-input
+                placeholder="最低磁盘空间(GB)"
+                v-model="modifiedJobForm.minDiskSpace"
+                class="ruleContent"
+              >
+                <template #prepend>最低磁盘空间(GB)</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="集群配置">
+          <el-row>
+            <el-col :span="16">
+              <el-input
+                placeholder="执行机器地址(可选,不指定代表全部;多值英文逗号分割)"
+                v-model="modifiedJobForm.designatedWorkers"
+                class="ruleContent"
+              >
+                <template #prepend>执行机器地址</template>
+              </el-input>
+            </el-col>
+            <el-col :span="8">
+              <el-input
+                placeholder="最大执行机器数量(0代表不限)"
+                v-model="modifiedJobForm.maxWorkerCount"
+                class="ruleContent"
+              >
+                <template #prepend>最大执行机器数量</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="报警配置">
+          <el-row>
+            <el-col :span="6">
+              <el-select
+                :style="{ width: '100%' }"
+                v-model="modifiedJobForm.notifyUserIds"
+                multiple
+                filterable
+                placeholder="选择报警通知人员"
+              >
+                <el-option
+                  v-for="user in userList"
+                  :key="user.id"
+                  :label="user.username"
+                  :value="user.id"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="6">
+              <el-input v-model="modifiedJobForm.alarmConfig.alertThreshold">
+                <template #prepend>错误阈值</template>
+              </el-input>
+            </el-col>
+            <el-col :span="6">
+              <el-input v-model="modifiedJobForm.alarmConfig.statisticWindowLen">
+                <template #prepend>统计窗口(s)</template>
+              </el-input>
+            </el-col>
+            <el-col :span="6">
+              <el-input v-model="modifiedJobForm.alarmConfig.silenceWindowLen">
+                <template #prepend>沉默窗口(s)</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item label="日志配置">
+          <el-row>
+            <el-col :span="6">
+              <el-select
+                v-model="modifiedJobForm.logConfig.type"
+                :style="{ width: '100%' }"
+                placeholder="日志类型"
+              >
+                <el-option
+                  v-for="item in logType"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="6">
+              <el-select
+                v-model="modifiedJobForm.logConfig.level"
+                :style="{ width: '100%' }"
+                placeholder="日志级别"
+              >
+                <el-option
+                  v-for="item in logLevel"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+            <el-col :span="12">
+              <el-input
+                v-if="[2, 4].includes(modifiedJobForm.logConfig.type)"
+                v-model="modifiedJobForm.logConfig.loggerName"
+              >
+                <template #prepend>Logger名称</template>
+              </el-input>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item label="高级设置">
+          <el-row>
+            <el-col :span="6">
+              <el-select
+                v-model="modifiedJobForm.advancedRuntimeConfig.taskTrackerBehavior"
+                placeholder="TaskTracker行为"
+              >
+                <el-option
+                  v-for="item in taskTrackerBehavior"
+                  :key="item.key"
+                  :label="item.label"
+                  :value="item.key"
+                />
+              </el-select>
+            </el-col>
+          </el-row>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="saveJob">保存</el-button>
+          <el-button @click="modifiedJobFormVisible = false">取消</el-button>
+        </el-form-item>
+      </el-form>
+    </el-dialog>
+
+    <el-dialog
+      :close-on-click-modal="false"
+      v-model="timeExpressionValidatorVisible"
+      v-if="timeExpressionValidatorVisible"
+    >
+      <TimeExpressionValidator
+        :time-expression="modifiedJobForm.timeExpression"
+        :time-expression-type="modifiedJobForm.timeExpressionType"
+      />
+    </el-dialog>
+
+    <!-- 时间表达式编辑 -->
+    <!-- <el-dialog :close-on-click-modal="false" v-model="timeExpressionEditorVisible" v-if='timeExpressionEditorVisible'>
+          <DailyTimeIntervalForm :timeExpression="modifiedJobForm.timeExpression" @contentChanged="eventFromDailyTimeIntervalExpress"></DailyTimeIntervalForm>
+        </el-dialog> -->
+
+    <!-- 任务导入导出 -->
+    <el-dialog :close-on-click-modal="false" v-model="jobExporterDialogVisible">
+      <Exporter
+        type="JOB"
+        :mode="jobExporterMode"
+        :target-id="jobExporterTargetId"
+        @finished="eventFromExporter"
+      />
+    </el-dialog>
+
+    <el-dialog title="参数运行" v-model="temporaryRowData" width="50%">
+      <el-input type="textarea" :rows="4" placeholder="填写参数" v-model="runParameter" />
+      <span default="footer" class="dialog-footer">
+        <el-button @click="onClickRunCancel">取消</el-button>
+        <el-button type="primary" @click="onClickRun(temporaryRowData)" :loading="runLoading"
+          >运行</el-button
+        >
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import TimeExpressionValidator from '../calculationManagement/TimeExpressionValidator.vue'
+// import DailyTimeIntervalForm from './common/DailyTimeIntervalForm'
+import Exporter from './common/Exporter.vue'
+import instanceManager from './InstanceManager.vue'
+import {
+  getTasktableList,
+  runingTask,
+  getcopyTaskMsg,
+  getjobList,
+  getDeletejob,
+  jobsave
+} from '@/api/model/taskManagement'
+import { onMounted, ref, onActivated, watch } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import dataJson from './dataJson.json'
+const { push } = useRouter() // 路由跳转
+const powerappId = window.localStorage.getItem('powerAppId')
+const jobQueryContent = ref({
+  appId: powerappId,
+  index: 0,
+  pageSize: 10,
+  jobId: undefined,
+  keyword: undefined
+})
+
+// 保存变更,包括新增和修改
+const saveJob = async () => {
+  const { lifeCycle, alarmConfig } = modifiedJobForm.value
+  if (lifeCycle && Array.isArray(lifeCycle)) {
+    const start = lifeCycle[0]
+    const end = lifeCycle[1]
+    modifiedJobForm.value.lifeCycle = {
+      start,
+      end
+    }
+  }
+  if (!alarmConfig.alertThreshold) {
+    alarmConfig.alertThreshold = 0
+  }
+  if (!alarmConfig.statisticWindowLen) {
+    alarmConfig.statisticWindowLen = 0
+  }
+  if (!alarmConfig.silenceWindowLen) {
+    alarmConfig.silenceWindowLen = 0
+  }
+  modifiedJobForm.value.alarmConfig = alarmConfig
+  const res = await jobsave(modifiedJobForm.value)
+  modifiedJobFormVisible.value = false
+  ElMessage.success('成功')
+  taskListData()
+}
+
+const modifiedJobFormVisible = ref(false)
+
+// 任务列表(查询结果),包含index、pageSize、totalPages、totalItems、data(List类型)
+const jobInfoPageResult = ref({
+  pageSize: 10,
+  totalItems: 0,
+  data: []
+})
+
+// 时间表达式选择类型
+const timeExpressionTypeOptions = ref([
+  { key: 'API', label: 'API' },
+  { key: 'CRON', label: 'CRON' },
+  { key: 'FIXED_RATE', label: '固定频率(毫秒)' },
+  { key: 'FIXED_DELAY', label: '固定延迟(毫秒)' },
+  { key: 'WORKFLOW', label: '工作流' },
+  { key: 'DAILY_TIME_INTERVAL', label: '每日固定间隔' }
+])
+
+// 执行方式类型
+const executeTypeOptions = ref([
+  { key: 'STANDALONE', label: '单机执行' },
+  { key: 'BROADCAST', label: '广播执行' },
+  { key: 'MAP', label: 'Map执行' },
+  { key: 'MAP_REDUCE', label: 'MapReduce 执行' }
+])
+
+// 处理器类型
+const processorTypeOptions = ref([
+  { key: 'BUILT_IN', label: '内建' },
+  { key: 'EXTERNAL', label: '外置(动态加载)' }
+])
+// 日志级别
+const logLevel = ref([
+  { key: 1, label: 'DEBUG' },
+  { key: 2, label: 'INFO' },
+  { key: 3, label: 'WARN' },
+  { key: 4, label: 'ERROR' },
+  { key: 99, label: 'OFF' }
+])
+// 日志类型
+const logType = ref([
+  { key: 1, label: 'ONLINE' },
+  { key: 2, label: 'LOCAL' },
+  { key: 3, label: 'STDOUT' },
+  { key: 4, label: 'LOCAL_AND_ONLINE' },
+  { key: 999, label: 'NULL' }
+])
+// 分发类型
+const dispatchStrategy = ref([
+  { key: 'HEALTH_FIRST', label: 'HEALTH_FIRST' },
+  { key: 'RANDOM', label: 'RANDOM' },
+  { key: 'SPECIFY', label: 'SPECIFY' }
+])
+// TaskTracker 表现
+const taskTrackerBehavior = ref([
+  { key: 1, label: 'NORMAL' },
+  { key: 11, label: 'PADDLING' }
+])
+
+// 新建任务对象
+const modifiedJobForm = ref({
+  id: undefined,
+  jobName: '',
+  jobDescription: '',
+  appId: powerappId,
+  jobParams: '',
+  timeExpressionType: '',
+  timeExpression: '',
+  executeType: '',
+  processorType: '',
+  processorInfo: '',
+  maxInstanceNum: 0,
+  concurrency: 5,
+  instanceTimeLimit: 0,
+  instanceRetryNum: 0,
+  taskRetryNum: 1,
+  dispatchStrategy: undefined,
+  dispatchStrategyConfig: undefined,
+
+  minCpuCores: 0,
+  minMemorySpace: 0,
+  minDiskSpace: 0,
+
+  enable: true,
+  designatedWorkers: '',
+  maxWorkerCount: 0,
+  notifyUserIds: [],
+  lifeCycle: null,
+  alarmConfig: {
+    alertThreshold: undefined,
+    statisticWindowLen: undefined,
+    silenceWindowLen: undefined
+  },
+  logConfig: {
+    logtype: 1,
+    loglevel: undefined,
+    loggerName: undefined
+  },
+  advancedRuntimeConfig: {
+    taskTrackerBehavior: undefined
+  }
+})
+
+// 任务导入按钮
+const onClickJobInputButton = () => {
+  jobExporterMode.value = 'INPUT'
+  jobExporterTargetId.value = undefined
+  jobExporterDialogVisible.value = true
+}
+const onClickChangePage = (index) => {
+  // 后端从0开始,前端从1开始
+  jobQueryContent.value.index = index - 1
+  taskListData()
+}
+
+const timeExpressionValidatorVisible = ref(false)
+// 点击校验
+const onClickValidateTimeExpression = () => {
+  timeExpressionValidatorVisible.value = true
+}
+
+// 新增任务,去除旧数据
+const onClickNewJob = () => {
+  modifiedJobForm.value = {
+    id: undefined,
+    jobName: undefined,
+    jobDescription: undefined,
+    jobParams: undefined,
+    timeExpression: undefined,
+    timeExpressionType: undefined,
+    processorInfo: undefined,
+    executeType: undefined,
+    lifeCycle: undefined,
+    processorType: undefined,
+    alarmConfig: {
+      alertThreshold: undefined,
+      statisticWindowLen: undefined,
+      silenceWindowLen: undefined
+    },
+    logConfig: {
+      type: 1,
+      level: undefined,
+      loggerName: undefined
+    },
+    advancedRuntimeConfig: {
+      taskTrackerBehavior: undefined
+    }
+  }
+  modifiedJobFormVisible.value = true
+}
+// 临时存储的行数据
+const temporaryRowData = ref(null)
+// 运行参数
+const runParameter = ref(null)
+const runLoading = ref(false)
+const onClickModify = (data) => {
+  // 修复点击编辑后再点击新增 行数据被清空 的问题
+  if (!data.alarmConfig) {
+    data.alarmConfig = {
+      alertThreshold: undefined,
+      statisticWindowLen: undefined,
+      silenceWindowLen: undefined
+    }
+  }
+  if (!data.lifeCycle) {
+    data.lifeCycle = null
+  }
+  modifiedJobForm.value = JSON.parse(JSON.stringify(data))
+  modifiedJobFormVisible.value = true
+}
+// 点击 立即运行按钮
+const onClickRun = async (data) => {
+  let params = {
+    jobId: data.id,
+    appId: powerappId
+  }
+  if (temporaryRowData.value && runParameter.value) {
+    // url += `&instanceParams=${encodeURIComponent(runParameter.value)}`
+    params.instanceParams = encodeURIComponent(runParameter.value)
+  }
+  runLoading.value = true
+  const res = await runingTask(params)
+  ElMessage.success('成功')
+  temporaryRowData.value = null
+  runLoading.value = false
+}
+// // 参数运行
+const onClickRunByParameter = (data) => {
+  temporaryRowData.value = data
+}
+// // 取消参数运行
+const onClickRunCancel = () => {
+  temporaryRowData.value = null
+  runParameter.value = null
+}
+// // 点击 删除任务
+const onClickDeleteJob = async (data) => {
+  let params = {
+    jobId: data.id
+  }
+  const res = await getDeletejob(params)
+  ElMessage.success('成功')
+  taskListData()
+}
+// 任务导入导出相关功能
+const jobExporterMode = ref(undefined)
+const jobExporterTargetId = ref(undefined)
+const jobExporterDialogVisible = ref(false)
+// 任务导出按钮
+const onClickJobExportButton = (row) => {
+  jobExporterMode.value = 'EXPORT'
+  jobExporterTargetId.value = row.id
+  jobExporterDialogVisible.value = true
+}
+// 任务导出组件的回调
+const eventFromExporter = (content) => {
+  console.log('receive callback from Exporter: ' + content)
+  jobExporterDialogVisible.value = false
+  if (jobExporterMode.value === 'INPUT') {
+    listJobInfos()
+  }
+}
+// 列出符合当前搜索条件的任务
+const listJobInfos = async () => {
+  const datas = await getjobList(jobQueryContent.value)
+  const res = datas.data
+  if (res && res.data) {
+    res.data = res.data.map((item) => {
+      const lifeCycle = item.lifeCycle
+      if (lifeCycle && lifeCycle.start && lifeCycle.end) {
+        item.lifeCycle = [lifeCycle.start, lifeCycle.end]
+      } else {
+        item.lifeCycle = null
+      }
+      return item
+    })
+  }
+
+  jobInfoPageResult.value = res
+}
+// // 点击 复制任务
+const onClickCopyJob = async (data) => {
+  let params = {
+    jobId: data.id
+  }
+  const res = await getcopyTaskMsg(params)
+  modifiedJobForm.value = res.data
+  modifiedJobFormVisible.value = true
+}
+// // 点击 历史记录
+const onClickRunHistory = (data) => {
+  console.log(JSON.stringify(data))
+  push({
+    name: 'InstanceManager',
+    query: {
+      jobId: data.id
+    }
+  })
+}
+
+const verifyPlaceholder = (processorType) => {
+  let res
+  switch (processorType) {
+    case 'BUILT_IN':
+      res = '全限定类名,eg:tech.powerjob.HelloWordProcessor'
+      break
+    case 'EXTERNAL':
+      res = '容器ID#全限定类名,eg:1#tech.powerjob.HelloWordProcessor'
+      break
+    case 'SHELL':
+      res = 'SHELL 脚本文件内容'
+      break
+    case 'PYTHON':
+      res = 'Python 脚本文件内容'
+  }
+  return res
+}
+
+const taskListData = async () => {
+  const datas = await getTasktableList(jobQueryContent.value)
+  changeTableData(datas)
+}
+const changeTableData = (datas) => {
+  const res = datas.data
+  console.log(res)
+  if (res && res.data) {
+    res.data = res.data.map((item) => {
+      const lifeCycle = item.lifeCycle
+      if (lifeCycle && lifeCycle.start && lifeCycle.end) {
+        item.lifeCycle = [lifeCycle.start, lifeCycle.end]
+      } else {
+        item.lifeCycle = null
+      }
+      return item
+    })
+  }
+  jobInfoPageResult.value = res
+}
+
+// 翻译执行类型
+const translateExecuteType = (executeType) => {
+  switch (executeType) {
+    case 'STANDALONE':
+      return '单机执行'
+    case 'BROADCAST':
+      return '广播执行'
+    case 'MAP_REDUCE':
+      return 'MapReduce 执行'
+    case 'MAP':
+      return 'Map执行'
+    default:
+      return 'UNKNOWN'
+  }
+}
+
+// 翻译处理器类型
+const translateProcessorType = (processorType) => {
+  if (processorType === 'EXTERNAL') {
+    return '外置(动态加载)'
+  }
+  return '内建'
+}
+
+const init = () => {
+  changeTableData(dataJson)
+  taskListData()
+}
+
+onMounted(() => {
+  init()
+})
+</script>
+
+<style >
+.job-editor-number {
+  display: flex;
+}
+.job-input-number {
+  background-color: #f5f7fa;
+  color: #909399;
+  /* vertical-align: middle; */
+  /* display: table-cell; */
+  position: relative;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 0 20px;
+  /* width: 1px; */
+  white-space: nowrap;
+  display: block;
+  border-top-right-radius: 0px;
+  border-bottom-right-radius: 0px;
+  line-height: 38px;
+  width: auto;
+}
+.el-input-number {
+  width: 100px;
+}
+
+.el-input-number .el-input {
+  width: 1000px;
+}
+.el-overlay .el-overlay-dialog .taskDialog {
+  margin-top: 5vh !important;
+}
+
+.taskDialog .el-dialog__body .el-form .el-form-item .el-form-item__content .el-row {
+  width: 100%;
+}
+</style>
+<style lang="sass">
+.taskDialog
+  .el-dialog__body
+    .el-form
+      .el-form-item
+        .el-form-item__content
+          .el-date-editor
+            width: 80%
+</style>

+ 48 - 0
src/views/calculationAnalysis/taskManagement/instanceDataJson.json

@@ -0,0 +1,48 @@
+{
+  "success": true,
+  "data": {
+      "index": 0,
+      "pageSize": 10,
+      "totalPages": 1,
+      "totalItems": 3,
+      "data": [
+          {
+              "jobId": "2",
+              "jobName": "CUSTOM_DXKLYXS",
+              "instanceId": "706859298197602432",
+              "wfInstanceId": "N/A",
+              "result": "",
+              "taskTrackerAddress": "192.168.0.47:27777",
+              "runningTimes": 1,
+              "status": 5,
+              "actualTriggerTime": "2024-08-22 13:26:29",
+              "finishedTime": "2024-08-22 13:26:29"
+          },
+          {
+              "jobId": "2",
+              "jobName": "CUSTOM_DXKLYXS",
+              "instanceId": "706855126303768704",
+              "wfInstanceId": "N/A",
+              "result": "",
+              "taskTrackerAddress": "192.168.0.47:27777",
+              "runningTimes": 1,
+              "status": 5,
+              "actualTriggerTime": "2024-08-22 13:09:54",
+              "finishedTime": "2024-08-22 13:09:54"
+          },
+          {
+              "jobId": "2",
+              "jobName": "CUSTOM_DXKLYXS",
+              "instanceId": "706516300456788096",
+              "wfInstanceId": "N/A",
+              "result": "",
+              "taskTrackerAddress": "192.168.0.47:27777",
+              "runningTimes": 1,
+              "status": 5,
+              "actualTriggerTime": "2024-08-21 14:43:32",
+              "finishedTime": "2024-08-21 14:43:32"
+          }
+      ]
+  },
+  "message": null
+}