2
0

2 Коммитууд 9629af140a ... a790884dd0

Эзэн SHA1 Мессеж Огноо
  sql715 a790884dd0 Merge branch 'master' of http://175.27.169.173:10880/zkqy-sass-platform/mec-cloud_-intelligent-manufacturing_crm 3 долоо хоног өмнө
  sql715 6c01484f53 业务表单 3 долоо хоног өмнө

+ 17 - 0
zkqy-ui/src/api/bpmprocess/process.js

@@ -175,3 +175,20 @@ export function getfromlist(data) {
   })
 }
 
+// 查询所有的业务脚本
+export function listBusinessFlowScript(query) {
+  return request({
+    url: '/system/SysBusinessFlowScript/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询所有的业务脚本页面
+export function listBusinessFlowVuePage(query) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/list',
+    method: 'get',
+    params: query
+  })
+}

+ 79 - 0
zkqy-ui/src/api/businessFlow/flowScript.js

@@ -0,0 +1,79 @@
+import request from '@/utils/request'
+
+// 查询业务脚本列表
+export function listSysBusinessFlowScript(query) {
+  return request({
+    url: '/system/SysBusinessFlowScript/list',
+    method: 'get',
+    params: query,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 查询业务脚本详细
+export function getSysBusinessFlowScript(id) {
+  return request({
+    url: '/system/SysBusinessFlowScript/' + id,
+    method: 'get',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 新增业务脚本
+export function addSysBusinessFlowScript(data) {
+  return request({
+    url: '/system/SysBusinessFlowScript',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 修改业务脚本
+export function updateSysBusinessFlowScript(data) {
+  return request({
+    url: '/system/SysBusinessFlowScript',
+    method: 'put',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 删除业务脚本
+export function delSysBusinessFlowScript(id) {
+  return request({
+    url: '/system/SysBusinessFlowScript/' + id,
+    method: 'delete',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+//  上传Java文件
+export function uploadScript(data) {
+  return request({
+    url: '/system/SysBusinessFlowScript/upload',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+//  下载Java模板文件
+export function downloadTemplateScript(data) {
+  return request({
+    url: '/system/SysBusinessFlowScript/download',
+    method: 'get',
+    baseURL: process.env.VUE_APP_BASE_API,
+    responseType: 'blob' // 关键!告诉axios接收二进制数据
+  })
+}
+//  下载Java模板文件
+export function downloadCodeScript(query) {
+  return request({
+    url: '/system/SysBusinessFlowScript/download/code',
+    method: 'get',
+    params: query,
+    baseURL: process.env.VUE_APP_BASE_API,
+    responseType: 'blob' // 关键!告诉axios接收二进制数据
+  })
+}

+ 221 - 0
zkqy-ui/src/api/businessFlow/vuePage.js

@@ -0,0 +1,221 @@
+import request from '@/utils/request'
+
+// 查询流程定义列表
+export function listVuePage(query) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/list',
+    method: 'get',
+    params: query,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+// /system/SysBusinessFlowVuePage/{id}
+// 查询流程定义详细
+export function getVuePage(processId) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/' + processId,
+    method: 'get',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 新增流程定义
+export function addVuePage(data) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 修改流程定义
+export function updateVuePage(data) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage',
+    method: 'put',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 删除流程定义
+export function delVuePage(processId) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/' + processId,
+    method: 'delete',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+//  上传Vue文件
+export function uploadVuePage(data) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/uploadPage',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+//  下载Vue模板文件
+export function downloadTemplateVue(data) {
+  return request({
+    url: '/system/SysBusinessFlowVuePage/download',
+    method: 'get',
+    baseURL: process.env.VUE_APP_BASE_API,
+    responseType: 'blob' // 关键!告诉axios接收二进制数据
+  })
+}
+// 查询所有的业务脚本
+export function listBusinessFlowScript(query) {
+  return request({
+    url: '/system/SysBusinessFlowScript/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询--按钮是否绑定脚本
+export function queryBtnScriptApi(data) {
+  return request({
+    url: '/system/vuePageLink/getByVueKeyAndContent',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+// 新增/修改--按钮绑定脚本
+export function addBtnScriptApi(data) {
+  return request({
+    url: '/system/vuePageLink/updateOrInsert',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+// 后面所有,如果vue页面用不到就删除
+//备份流程
+export function bpmBackups(data) {
+  return request({
+    url: 'system/process/bpmBackups',
+    method: 'put',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 导出流程文件
+export function exportFileProcess(processIds) {
+  console.log()
+  return request({
+    url: '/system/process/exportProcessFile/' + processIds,
+    method: 'get',
+    responseType: 'blob',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 启用版本状态
+// 新增流程定义
+export function enableProcess(data) {
+  return request({
+    url: '/system/process/enableProcess',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+
+// 新增流程配置
+export function addConfiguration(data) {
+  return request({
+    url: '/system/configuration/addProcessNodeConfiguration',
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+    // baseURL: 'http://192.168.110.59:8055',
+  })
+}
+
+// 修改流程配置
+export function updateConfiguration(data) {
+  return request({
+    url: '/system/configuration/updateProcessNodeConfiguration',
+    method: 'put',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+// 查询流程节点详情
+export function getNodeData(processKey) {
+  return request({
+    url: '/system/configuration/selectByProcessByKey/' + processKey,
+    method: 'get',
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+
+
+// 查询流程节点脚本列表
+export function listScript(query) {
+  return request({
+    url: '/system/script/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询流程节点脚本详细
+export function getScript(id) {
+  return request({
+    url: '/system/script/' + id,
+    method: 'get'
+  })
+}
+
+// 新增流程节点脚本
+export function addScript(data) {
+  return request({
+    url: '/system/script',
+    method: 'post',
+    data: data
+  })
+}
+
+// 下载模版文件
+export function downloadTheNodeTemplate(data) {
+  return request({
+    url: '/system/script/downloadTheNodeTemplate',
+    method: 'get',
+    responseType: 'blob', // 显式设置 responseType 为 blob
+    params: data,
+  })
+}
+// 下载需要检查的模版文件
+export function checkDownloadTheNodeTemplate(data) {
+  return request({
+    url: '/system/script/checkDownloadTheNodeTemplate',
+    method: 'get',
+    responseType: 'blob', // 显式设置 responseType 为 blob
+    params: data,
+  })
+}
+
+//触发异常节点
+export function triggerExceptionNode(data) {
+  return request({
+    // url: '/system/execute/node/triggerExceptionNode',
+    url: '/system/runbpm/process/triggerExceptionNode',//触发异常节点  1214版
+    method: 'post',
+    data: data,
+    baseURL: process.env.VUE_APP_BASE_API
+  })
+}
+//查询流程节点信息
+export function getfromlist(data) {
+  return request({
+    url: '/system/script/getfromlist',
+    method: 'get',
+  })
+}
+

+ 202 - 0
zkqy-ui/src/components/BusinessFlow/DynamicComponentWrapper.vue

@@ -0,0 +1,202 @@
+<template>
+  <div ref="display"></div>
+</template>
+<script>
+import Vue from 'vue';
+import { randomStr } from '@/utils/templateParser'
+
+export default {
+  name: 'DynamicComponentWrapper',
+  props: {
+    code: {
+      type: String,
+        default: ''
+      }
+    },
+    data () {
+      return {
+        component: null,
+        // 随机字符串
+        randomStr: randomStr(),
+        html: '',
+        js: '',
+        css: ''
+      }
+    },
+    watch: {
+      code () {
+        this.destroyCode();
+        this.renderCode();
+      }
+    },
+    mounted () {
+      this.renderCode();
+    },
+    methods: {
+      // source:.vue 文件代码,即 props: code;
+      // type:分割的部分,也就是 template、script、style。
+      getSource (source, type) {
+        const regex = new RegExp(`<${type}[^>]*>`);
+        let openingTag = source.match(regex);
+  
+        if (!openingTag) return '';
+        else openingTag = openingTag[0];
+  
+        return source.slice(source.indexOf(openingTag) + openingTag.length, source.lastIndexOf(`</${type}>`));
+      },
+      // 分隔传递进来的需要动态渲染的.vue文件的内容
+      splitCode () {
+        // vue的 <script> 部分一般都是以export default 开始的,但是在splitCode中,把它替换成了return
+        const script = this.getSource(this.code, 'script').replace(/export default/, 'return ');
+        const style = this.getSource(this.code, 'style');
+        // 在分割的外层套了一个 <div id="app"> 
+        // 防止使用者传递的 code 可能会忘记在外层包一个节点,导致组件没有根节点的报错。
+        const template = '<div id="app-template">' + this.getSource(this.code, 'template') + '</div>';
+  
+        this.js = script;
+        this.css = style;
+        this.html = template;
+      },
+      renderCode () {
+        this.splitCode();
+        if (this.html !== '' && this.js !== '') {
+          const parseStrToFunc = new Function(this.js)();
+          parseStrToFunc.template =  this.html;
+
+          // 保存当前组件的引用
+          const self = this;
+          // 添加标记,用于防止重复触发事件
+          let isEventTriggered = false;
+
+          // 重写组件的方法
+          const originalMethods = parseStrToFunc.methods || {};
+          const wrappedMethods = {};
+          
+          // 遍历所有方法并包装
+          Object.keys(originalMethods).forEach(methodName => {
+            wrappedMethods[methodName] = function(...args) {
+              // 获取触发事件的按钮元素
+              const button = event.target;
+              if (button && (button.tagName === 'BUTTON' || button.classList.contains('el-button'))) {
+                // 获取按钮的文本内容
+                const buttonText = button.textContent.trim();
+                // 使用保存的组件引用来发送事件
+                if (!isEventTriggered) {
+                  isEventTriggered = true;
+                  self.$emit('button-click', {
+                    text: buttonText
+                  });
+                  // 重置标记
+                  setTimeout(() => {
+                    isEventTriggered = false;
+                  }, 100);
+                }
+              }
+              
+              // 检查是否是弹窗相关的方法
+              const isDialogMethod = methodName.toLowerCase().includes('dialog') || 
+                                   methodName.toLowerCase().includes('modal') ||
+                                   methodName.toLowerCase().includes('show') ||
+                                   methodName.toLowerCase().includes('open');
+              
+              // 只允许弹窗相关的方法执行
+              if (isDialogMethod) {
+                return originalMethods[methodName].apply(this, args);
+              }
+              
+              // 阻止其他方法的执行
+              return;
+            };
+          });
+          
+          // 使用包装后的方法
+          parseStrToFunc.methods = wrappedMethods;
+          
+          const Component = Vue.extend(parseStrToFunc);
+          this.component = new Component().$mount();
+          this.$refs.display.appendChild(this.component.$el);
+
+          // 监听所有按钮点击事件
+          const observer = new MutationObserver((mutations) => {
+            mutations.forEach((mutation) => {
+              if (mutation.type === 'childList') {
+                this.bindButtonEvents();
+              }
+            });
+          });
+
+          const config = { childList: true, subtree: true };
+          observer.observe(this.component.$el, config);
+          this.observer = observer;
+
+          // 初始绑定按钮事件
+          this.bindButtonEvents();
+
+          if (this.css !== '') {
+            const style = document.createElement('style');
+            style.type = 'text/css';
+            style.id = this.id;
+            style.innerHTML = this.css;
+            document.getElementsByTagName('head')[0].appendChild(style);
+          }
+        }
+      },
+
+      // 绑定按钮点击事件的方法
+      bindButtonEvents() {
+        // 获取所有按钮,包括 Element UI 的按钮
+        const buttons = this.component.$el.querySelectorAll('button, .el-button');
+        console.log('找到的按钮:', buttons);
+        
+        buttons.forEach(button => {
+          // 移除可能存在的旧事件监听器
+          button.removeEventListener('click', this.handleButtonClick);
+          // 添加新的事件监听器
+          button.addEventListener('click', this.handleButtonClick);
+          
+          // 为 Element UI 按钮添加标记
+          if (button.classList.contains('el-button')) {
+            button.setAttribute('data-element-ui', 'true');
+          }
+        });
+      },
+
+      // 按钮点击事件处理函数
+      handleButtonClick(event) {
+        const button = event.target;
+        // 获取按钮的文本内容
+        const buttonText = button.textContent.trim();
+        // 发送事件到父组件,只传递按钮文本
+        this.$emit('button-click', {
+          text: buttonText
+        });
+      },
+
+      destroyCode () {
+          const $target = document.getElementById(this.id);
+          if ($target) $target.parentNode.removeChild($target);
+    
+          if (this.component) {
+            // 停止观察 DOM 变化
+            if (this.observer) {
+              this.observer.disconnect();
+            }
+
+            // 移除所有按钮点击事件监听
+            const buttons = this.component.$el.querySelectorAll('button');
+            buttons.forEach(button => {
+              button.removeEventListener('click', this.handleButtonClick);
+            });
+
+            this.$refs.display.removeChild(this.component.$el);
+            this.component.$destroy();
+            this.component = null;
+          }
+      },
+    },
+    beforeDestroy () {
+      this.destroyCode();
+    }
+  }
+ 
+  </script>

+ 82 - 0
zkqy-ui/src/components/BusinessFlow/TableTest.vue

@@ -0,0 +1,82 @@
+<template>
+  <div class="table-test">
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      style="width: 100%">
+      <el-table-column
+        prop="id"
+        label="ID"
+        width="180">
+      </el-table-column>
+      <el-table-column
+        prop="name"
+        label="名称"
+        width="180">
+      </el-table-column>
+      <el-table-column
+        prop="status"
+        label="状态">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
+            {{ statusOptions.find(opt => opt.value === scope.row.status)?.label || '未知' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'TableTest',
+  data() {
+    return {
+      statusOptions: [
+        { value: '0', label: '正常' },
+        { value: '1', label: '停用' }
+      ],
+      tableData: [],
+      loading: false
+    }
+  },
+  props: {
+    data: {
+      type: [Array, String],
+      default: () => []
+    }
+  },
+  watch: {
+    data: {
+      immediate: true,
+      handler(newVal) {
+        if (typeof newVal === 'string') {
+          try {
+            this.tableData = JSON.parse(newVal)
+          } catch (e) {
+            console.error('数据解析错误:', e)
+            this.tableData = []
+          }
+        } else {
+          this.tableData = Array.isArray(newVal) ? newVal : []
+        }
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      if (this.$el && typeof this.$el.querySelectorAll === 'function') {
+        // 在这里进行 DOM 操作
+      }
+    })
+  }
+  // ... existing code ...
+}
+</script>
+
+<style scoped>
+.table-test {
+  padding: 20px;
+}
+</style> 

+ 16 - 1
zkqy-ui/src/lang/en.js

@@ -122,7 +122,8 @@ export default {
     isConfirmDelete: 'Are you sure to delete process definition number',
     pleaseSelectData: 'Please select process data items!',
     confirm: 'Confirm',
-    cancel: 'Cancel'
+    cancel: 'Cancel',
+    modify: 'Modify'
   },
   dataModeling: {
     tableName: 'TableName',
@@ -1051,6 +1052,20 @@ export default {
     addBtnGroupName:'Create a new button group',
     addBtnName: "Create a new button group",
     businessScript:"Business Script",
+    query: 'Query',
+    insert: 'Insert',
+    update: 'Update',
+    delete: 'Delete',
+    otherMethod: 'Other',
+    businessScriptPage: "open Business Script Page",
+    otherMethod: 'Other Method',
+    bindBusinessFlowScriptType:"Script Type",
+    selectBusinessFlowScriptType:"Select Script Type",
+    selectBusinessFlowScript:"Select Script",
+    bindBusinessFlowVuePage:"Bind Vue Page",
+    selectBusinessFlowVuePage:"Select Vue Page",
+    query:"query",
+    valid:"valid",
   },
   formGroup: {
     title: 'Form Group Management',

+ 16 - 0
zkqy-ui/src/lang/zh.js

@@ -123,6 +123,7 @@ export default {
     pleaseSelectData: '请勾选流程数据条!',
     confirm: '确 定',
     cancel: '取 消',
+    modify: '修改',
   },
   dataModeling: {
     tableName: '表名称',
@@ -1052,6 +1053,21 @@ export default {
     export: '导出',
     addBtnGroupName:'新建按钮组',
     addBtnName: "新建按钮组",
+    query: '查询',
+    insert: '新增',
+    update: '修改',
+    delete: '删除',
+    otherMethod: '其他',
+    bindBusinessFlowScriptType:"脚本类型",
+    selectBusinessFlowScriptType:"请选择脚本类型",
+    selectBusinessFlowScript:"请选择业务脚本",
+    bindBusinessFlowVuePage:"绑定页面",
+    selectBusinessFlowVuePage:"请选择绑定页面",
+    otherMethod: '其它方法',
+    businessScript:"执行业务脚本",
+    businessScriptPage:"打开业务脚本页面",
+    query:"查询",
+    valid:"校验",
   },
   formGroup: {
     title: '表单组管理',

+ 1 - 1
zkqy-ui/src/main.js

@@ -2,7 +2,7 @@ import Vue from 'vue'
 import { useAntd } from 'k-form-design/packages/core/useComponents'
 import KFormDesign from 'k-form-design/packages/use.js'
 import 'k-form-design/lib/k-form-design.css'
-
+// import Vue from 'vue/dist/vue.js'
 // require('k-form-design/lib/k-form-design.css')
 import Cookies from 'js-cookie'
 import ELEMENT from 'element-ui';

+ 4 - 0
zkqy-ui/src/utils/request.js

@@ -109,9 +109,13 @@ service.interceptors.response.use(res => {
     } else if (code === 500) {
       // Message({message: msg, type: 'error'})
       let resUrl = res.config.url;
+      console.log(resUrl)
       if (resUrl == '/login') {
         Message({message: msg, type: 'error'})
         return Promise.reject(new Error(msg))
+      }  if (resUrl == '/system/SysBusinessFlowVuePage/uploadPage') {
+        Message({message: msg, type: 'error'})
+        return Promise.resolve()
       } else {
         return Promise.resolve()
       }

+ 376 - 0
zkqy-ui/src/utils/templateParser.js

@@ -0,0 +1,376 @@
+// utils/templateParser.js
+export function parseTemplate(template) {
+    // 移除注释
+    template = template.replace(/<!--[\s\S]*?-->/g, '');
+    
+    // 使用更健壮的正则表达式匹配按钮元素
+    const buttonRegex = /<button[^>]*>([\s\S]*?)<\/button>/g
+    const inputRegex = /<input[^>]*>/g
+    const selectRegex = /<select[^>]*>([\s\S]*?)<\/select>/g
+    
+    let modifiedTemplate = template
+    
+    // 处理按钮
+    modifiedTemplate = modifiedTemplate.replace(buttonRegex, (match, content) => {
+      // 提取按钮的原始属性
+      const buttonAttrs = match.match(/<button([^>]*)>/)[1]
+      // 检查是否已经有@click事件
+      if (match.includes('@click')) {
+        // 如果已经有@click事件,替换为我们的处理方法
+        const clickHandler = match.match(/@click="([^"]+)"/)[1]
+        return `<button${buttonAttrs} @click="handleButtonClick($event, '${content.trim()}', '${clickHandler}')" data-original-handler="${clickHandler}">${content}</button>`
+      } else {
+        // 如果没有@click事件,添加我们的处理方法
+        return `<button${buttonAttrs} @click="handleButtonClick($event, '${content.trim()}')">${content}</button>`
+      }
+    })
+    
+    // 处理input
+    modifiedTemplate = modifiedTemplate.replace(inputRegex, (match) => {
+      if (!match.includes('@input')) {
+        const inputAttrs = match.match(/<input([^>]*)>/)[1]
+        return `<input${inputAttrs} @input="handleInputChange($event)">`
+      }
+      return match
+    })
+    
+    // 处理select
+    modifiedTemplate = modifiedTemplate.replace(selectRegex, (match, content) => {
+      if (!match.includes('@change')) {
+        const selectAttrs = match.match(/<select([^>]*)>/)[1]
+        return `<select${selectAttrs} @change="handleSelectChange($event)">${content}</select>`
+      }
+      return match
+    })
+    
+    return modifiedTemplate
+  }
+// 从指定的 a-zA-Z0-9 中随机生成 32 位的字符串
+export  function randomStr(len = 32) {
+  const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+  const maxPos = $chars.length;
+  let str = '';
+  for (let i = 0; i < len; i++) {
+    str += $chars.charAt(Math.floor(Math.random() * maxPos));
+  }
+  return str;
+}
+// 验证组件名称是否符合规范
+function validateComponentName(name) {
+  // HTML5 规范要求:
+  // 1. 必须以字母开头
+  // 2. 只能包含字母、数字、连字符(-)和下划线(_)
+  // 3. 不能包含空格
+  const validNameRegex = /^[a-zA-Z][a-zA-Z0-9-_]*$/;
+  if (!validNameRegex.test(name)) {
+    // 如果名称不符合规范,生成一个有效的名称
+    return 'Component' + Math.random().toString(36).substr(2, 9);
+  }
+  return name;
+}
+
+// 字符串解析成vue组件
+export function parseComponent(content) {
+  // 提取template、script、style三个模块
+  const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
+  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
+  const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/);
+
+  // 初始化组件选项对象
+  const componentOptions = {};
+
+  // 从模板中提取所有使用的数据属性
+  const dataProperties = new Set();
+  const objectProperties = new Set();
+  const methodProperties = new Set();
+
+  // 解析template
+  if (templateMatch) {
+    let template = templateMatch[1].trim();
+    
+    // 收集所有可能的数据属性和方法
+    const collectProperties = (str) => {
+      const vModelRegex = /v-model="([^"]+)"/g;
+      const vBindRegex = /:([^=]+)="([^"]+)"/g;
+      const vShowRegex = /v-show="([^"]+)"/g;
+      const vIfRegex = /v-if="([^"]+)"/g;
+      const vForRegex = /v-for="([^"]+)"/g;
+      const interpolationRegex = /{{([^}]+)}}/g;
+      const eventRegex = /@([^=]+)="([^"]+)"/g;
+
+      let match;
+      while ((match = vModelRegex.exec(str)) !== null) {
+        const prop = match[1].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = vBindRegex.exec(str)) !== null) {
+        const prop = match[2].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = vShowRegex.exec(str)) !== null) {
+        const prop = match[1].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = vIfRegex.exec(str)) !== null) {
+        const prop = match[1].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = vForRegex.exec(str)) !== null) {
+        const prop = match[1].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = interpolationRegex.exec(str)) !== null) {
+        const prop = match[1].trim();
+        dataProperties.add(prop);
+        if (prop.includes('.')) {
+          objectProperties.add(prop);
+        }
+      }
+      while ((match = eventRegex.exec(str)) !== null) {
+        const method = match[2].trim();
+        if (!method.includes('(')) {
+          methodProperties.add(method);
+        } else {
+          const methodName = method.split('(')[0].trim();
+          methodProperties.add(methodName);
+        }
+      }
+    };
+
+    collectProperties(template);
+
+    // 初始化数据对象
+    const dataObj = {};
+    dataProperties.forEach(prop => {
+      if (!prop.includes('.')) {
+        // 根据属性名推断初始值类型
+        if (prop.endsWith('List') || prop.endsWith('Array')) {
+          dataObj[prop] = [];
+        } else if (prop.endsWith('Options')) {
+          dataObj[prop] = [];
+        } else if (prop.endsWith('Map') || prop.endsWith('Dict')) {
+          dataObj[prop] = {};
+        } else if (prop.endsWith('Flag') || prop.endsWith('Visible') || prop.endsWith('Modal')) {
+          dataObj[prop] = false;
+        } else if (prop.endsWith('Count') || prop.endsWith('Total')) {
+          dataObj[prop] = 0;
+        } else {
+          dataObj[prop] = null;
+        }
+      }
+    });
+
+    // 初始化所有对象属性
+    objectProperties.forEach(prop => {
+      const parts = prop.split('.');
+      let current = dataObj;
+      
+      for (let i = 0; i < parts.length - 1; i++) {
+        const part = parts[i];
+        if (!current[part]) {
+          current[part] = {};
+        }
+        current = current[part];
+      }
+      
+      const lastPart = parts[parts.length - 1];
+      if (current[lastPart] === undefined) {
+        current[lastPart] = null;
+      }
+    });
+
+    // 设置默认的 data 函数
+    componentOptions.data = function() {
+      return dataObj;
+    };
+
+    // 设置默认的 methods
+    componentOptions.methods = {};
+    methodProperties.forEach(method => {
+      if (!componentOptions.methods[method]) {
+        componentOptions.methods[method] = function() {
+          console.log(`Method ${method} called`);
+        };
+      }
+    });
+
+    componentOptions.template = `<div class="app-container">${template}</div>`;
+  }
+
+  // 解析script
+  if (scriptMatch) {
+    const scriptContent = scriptMatch[1].trim();
+    try {
+      if (scriptContent.includes('setup')) {
+        Object.assign(componentOptions, {
+          setup: true,
+          content: scriptContent
+        });
+      } else {
+        const exportDefaultMatch = scriptContent.match(/export\s+default\s*{([\s\S]*)}/);
+        if (exportDefaultMatch) {
+          const scriptOptions = exportDefaultMatch[1];
+          
+          // 解析 name
+          const nameMatch = scriptOptions.match(/name\s*:\s*['"]([^'"]+)['"]/);
+          if (nameMatch) {
+            componentOptions.name = validateComponentName(nameMatch[1]);
+          } else {
+            componentOptions.name = 'Component' + Math.random().toString(36).substr(2, 9);
+          }
+
+          // 解析 data
+          const dataMatch = scriptOptions.match(/data\s*\(\s*\)\s*{\s*return\s*{([^}]+)}/);
+          if (dataMatch) {
+            const dataStr = dataMatch[1];
+            const dataMatches = dataStr.matchAll(/(\w+)\s*:\s*([^,]+)/g);
+            const scriptDataObj = {};
+            
+            for (const match of dataMatches) {
+              try {
+                scriptDataObj[match[1]] = eval(match[2].trim());
+              } catch (e) {
+                console.warn('解析data属性失败:', e);
+                scriptDataObj[match[1]] = match[2].trim();
+              }
+            }
+
+            // 合并模板中收集的数据和脚本中定义的数据
+            const mergedData = Object.assign({}, dataObj, scriptDataObj);
+            componentOptions.data = function() {
+              return mergedData;
+            };
+          }
+
+          // 解析 methods
+          const methodsMatch = scriptOptions.match(/methods\s*:\s*{([^}]+)}/);
+          if (methodsMatch) {
+            componentOptions.methods = componentOptions.methods || {};
+            const methodsStr = methodsMatch[1];
+            const methodMatches = methodsStr.matchAll(/(\w+)\s*:\s*function\s*\([^)]*\)\s*{([^}]+)}/g);
+            for (const match of methodMatches) {
+              const methodName = match[1];
+              const methodBody = match[2].trim();
+              try {
+                componentOptions.methods[methodName] = new Function(methodBody);
+              } catch (e) {
+                console.warn(`解析方法 ${methodName} 失败:`, e);
+                componentOptions.methods[methodName] = function() {
+                  console.log(`Method ${methodName} called`);
+                };
+              }
+            }
+          }
+
+          // 解析 computed
+          const computedMatch = scriptOptions.match(/computed\s*:\s*{([^}]+)}/);
+          if (computedMatch) {
+            componentOptions.computed = {};
+            const computedStr = computedMatch[1];
+            const computedMatches = computedStr.matchAll(/(\w+)\s*\(\s*\)\s*{\s*return\s*([^}]+)}/g);
+            for (const match of computedMatches) {
+              try {
+                componentOptions.computed[match[1]] = match[2].trim();
+              } catch (e) {
+                console.warn(`解析计算属性 ${match[1]} 失败:`, e);
+              }
+            }
+          }
+
+          // 解析 watch
+          const watchMatch = scriptOptions.match(/watch\s*:\s*{([^}]+)}/);
+          if (watchMatch) {
+            componentOptions.watch = {};
+            const watchStr = watchMatch[1];
+            const watchMatches = watchStr.matchAll(/(\w+)\s*:\s*(?:function\s*\([^)]*\)\s*{([^}]+)}|{([^}]+)})/g);
+            for (const match of watchMatches) {
+              const watchKey = match[1];
+              const watchValue = match[2] || match[3];
+              if (watchValue) {
+                try {
+                  if (watchValue.includes('handler')) {
+                    const handlerMatch = watchValue.match(/handler\s*:\s*function\s*\([^)]*\)\s*{([^}]+)}/);
+                    if (handlerMatch) {
+                      componentOptions.watch[watchKey] = {
+                        handler: handlerMatch[1].trim(),
+                        immediate: watchValue.includes('immediate: true'),
+                        deep: watchValue.includes('deep: true')
+                      };
+                    }
+                  } else {
+                    componentOptions.watch[watchKey] = watchValue.trim();
+                  }
+                } catch (e) {
+                  console.warn(`解析watch ${watchKey} 失败:`, e);
+                }
+              }
+            }
+          }
+        }
+      }
+    } catch (e) {
+      console.error('解析script失败:', e);
+      componentOptions.name = 'Component' + Math.random().toString(36).substr(2, 9);
+    }
+  } else {
+    componentOptions.name = 'Component' + Math.random().toString(36).substr(2, 9);
+  }
+
+  // 解析style
+  if (styleMatch) {
+    componentOptions._style = {
+      content: styleMatch[1].trim(),
+      attrs: (styleMatch[0].match(/<style([^>]*)>/) || [])[1] || ''
+    };
+  }
+
+  // 在返回之前添加按钮处理方法
+  return addButtonHandler(componentOptions);
+}
+
+// 添加处理按钮点击的方法
+export function addButtonHandler(componentOptions) {
+  if (!componentOptions.methods) {
+    componentOptions.methods = {};
+  }
+
+  // 添加按钮点击处理方法
+  componentOptions.methods.handleButtonClick = function(event, buttonText, originalHandler) {
+    // 发送事件到父组件
+    this.$emit('button-click', {
+      text: buttonText,
+      originalHandler: originalHandler,
+      event: event
+    });
+
+    // 如果是弹窗相关的方法,则执行原始方法
+    if (originalHandler && (
+      originalHandler.includes('Modal') || 
+      originalHandler.includes('Dialog') || 
+      originalHandler.includes('show') || 
+      originalHandler.includes('hide')
+    )) {
+      // 执行原始方法
+      if (typeof this[originalHandler] === 'function') {
+        this[originalHandler]();
+      }
+    }
+  };
+
+  return componentOptions;
+}

+ 0 - 6
zkqy-ui/src/views/bpmprocess/scriptManage.vue

@@ -606,12 +606,6 @@ export default {
         scriptName: [
           { required: true, message: this.$t('script.pleaseEnterScriptName'), trigger: 'blur' }
         ],
-        // scriptFunctionName: [
-        //   { required: true, message: "请输入脚本函数名", trigger: "blur" },
-        // ],
-        // scriptFunctionCode: [
-        //   { required: true, message: "请输入脚本函数体", trigger: "blur" },
-        // ],
         scriptType: [
           { required: true, message: this.$t('script.pleaseSelectScriptType'), trigger: 'change' }
         ],

+ 289 - 0
zkqy-ui/src/views/businessFlow/BindingPageScript/index.vue

@@ -0,0 +1,289 @@
+<template>
+    <div class="app-container">
+    <div class="display-module">
+      <dynamic-component-wrapper
+        v-if="vueCode"
+        :code="vueCode"
+        @button-click="handleButtonClick"
+      />
+    </div>
+    <div class="operation-module">
+      <div class="current-button">
+        当前选择按钮: {{ currentButtonName || '未选择按钮 或 按钮不支持绑定脚本'}}
+      </div>
+      <el-form :model="formData" :rules="rules" ref="formRef" label-width="100px">
+        <el-form-item label="绑定脚本" prop="select1">
+          <el-select v-model="formData.select1" placeholder="请选择">
+            <el-option
+              v-for="item in options1"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="脚本类型" prop="select2">
+          <el-select v-model="formData.select2" placeholder="请选择">
+            <el-option
+              v-for="item in options2" 
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="onSubmit">提交</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+  </div>
+  </template>
+  
+  <script>
+  import {  getVuePage, listBusinessFlowScript, queryBtnScriptApi, addBtnScriptApi, updateBtnScriptApi } from "@/api/businessFlow/vuePage";
+  import DynamicComponentWrapper from '@/components/BusinessFlow/DynamicComponentWrapper.vue'
+  import { parseComponent } from '@/utils/templateParser'
+  
+  export default {
+    name: 'BindingPageScript',
+    components: {
+        DynamicComponentWrapper
+    },
+    data () {
+      return {
+        // 跳转页面-路由传递的row中的Id的值
+        rowId: null,
+        // 获取到的所有数据
+        vuePageData: {},
+        //获取到的vueCode, vue代码值
+        vueCode: '',
+        //获取到的tenantId值
+        tenantId: null,
+        // 获取到的vueKey值--uuid
+        vueKey:'',
+        loading:false,
+        formData:{
+          select1:'',
+          select2:''
+        },
+        // 脚本下拉
+        options1:[],
+        // 脚本类型
+        options2:[
+          {
+            value: "query",
+            label: this.$t('excuteBtn.query'),
+          },
+          {
+            value: "add",
+            label: this.$t('excuteBtn.insert'),
+          },
+          {
+            value: "update",
+            label: this.$t('excuteBtn.update'),
+          },
+          {
+            value: "delete",
+            label: this.$t('excuteBtn.delete'),
+          },
+          {
+            value: "other",
+            label: this.$t('excuteBtn.otherMethod'),
+          },
+        ],
+        btnScriptFlag:false,//按钮是否绑定脚本
+        currentButtonName:'',//当前选择按钮名字
+        // 添加防抖标记
+        isQuerying: false,
+        queryTimer: null,
+        // 添加表单验证规则
+        rules: {
+          select1: [
+            { required: true, message: '请选择绑定脚本', trigger: 'change' }
+          ],
+          select2: [
+            { required: true, message: '请选择脚本类型', trigger: 'change' }
+          ]
+        },
+      }
+    },
+    created () {
+      // 获取路由传递的参数
+      const rowId = this.$route.query.mode
+      console.log('路由传递的rowId:', rowId)
+      if(rowId){
+        this.rowId = rowId
+        this.getList()
+        this.getScriptList()
+      }
+    },
+    methods: {
+      getList() {
+        this.loading = true;
+        let id = this.rowId
+        console.log('this.rowId:', this.rowId)
+        getVuePage(id).then(response => {
+          if(response.code == 200){
+            this.vuePageData = response.data;
+            const vueCodeStr = decodeURIComponent(response.data?.vueCode)
+            this.vueCode = vueCodeStr 
+            this.vueKey = response.data?.vueKey 
+            this.tenantId = response.data?.tenantId
+            console.log('[this.vuePageData]',  this.vuePageData)
+            console.log('[this.vueCode]',  this.vueCode)
+            console.log('[this.tenantId]',  this.tenantId)
+          }else{
+            this.vuePageData = {}
+          }
+          this.loading = false;
+        });
+      },
+      getScriptList(){
+        listBusinessFlowScript({ isEnablePaging: false }).then(response => {
+          console.log('[response]',  response)
+          if(response.code == 200){
+            this.options1 = response.rows.map(row=>{
+              return{
+                ...row,
+                label: row.remark,
+                value: row.businessKey
+              }
+            })
+          }
+        })
+      },
+      handleButtonClick(buttonData) {
+        console.log('按钮点击事件:', buttonData);
+        this.currentButtonName = buttonData.text
+        let data = {
+          vueKey: this.vueKey,
+          vueContent: buttonData.text,  // 使用按钮的文本内容
+        }
+        // 使用防抖处理查询请求
+        this.debounceQueryBtnScript(data);
+      },
+      // 添加防抖方法
+      debounceQueryBtnScript(data) {
+        if (this.isQuerying) {
+          return;
+        }
+        
+        if (this.queryTimer) {
+          clearTimeout(this.queryTimer);
+        }
+
+        this.queryTimer = setTimeout(() => {
+          this.isQuerying = true;
+          this.queryBtnScript(data).finally(() => {
+            this.isQuerying = false;
+          });
+        }, 300);
+      },
+      // 修改查询方法返回 Promise
+      queryBtnScript(data){
+        console.log('[data]', data)
+        return queryBtnScriptApi(data).then(response => {
+          console.log('[response]',  response)
+          if(response.code ===200 ){
+            if(response.msg == '未绑定'){
+                this.btnScriptFlag = false
+                this.formData = {
+                  select1:'',
+                  select2:''
+                }
+            }else{
+              console.log('[response.data]',  response.data)
+              let data =  response.data
+              this.btnScriptFlag = true
+              this.formData = {
+                  select1:data.businessKey,
+                  select2:data.businessType
+                }
+            }
+          }
+        }).catch(error => {
+          console.error('查询按钮脚本失败:', error);
+        });
+      },
+      // 表单提交
+      onSubmit(){
+        this.$refs.formRef.validate((valid) => {
+          if (valid) {
+            console.log('[this.vueKey]',this.vueKey,)
+            console.log('[this.currentButtonName]',this.currentButtonName,)
+            console.log('[this.formData]',this.formData,)
+            let data = {
+              vueKey: this.vueKey,
+              vueContent: this.currentButtonName,
+              businessKey: this.formData.select1,
+              businessType: this.formData.select2
+            }
+            console.log('[data]',data)
+            addBtnScriptApi(data).then(response => {
+              console.log('[response]',  response)
+              if (response.code === 200) {
+                this.$message.success('绑定成功');
+                // 重置表单
+                // this.$refs.formRef.resetFields();
+              } else {
+                this.$message.error(response.msg || '绑定失败');
+              }
+            })
+          } else {
+            this.$message.warning('请填写必填项');
+            return false;
+          }
+        });
+      },
+    }
+  }
+  </script>
+  <style lang="scss">
+  // 全局样式,确保下拉框在最上层
+  .el-select-dropdown {
+    z-index: 99999 !important;
+  }
+  </style>
+  <style lang="scss" scoped>
+  .app-container {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: row;
+  }
+  .display-module {
+    flex: 70%;
+    position: relative;
+    z-index: 1;
+    // 添加样式隔离
+    :deep(.dynamic-component-container) {
+      height: 100%;
+      overflow: auto;
+      // 重置一些可能影响全局的样式
+      * {
+        box-sizing: border-box;
+      }
+      // 确保Element UI组件样式正确
+      .el-button,
+      .el-input,
+      .el-select,
+      .el-table {
+        font-family: inherit;
+      }
+    }
+  }
+  .operation-module {
+    flex: 30%;
+    position: relative;
+    z-index: 9999;
+    background: #fff;
+    padding: 20px;
+    box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
+    :deep(.el-select) {
+      width: 100%;
+    }
+  }
+  </style>
+  

+ 504 - 0
zkqy-ui/src/views/businessFlow/script/index.vue

@@ -0,0 +1,504 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索模块 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <!-- vue 的页面标识(businessKey) 类似管道节点的脚本编码-->
+      <el-form-item label="脚本编码" prop="businessKey">
+        <el-input
+          v-model="queryParams.businessKey"
+          placeholder="请输入脚本编码"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- 页面代码(methodName),用户上传的代码 -->
+      <el-form-item label="方法类名" prop="methodName">
+        <el-input
+          v-model="queryParams.methodName"
+          placeholder="请输入方法类名"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="脚本名称" prop="remark">
+        <el-input
+          v-model="queryParams.remark"
+          placeholder="请输入脚本名称"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- 租户名称(tenantId), 下拉框--当前租户id, 符合的话可以编辑, 不然只能查看 -->
+      <el-form-item label="租户id" prop="tenantId">
+        <el-select
+          v-model="queryParams.tenantId"
+          placeholder="请选择租户"
+          filterable
+          clearable
+        >
+          <el-option
+            v-for="item in TenantAllList"
+            :key="item.tenantId"
+            :label="item.tenantName"
+            :value="item.tenantId"
+          >
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 按钮模块 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['system:vuePage:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['system:vuePage:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['system:vuePage:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="downloadJavaTemplate"
+        >下载Java模板</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    <!-- 表格模块 -->
+    <el-table v-loading="loading" :data="scriptList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="脚本编码" align="center" prop="businessKey" :show-overflow-tooltip="true"  />
+      <el-table-column label="方法类名" align="center" prop="methodName" :show-overflow-tooltip="true" />
+      <el-table-column label="脚本名称" align="center" prop="remark" :show-overflow-tooltip="true" />
+<!--      <el-table-column label="是否上传" align="center" width="180">-->
+<!--        <template #default="{row}">-->
+<!--          <el-tag :type="row.path ? 'primary' : 'danger'">-->
+<!--            {{ row.path ? '已上传' : '未上传' }}-->
+<!--          </el-tag>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template
+          slot-scope="scope"
+          v-if=" $store.state.user.tenant.tenantId == scope.row.tenantId"
+        >
+          <el-dropdown>
+            <el-button type="warning" plain size="small">
+              操作<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-download"
+                  @click="downloadJavaCode(scope.row)"
+                >下载代码</el-button>
+              </el-dropdown-item>
+              <el-dropdown-item>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-edit"
+                  @click="handleUpdate(scope.row)"
+                >修改</el-button>
+              </el-dropdown-item>
+              <el-dropdown-item>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-delete"
+                  @click="handleDelete(scope.row)"
+                >删除</el-button>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页模块 -->
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+    <!-- 添加或修改vue页面数据弹框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="脚本编码" prop="businessKey">
+          <el-col :span="20">
+            <el-input
+              v-model="form.businessKey"
+              placeholder="请输入脚本编码"
+              :disabled="true"
+            />
+          </el-col>
+          <el-col :span="4">
+            <!-- 复制 -->
+            <el-button
+              icon="el-icon-document-copy"
+              v-clipboard:copy="form.businessKey"
+              v-clipboard:success="onCopy"
+              v-clipboard:error="onError"
+            ></el-button>
+          </el-col>
+        </el-form-item>
+        <el-form-item label="方法类名" prop="methodName">
+          <el-input v-model="form.methodName" placeholder="上传文件后获取方法类名" disabled />
+        </el-form-item>
+        <el-form-item label="脚本名称" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入脚本名称" />
+        </el-form-item>
+        <!-- 页面代码 -->
+        <el-form-item label="上传脚本" prop="path">
+          <el-upload
+            action="#"
+            :before-upload="beforeUpload"
+            :show-file-list="true"
+            accept=".java"
+            :http-request="customUpload"
+            :limit="1"
+            :file-list="fileList"
+          >
+            <el-button type="primary" size="small">上传文件</el-button>
+            <span slot="tip" class="el-upload__tip" style="margin-left: 10px;">
+              只能上传 java文件
+            </span>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确认</el-button>
+        <el-button @click="cancel">取消</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+  addSysBusinessFlowScript, delSysBusinessFlowScript, downloadCodeScript, downloadTemplateScript,
+  getSysBusinessFlowScript,
+  listSysBusinessFlowScript, updateSysBusinessFlowScript,
+  uploadScript
+} from '@/api/businessFlow/flowScript'
+import { v4 as uuidv4 } from 'uuid'
+import { getTenantAllList } from '@/api/system/tenant'
+import { mapState } from "vuex";
+export default {
+  name: "vuePage",
+  data() {
+    return {
+      // 租户下拉框
+      TenantAllList: [],
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 选中数组(包含所有数据)
+      selection: [],
+      // 多选框勾选数据
+      tempSelection: [],
+      fileList: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 存放业务脚本的vue页面表格数据
+      scriptList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        businessKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+        tenantId: null, //租户id, 下拉
+        remark: null //页面名称
+      },
+      // 表单参数
+      form: {
+        businessKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+        tenantId: null, //租户id, 下拉
+        remark: null //页面名称
+      },
+      // 表单校验
+      rules: {
+        remark: [
+          { required: true, message: "请输入页面名称", trigger: 'change' }
+        ],
+      },
+    };
+  },
+  computed: {
+    ...mapState({
+      databaseName: (state) => state.user.dataSource.databaseName,
+      databaseType: (state) => state.user.dataSource.databaseType,
+      username: (state) => state.user.dataSource.username,
+      tenantId: (state) => state.user.tenant.tenantId,
+    }),
+  },
+
+  created() {
+    this.getList();
+    this.getTenantAllList()
+
+  },
+  methods: {
+    // 复制成功
+    onCopy () {
+      this.$modal.msgSuccess("复制成功")
+    },
+    //复制失败
+    onError () {
+      this.$modal.msgError("复制失败")
+    },
+    /** 查询存放业务脚本的vue页面列表 */
+    getList() {
+      this.loading = true;
+      this.tempSelection = JSON.parse(JSON.stringify(this.selection));
+      listSysBusinessFlowScript(this.queryParams).then(response => {
+        this.scriptList = response.rows
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    getTenantAllList () {
+      getTenantAllList().then(response => {
+        this.TenantAllList = response.rows
+      })
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.fileList = [];
+      this.form = {
+        id: null,
+        methodName:null,
+        path:null,
+        businessKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+        tenantId: null, //租户id,vuex中拿到
+        remark: null //页面名称
+      };
+      // 修改这里 ↓
+      if (this.$refs['form']) {
+        this.$refs['form'].resetFields();
+      }
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      // this.resetForm("queryForm");
+      // this.handleQuery();
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        methodName: null,
+        businessKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+        tenantId: null, //租户id, 下拉
+        remark: null //页面名称
+      };
+      this.$nextTick(() => {
+        if (this.$refs.queryForm) {
+          this.$refs.queryForm?.clearValidate();
+        }
+      });
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.selection = selection;
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      //this.monacoEditor?.dispose()
+      this.reset();
+      this.$nextTick(() => {
+        const newBusinessKey = uuidv4().replace(/-/g, '');
+        this.$set(this.form, 'businessKey', 'businessKey_' + newBusinessKey)
+        this.open = true
+        this.title = "添加业务脚本页面";
+      });
+      this.open = true;
+      this.title = "添加业务脚本页面";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      // console.log('id',id)
+      getSysBusinessFlowScript(id).then(response => {
+        if(response.code == 200){
+          this.form = response.data;
+          this.open = true;
+          this.title = "修改业务脚本页面";
+        }else{
+          this.$modal.msgError("获取单条数据失败")
+        }
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.form.tenantId = this.tenantId
+      this.$refs["form"].validate(valid => {
+        console.log(valid)
+        if (valid) {
+          if (this.form.id != null) {
+            const dataToSubmit = { ...this.form }; // 使用扩展运算符浅拷贝 form 对象
+            updateSysBusinessFlowScript(dataToSubmit).then(response => {
+              if(response.code == 200){
+                console.log('response',response)
+                this.$modal.msgSuccess("修改成功");
+                this.open = false;
+              }else {
+                this.$modal.msgSuccess("修改失败");
+              }
+              this.getList();
+            });
+          } else {
+            const dataToSubmit = { ...this.form }; // 使用扩展运算符浅拷贝 form 对象
+            delete dataToSubmit.id; // 删除 id
+            addSysBusinessFlowScript(dataToSubmit).then(response => {
+              console.log('response',response)
+              if(response.code == 200){
+                this.$modal.msgSuccess("新增成功");
+                this.open = false;
+              }else {
+                this.$modal.msgSuccess("新增失败");
+              }
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$modal.confirm('是否确认删除业务脚本页面编号为"' + row.businessKey + '"的数据项?').then(function() {
+        return delSysBusinessFlowScript(ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    downloadJavaTemplate(){
+      downloadTemplateScript().then(response => {
+        if (response instanceof Blob) {
+          // 创建一个blob链接
+          const url = window.URL.createObjectURL(new Blob([response]));
+          const link = document.createElement('a');
+          link.href = url;
+          link.setAttribute('download', 'template.java');
+          document.body.appendChild(link);
+          link.click();
+          document.body.removeChild(link);
+        }
+      });
+    },
+    downloadJavaCode(row){
+      const queryParams = {
+        businessKey: row.businessKey,
+      }
+      downloadCodeScript(queryParams).then(response => {
+        if (response instanceof Blob) {
+          // 创建一个blob链接
+          const url = window.URL.createObjectURL(new Blob([response]));
+          const link = document.createElement('a');
+          link.href = url;
+          link.setAttribute('download', row.businessKey + '.java');
+          document.body.appendChild(link);
+          link.click();
+          document.body.removeChild(link);
+        }
+      });
+    },
+    // 上传前校验
+    beforeUpload(file) {
+      const isJavaFile = file.name.endsWith('.java');
+      if (!isJavaFile) this.$message.error('只能上传 Java 文件!');
+      return isJavaFile;
+    },
+    //自定义上传接口
+    customUpload({ file }) {
+      const formData = new FormData();
+      formData.append('file', file);  // 根据后端接口字段名调整 key 名
+      formData.append('businessKey', this.form.businessKey);
+      this.fileList = [{ name: file.name }];
+      uploadScript(formData)
+        .then(response => {
+          this.handleUploadSuccess(response, file);
+        })
+    },
+    // 上传成功处理
+    handleUploadSuccess(response, file) {
+      if (response.code === 200) {
+        const data = response.data;
+        this.form.methodName = data;
+        this.$message.success('文件上传成功');
+      }
+    },
+  }
+};
+</script>
+<style lang="scss" scoped>
+.codeEditBox {
+  width: 100%;
+  height: 200px;
+}
+.monaco-container {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 533 - 0
zkqy-ui/src/views/businessFlow/vuePage/index.vue

@@ -0,0 +1,533 @@
+<template>
+    <div class="app-container">
+      <!-- 搜索模块 -->
+      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+        <!-- vue 的页面标识(vueKey) 类似管道节点的脚本编码-->
+        <el-form-item label="脚本编码" prop="vueKey">
+          <el-input
+            v-model="queryParams.vueKey"
+            placeholder="请输入脚本编码"
+            clearable
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <!-- 页面代码(vueCode),用户上传的代码 -->
+        <el-form-item label="页面代码" prop="vueCode">
+          <el-input
+            v-model="queryParams.vueCode"
+             placeholder="请输入页面代码"
+            clearable
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <!-- 页面名称(remark),用户自己取的名字 -->
+        <el-form-item label="页面名称" prop="remark">
+          <el-input
+            v-model="queryParams.remark"
+            placeholder="请输入创建者id"
+            clearable
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <!-- 租户名称(tenantId), 下拉框--当前租户id, 符合的话可以编辑, 不然只能查看 -->
+        <el-form-item label="租户id" prop="tenantId">
+          <el-select
+            v-model="queryParams.tenantId"
+            placeholder="请选择租户"
+            filterable
+            clearable
+          >
+            <el-option
+              v-for="item in TenantAllList"
+              :key="item.tenantId"
+              :label="item.tenantName"
+              :value="item.tenantId"
+            >
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <!-- 按钮模块 -->
+      <el-row :gutter="10" class="mb8">
+        <el-col :span="1.5">
+          <el-button
+            type="primary"
+            plain
+            icon="el-icon-plus"
+            size="mini"
+            @click="handleAdd"
+            v-hasPermi="['system:vuePage:add']"
+          >新增</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            style="display: none;"
+            type="success"
+            plain
+            icon="el-icon-edit"
+            size="mini"
+            :disabled="single"
+            @click="handleUpdate"
+            v-hasPermi="['system:vuePage:edit']"
+          >修改</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="danger"
+            plain
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="multiple"
+            @click="handleDelete"
+            v-hasPermi="['system:vuePage:remove']"
+          >删除</el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            type="danger"
+            plain
+            icon="el-icon-download"
+            size="mini"
+            @click="downloadVueTemplate"
+          >下载Vue模板</el-button>
+        </el-col>
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </el-row>
+      <!-- 表格模块 -->
+      <el-table v-loading="loading" :data="vuePageList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="脚本编码" align="center" prop="vueKey" :show-overflow-tooltip="true"  />
+        <el-table-column label="页面名称" align="center" prop="remark" :show-overflow-tooltip="true" />
+        <el-table-column label="页面代码" align="center" prop="vueCode" :show-overflow-tooltip="true" width="180" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template
+          slot-scope="scope"
+          v-if=" $store.state.user.tenant.tenantId == scope.row.tenantId "
+        >
+        <el-dropdown>
+            <el-button type="warning" plain size="small">
+              操作<i class="el-icon-arrow-down el-icon--right"></i>
+            </el-button>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item>
+                <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-edit"
+                  @click="handleUpdate(scope.row)"
+                  v-hasPermi="['system:VuePage:edit']"
+                >修改</el-button>
+              </el-dropdown-item>
+              <el-dropdown-item>
+              <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-delete"
+                  @click="handleDelete(scope.row)"
+                  v-hasPermi="['system:VuePage:remove']"
+                >删除</el-button>
+              </el-dropdown-item>
+              <el-dropdown-item>
+              <el-button
+                  size="mini"
+                  type="text"
+                  icon="el-icon-edit"
+                  @click="BindingPageScript(scope.row)"
+                  v-hasPermi="['system:VuePage:edit']"
+                >页面按钮绑定脚本</el-button>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 分页模块 -->
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+      <!-- 添加或修改vue页面数据弹框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="脚本编码" prop="vueKey">
+          <el-col :span="20">
+            <el-input
+              v-model="form.vueKey"
+              placeholder="请输入脚本编码"
+              :disabled="true"
+            />
+          </el-col>
+          <el-col :span="4">
+            <!-- 复制 -->
+            <el-button
+              icon="el-icon-document-copy"
+              v-clipboard:copy="form.vueKey"
+              v-clipboard:success="onCopy"
+              v-clipboard:error="onError"
+            ></el-button>
+          </el-col>
+        </el-form-item>
+        <el-form-item label="页面名称" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入页面名称" />
+        </el-form-item>
+      <!-- 页面代码 -->
+        <el-form-item label="页面代码" prop="vueCode">
+            <el-input
+            v-model="form.vueCode"
+            type="textarea"
+            :rows="10"
+            placeholder="请输入页面代码"
+            style="margin-bottom: 10px;"
+          />
+          <el-upload
+            action="#"
+            :before-upload="beforeUpload"
+            :on-success="handleUploadSuccess"
+            :show-file-list="false"
+            accept=".vue"
+            :http-request="customUpload"
+          >
+            <el-button type="primary" size="small">上传文件</el-button>
+            <span slot="tip" class="el-upload__tip" style="margin-left: 10px;">
+              只能上传 vue文件
+            </span>
+          </el-upload>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确认</el-button>
+        <el-button @click="cancel">取消</el-button>
+      </div>
+    </el-dialog>
+
+    </div>
+  </template>
+  
+  <script>
+  import { listVuePage, getVuePage, delVuePage, addVuePage, updateVuePage, uploadVuePage, downloadTemplateVue } from "@/api/businessFlow/vuePage";
+  import { v4 as uuidv4 } from 'uuid'
+  import { getTenantAllList } from '@/api/system/tenant'
+  import { mapState } from "vuex";
+  export default {
+    name: "vuePage",
+    data() {
+      return {
+        // 租户下拉框
+        TenantAllList: [],
+        // 遮罩层
+        loading: true,
+        // 选中数组
+        ids: [],
+        // 选中数组(包含所有数据)
+        selection: [],
+        // 多选框勾选数据
+        tempSelection: [],
+        // 非单个禁用
+        single: true,
+        // 非多个禁用
+        multiple: true,
+        // 显示搜索条件
+        showSearch: true,
+        // 总条数
+        total: 0,
+        // 存放业务脚本的vue页面表格数据
+        vuePageList: [],
+        // 弹出层标题
+        title: "",
+        // 是否显示弹出层
+        open: false,
+        // 上传相关状态
+        uploadLoading: false,
+        // 查询参数
+        queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          vueCode: null, //页面代码
+          vueKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+          tenantId: null, //租户id, 下拉
+          remark: null //页面名称
+        },
+        // 表单参数
+        form: {
+          vueCode: null, //页面代码
+          vueKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+          tenantId: null, //租户id, 下拉
+          remark: null //页面名称
+        },
+        // 表单校验
+      rules: {
+        vueCode: [
+          { required: true, message: "请输入页面代码", trigger: 'blur' }
+        ],
+        remark: [
+          { required: true, message: "请输入页面名称", trigger: 'change' }
+        ],
+      },
+      };
+    },
+    computed: {
+    ...mapState({
+      databaseName: (state) => state.user.dataSource.databaseName,
+      databaseType: (state) => state.user.dataSource.databaseType,
+      username: (state) => state.user.dataSource.username,
+      tenantId: (state) => state.user.tenant.tenantId,
+    }),
+    },
+    
+    created() {
+      this.getList();
+      this.getTenantAllList()
+
+    },
+    methods: {
+      // 复制成功
+      onCopy () {
+        this.$modal.msgSuccess("复制成功")
+      },
+      //复制失败
+      onError () {
+        this.$modal.msgError("复制失败")
+      },
+      /** 查询存放业务脚本的vue页面列表 */
+      getList() {
+        this.loading = true;
+        this.tempSelection = JSON.parse(JSON.stringify(this.selection));
+        listVuePage(this.queryParams).then(response => {
+          this.vuePageList = response.rows.map(row =>{
+            return {
+              ...row,
+              vueCode: decodeURIComponent(row?.vueCode)
+            }
+          });
+          this.total = response.total;
+          this.loading = false;
+        });
+      },
+      getTenantAllList () {
+      getTenantAllList().then(response => {
+        this.TenantAllList = response.rows
+      })
+    },
+      // 取消按钮
+      cancel() {
+        this.open = false;
+        this.reset();
+      },
+      // 表单重置
+      reset() {
+        this.form = {
+          id: null,
+          vueCode: null, //页面代码
+          vueKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+          tenantId: null, //租户id,vuex中拿到
+          remark: null //页面名称
+        };
+        // 修改这里 ↓
+        if (this.$refs['form']) {
+            this.$refs['form'].resetFields();
+        }
+      },
+      /** 搜索按钮操作 */
+      handleQuery() {
+        this.queryParams.pageNum = 1;
+        this.getList();
+      },
+      /** 重置按钮操作 */
+      resetQuery() {
+        // this.resetForm("queryForm");
+        // this.handleQuery();
+        this.queryParams = {
+            pageNum: 1,
+            pageSize: 10,
+            vueCode: null, //页面代码
+            vueKey: null, //脚本编码, 用来和页面id给按钮组合匹配的id
+            tenantId: null, //租户id, 下拉
+            remark: null //页面名称
+        };
+        this.$nextTick(() => {
+            if (this.$refs.queryForm) {
+            this.$refs.queryForm?.clearValidate();
+            }
+        });
+        this.handleQuery();
+      },
+      // 多选框选中数据
+      handleSelectionChange(selection) {
+        this.ids = selection.map(item => item.id)
+        this.selection = selection;
+        this.single = selection.length!==1
+        this.multiple = !selection.length
+      },
+      /** 新增按钮操作 */
+      handleAdd() {
+        //this.monacoEditor?.dispose()
+        this.reset();
+        this.$nextTick(() => {
+        const newVueKey = uuidv4()
+        this.$set(this.form, 'vueKey', 'vueKey_' + newVueKey)
+        this.open = true
+        this.title = "添加存放业务脚本的vue页面";
+      });
+        this.open = true;
+        this.title = "添加存放业务脚本的vue页面";
+      },
+      /** 修改按钮操作 */
+      handleUpdate(rowData) {
+        this.reset();
+        let row;
+        if (rowData instanceof PointerEvent || rowData instanceof Event) {
+            row = this.selection[0]
+        } else {
+            row = rowData
+        }
+        // console.log('row',rowData)
+        const id = row.id || this.ids
+        // console.log('id',id)
+        getVuePage(id).then(response => {
+            if(response.code == 200){
+                let vueCode = decodeURIComponent(response.data?.vueCode);
+                this.form = response.data;
+                this.form.vueCode = vueCode;
+                this.open = true;
+                this.title = "修改存放业务脚本的vue页面";
+            }else{
+                this.$modal.msgError("获取单条数据失败")
+            }
+        });
+      },
+      /** 提交按钮 */
+      submitForm() {
+        this.form.tenantId = this.tenantId
+        this.$refs["form"].validate(valid => {
+          if (valid) {
+            if (this.form.id != null) {
+              const dataToSubmit = { ...this.form }; // 使用扩展运算符浅拷贝 form 对象
+              if(dataToSubmit.vueCode) dataToSubmit.vueCode = encodeURIComponent(dataToSubmit.vueCode);
+              updateVuePage(dataToSubmit).then(response => {
+                if(response.code == 200){
+                    console.log('response',response)
+                    this.$modal.msgSuccess("修改成功");
+                    this.open = false;
+                }else {
+                    this.$modal.msgSuccess("修改失败");
+                }
+                this.getList();
+              });
+            } else {
+              const dataToSubmit = { ...this.form }; // 使用扩展运算符浅拷贝 form 对象
+              console.log('dataToSubmit',dataToSubmit.vueCode)
+              delete dataToSubmit.id; // 删除 id
+              // 对vueCode进行编码处理
+              if(dataToSubmit.vueCode) dataToSubmit.vueCode = encodeURIComponent(dataToSubmit.vueCode);
+              
+              addVuePage(dataToSubmit).then(response => {
+                console.log('response',response)
+                if(response.code == 200){
+                    this.$modal.msgSuccess("新增成功");
+                    this.open = false;
+                }else {
+                    this.$modal.msgSuccess("新增失败");
+                }
+                this.getList();
+              });
+            }
+          }
+        });
+      },
+      /** 删除按钮操作 */
+      handleDelete(row) {
+        const ids = row.id || this.ids;
+        this.$modal.confirm('是否确认删除存放业务脚本的vue页面编号为"' + ids + '"的数据项?').then(function() {
+          return delVuePage(ids);
+        }).then(() => {
+          this.getList();
+          this.$modal.msgSuccess("删除成功");
+        }).catch(() => {});
+      },
+      /** 页面按钮绑定脚本 */
+      BindingPageScript(row){
+        console.log('row',row)
+        if(row.tenantId && row.id){
+          this.$router.push({
+          path: "/htgl/businessFlow/BindingPageScript",
+          query: {
+            mode: row.id
+          }
+        });
+        }else {
+          this.$message.error('文件标识不存在!');
+        }
+      },
+        // 上传前校验
+        beforeUpload(file) {
+            const isVueFile = file.name.endsWith('.vue');
+            if (!isVueFile) this.$message.error('只能上传 Vue 文件!');
+            return isVueFile;
+        },
+        //自定义上传接口
+        customUpload({ file }) {
+            const formData = new FormData();
+            formData.append('file', file);  // 根据后端接口字段名调整 key 名
+            uploadVuePage(formData)
+            .then(response => {
+                this.handleUploadSuccess(response, file);
+            })
+        },
+        // 上传成功处理
+        handleUploadSuccess(response, file) {
+            this.uploadLoading = false;
+            if (response.code === 200) {
+                // console.log(response)
+                // 假设接口返回文件内容在response.data.content中
+                this.form.vueCode = response.data;
+                this.$modal.msgSuccess('文件上传成功');
+            }
+        },
+        // 读取本地文件的方法
+        readLocalFile(file) {
+        return new Promise((resolve, reject) => {
+            const reader = new FileReader();
+            reader.onload = (event) => {
+            resolve(event.target.result);
+            };
+            reader.onerror = (error) => {
+            reject(error);
+            };
+            reader.readAsText(file);
+        });
+        },
+        downloadVueTemplate(){
+          downloadTemplateVue().then(response => {
+            if (response instanceof Blob) {
+              // 创建一个blob链接
+              const url = window.URL.createObjectURL(new Blob([response]));
+              const link = document.createElement('a');
+              link.href = url;
+              link.setAttribute('download', 'template.vue');
+              document.body.appendChild(link);
+              link.click();
+              document.body.removeChild(link);
+            }
+          })
+        },
+    }
+  };
+  </script>
+  <style lang="scss" scoped>
+  .codeEditBox {
+    width: 100%;
+    height: 200px;
+  }
+  .monaco-container {
+    width: 100%;
+    height: 100%;
+  }
+  </style>
+  

+ 122 - 10
zkqy-ui/src/views/system/excuteBtnMange/index.vue

@@ -446,6 +446,63 @@
                 </el-select>
               </el-form-item>
             </el-col>
+            <!--业务脚本-->
+            <el-col :span="12" v-show="btnGroupFormData.btnType == 'BUSINESSSCRIPT'">
+              <el-form-item :label="$t('excuteBtn.bindScript')" prop="btnBusinessFlowScriptKey">
+                <el-select
+                  v-model="btnGroupFormData.btnBusinessFlowScriptKey"
+                  :placeholder="$t('excuteBtn.selectBusinessFlowScript')"
+                  clearable
+                  filterable
+                >
+                  <el-option
+                    v-for="item in businessFlowScriptOption"
+                    :key="item.businessKey"
+                    :label="item.remark"
+                    :value="item.businessKey"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <!--业务脚本类型-->
+            <el-col :span="12" v-show="btnGroupFormData.btnType == 'BUSINESSSCRIPT'">
+              <el-form-item :label="$t('excuteBtn.bindBusinessFlowScriptType')" prop="btnBusinessFlowScriptType">
+                <el-select
+                  v-model="btnGroupFormData.btnBusinessFlowScriptType"
+                  :placeholder="$t('excuteBtn.selectBusinessFlowScriptType')"
+                  clearable
+                  filterable
+                >
+                  <el-option
+                    v-for="item in businessFlowScriptTypeOption"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <!--业务脚本页面-->
+            <el-col :span="12" v-show="btnGroupFormData.btnType == 'BUSINESSSCRIPTPAGE'">
+              <el-form-item :label="$t('excuteBtn.bindBusinessFlowVuePage')" prop="btnBusinessFlowVueKey">
+                <el-select
+                  v-model="btnGroupFormData.btnBusinessFlowVueKey"
+                  :placeholder="$t('excuteBtn.selectBusinessFlowVuePage')"
+                  clearable
+                  filterable
+                >
+                  <el-option
+                    v-for="item in businessFlowVuePageOption"
+                    :key="item.vueKey"
+                    :label="item.remark"
+                    :value="item.vueKey"
+                  >
+                  </el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
             <el-col
               :span="24"
               v-show="
@@ -557,6 +614,7 @@
                 </el-button>
               </div>
             </el-col>
+
           </template>
           <!-- 条件参数 -->
           <div class="filter-table-wrap" style="width: 100%">
@@ -746,7 +804,7 @@ import {
   checkBtn,
 } from "@/api/system/btn";
 import { listForm } from "@/api/dragform/form";
-import { listProcess } from "@/api/bpmprocess/process";
+import {listBusinessFlowScript, listBusinessFlowVuePage, listProcess} from "@/api/bpmprocess/process";
 import { listTable } from "@/api/dragform/tableList";
 import { listScript } from "@/api/bpmprocess/process";
 import Treeselect from "@riophae/vue-treeselect";
@@ -830,13 +888,7 @@ export default {
             validator: this.checkBtnProcessKey,
             trigger: "change",
           },
-        ],
-        btnScriptKey: [
-          {
-            validator: this.checkBtnScriptKey,
-            trigger: "change",
-          },
-        ],
+        ]
       },
       // 表单类型
       // formTypeOptions: [
@@ -874,7 +926,9 @@ export default {
         btnHasPermi: "", //权限字符
         btnSort: 0, //按钮顺序
         btnKey: "",
-
+        btnBusinessFlowScriptKey:"",
+        btnBusinessFlowScriptType:"",
+        btnBusinessFlowVueKey:"",
         btnStyle: "", //top 类型的按钮参数
       },
       btnStyleOptions: [
@@ -910,6 +964,34 @@ export default {
       tableOptions: [],
       processOptions: [],
       scriptOptions: [],
+      businessFlowScriptOption:[], // 业务脚本组
+      businessFlowVuePageOption:[], // 业务脚本页面
+      businessFlowScriptTypeOption:[
+        {
+          value: "valid",
+          label: this.$t('excuteBtn.insert'),
+        },
+        {
+          value: "query",
+          label: this.$t('excuteBtn.query'),
+        },
+        {
+          value: "add",
+          label: this.$t('excuteBtn.insert'),
+        },
+        {
+          value: "update",
+          label: this.$t('excuteBtn.update'),
+        },
+        {
+          value: "delete",
+          label: this.$t('excuteBtn.delete'),
+        },
+        {
+          value: "other",
+          label: this.$t('excuteBtn.otherMethod'),
+        },
+      ], // 业务脚本组的类型
       // 普遍字段参数
       commonFieldData: [
         // {
@@ -1088,6 +1170,14 @@ export default {
             value:"CALCULATE",
             label: this.$t('excuteBtn.calculate'),
           },
+          {
+            value:"BUSINESSSCRIPT",
+            label: this.$t('excuteBtn.businessScript'),
+          },
+          {
+            value:"BUSINESSSCRIPTPAGE",
+            label: this.$t('excuteBtn.businessScriptPage'),
+          },
         ];
       } else {
         return [
@@ -1131,6 +1221,14 @@ export default {
             value: "EXPORT",
             label: this.$t('excuteBtn.export'),
           },
+          {
+            value:"BUSINESSSCRIPT",
+            label: this.$t('excuteBtn.businessScript'),
+          },
+          {
+            value:"BUSINESSSCRIPTPAGE",
+            label: this.$t('excuteBtn.businessScriptPage'),
+          },
         ];
       }
     },
@@ -1176,7 +1274,6 @@ export default {
     },
     checkBtnScriptKey(rule, value, callback) {
       let { btnType } = this.btnGroupFormData;
-      console.log(btnType);
       if (btnType == "EXECUTE") {
         if (!value) {
           callback(new Error("请绑定脚本"));
@@ -1618,6 +1715,21 @@ export default {
         } else {
           this.$message.error("网络异常请稍后再试");
         }
+
+        let businessFlowScriptOptionRes = await listBusinessFlowScript({ isEnablePaging: false });
+        if (businessFlowScriptOptionRes.code == 200) {
+          this.businessFlowScriptOption = businessFlowScriptOptionRes.rows;
+        } else {
+          this.$message.error("网络异常请稍后再试");
+        }
+
+        let businessFlowVuePageOptionRes = await listBusinessFlowVuePage({ isEnablePaging: false });
+        if (businessFlowVuePageOptionRes.code == 200) {
+          this.businessFlowVuePageOption = businessFlowVuePageOptionRes.rows;
+        } else {
+          this.$message.error("网络异常请稍后再试");
+        }
+
         // 获取表单组列表
         let formGroupRes = await listFormGroup({ isEnablePaging: false });
         if (formGroupRes.code == 200) {

+ 3 - 1
zkqy-ui/vue.config.js

@@ -100,7 +100,9 @@ module.exports = {
       alias: {
         '@': resolve('src'),
         '@packages': resolve('src/views/system/bpmnPro/components'),
-        '@utils': resolve('src/utils/bpmn')
+        '@utils': resolve('src/utils/bpmn'),
+        // 带编译器的 Vue 版本
+        'vue$': 'vue/dist/vue.esm.js'
       }
     },
     plugins: [