赞
踩
更多ruoyi-nbcio功能请看演示系统
gitee源代码地址
前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio
演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/
更多nbcio-boot功能请看演示系统
gitee源代码地址
后端代码: https://gitee.com/nbacheng/nbcio-boot
前端代码:https://gitee.com/nbacheng/nbcio-vue.git
在线演示(包括H5) : http://122.227.135.243:9888
1、原有ProcessDesigner.vue的vue2代码如下:
- <template>
- <div class="my-process-designer">
- <div class="my-process-designer__header">
- <slot name="control-header"></slot>
- <template v-if="!$slots['control-header']">
- <el-button-group key="file-control">
- <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-edit-outline" @click="onSave">保存流程</el-button>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="FolderOpened" @click="$refs.refFile.click()">打开文件</el-button>
- <el-tooltip effect="light">
- <template #content>
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsXml()">下载为XML文件</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
- </template>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Download">下载文件</el-button>
- </el-tooltip>
- <el-tooltip effect="light">
- <template #content>
- <el-button :size="headerButtonSize" type="primary" @click="previewProcessXML">预览XML</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="previewProcessJson">预览JSON</el-button>
- </template>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="View">预览</el-button>
- </el-tooltip>
- <el-tooltip v-if="simulation" effect="light" :content="this.simulationStatus ? '退出模拟' : '开启模拟'">
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Cpu" @click="processSimulation">
- 模拟
- </el-button>
- </el-tooltip>
- </el-button-group>
- <el-button-group key="align-control">
- <el-tooltip effect="light" content="向左对齐">
- <el-button :size="headerButtonSize" class="align align-left" :icon="Histogram" @click="elementsAlign('left')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向右对齐">
- <el-button :size="headerButtonSize" class="align align-right" :icon="Histogram" @click="elementsAlign('right')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向上对齐">
- <el-button :size="headerButtonSize" class="align align-top" :icon="Histogram" @click="elementsAlign('top')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向下对齐">
- <el-button :size="headerButtonSize" class="align align-bottom" :icon="Histogram" @click="elementsAlign('bottom')" />
- </el-tooltip>
- <el-tooltip effect="light" content="水平居中">
- <el-button :size="headerButtonSize" class="align align-center" :icon="Histogram" @click="elementsAlign('center')" />
- </el-tooltip>
- <el-tooltip effect="light" content="垂直居中">
- <el-button :size="headerButtonSize" class="align align-middle" :icon="Histogram" @click="elementsAlign('middle')" />
- </el-tooltip>
- </el-button-group>
- <el-button-group key="scale-control">
- <el-tooltip effect="light" content="缩小视图">
- <el-button :size="headerButtonSize" :disabled="defaultZoom < 0.2" :icon="ZoomOut" @click="processZoomOut()" />
- </el-tooltip>
- <el-button :size="headerButtonSize">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
- <el-tooltip effect="light" content="放大视图">
- <el-button :size="headerButtonSize" :disabled="defaultZoom > 4" :icon="ZoomIn" @click="processZoomIn()" />
- </el-tooltip>
- <el-tooltip effect="light" content="重置视图并居中">
- <el-button :size="headerButtonSize" :icon="ScaleToOriginal" @click="processReZoom()" />
- </el-tooltip>
- </el-button-group>
- <el-button-group key="stack-control">
- <el-tooltip effect="light" content="撤销">
- <el-button :size="headerButtonSize" :disabled="!revocable" :icon="RefreshLeft" @click="processUndo()" />
- </el-tooltip>
- <el-tooltip effect="light" content="恢复">
- <el-button :size="headerButtonSize" :disabled="!recoverable" :icon="RefreshRight" @click="processRedo()" />
- </el-tooltip>
- <el-tooltip effect="light" content="重新绘制">
- <el-button :size="headerButtonSize" :icon="Refresh" @click="processRestart" />
- </el-tooltip>
- </el-button-group>
- </template>
- <!-- 用于打开本地文件-->
- <input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
- </div>
- <div class="my-process-designer__container">
- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div>
- </div>
- <el-dialog title="预览" width="60%" v-model="previewModelVisible" append-to-body destroy-on-close>
- <highlightjs :language="previewType" :code="previewResult" style="height: 80vh" />
- </el-dialog>
- <!--<el-dialog :title="`预览${previewType}`" width="60%" v-model="previewModelVisible" append-to-body destroy-on-close>
- <Codemirror
- v-model:value="previewResult"
- :options="cmOptions"
- border
- :height="700"
- />
- </el-dialog>-->
- </div>
- </template>
-
- <script>
- import { Histogram, Cpu, Refresh, RefreshLeft, RefreshRight, ZoomOut, ZoomIn, View, Download, FolderOpened, ScaleToOriginal } from '@element-plus/icons-vue'
- import BpmnModeler from "bpmn-js/lib/Modeler";
- import DefaultEmptyXML from "./plugins/defaultEmpty";
- // 翻译方法
- import customTranslate from "./plugins/translate/customTranslate";
- import translationsCN from "./plugins/translate/zh";
- // 模拟流转流程
- import tokenSimulation from "bpmn-js-token-simulation";
- // 标签解析构建器
- // import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
- // 标签解析 Moddle
- import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json';
- import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json';
- import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json';
- // 标签解析 Extension
- import camundaModdleExtension from './plugins/extension-moddle/camunda';
- import activitiModdleExtension from './plugins/extension-moddle/activiti';
- import flowableModdleExtension from './plugins/extension-moddle/flowable';
- // 引入json转换与高亮
- // import X2JS from "x2js";
- import convert from "xml-js";
-
- import Codemirror from 'codemirror-editor-vue3';
- import 'codemirror/theme/monokai.css'
- import 'codemirror/mode/javascript/javascript.js';
- import 'codemirror/mode/xml/xml.js';
-
- export default {
- name: "MyProcessDesigner",
- componentName: "MyProcessDesigner",
- components: {
- Codemirror
- },
- setup() {
- return {
- Histogram, Cpu, Refresh, RefreshLeft, RefreshRight, ZoomOut, ZoomIn, View, Download, FolderOpened, ScaleToOriginal
- }
- },
- emits: ['destroy', 'init-finished', 'commandStack-changed', 'update:modelValue', 'change', 'canvas-viewbox-changed', 'element-click'],
- props: {
- modelValue: String, // xml 字符串
- processId: String,
- processName: String,
- translations: Object, // 自定义的翻译文件
- options: {
- type: Object,
- default: () => ({})
- }, // 自定义的翻译文件
- additionalModel: [Object, Array], // 自定义model
- moddleExtension: Object, // 自定义moddle
- onlyCustomizeAddi: {
- type: Boolean,
- default: false
- },
- onlyCustomizeModdle: {
- type: Boolean,
- default: false
- },
- simulation: {
- type: Boolean,
- default: true
- },
- keyboard: {
- type: Boolean,
- default: true
- },
- prefix: {
- type: String,
- default: "flowable"
- },
- events: {
- type: Array,
- default: () => ["element.click"]
- },
- headerButtonSize: {
- type: String,
- default: "small",
- validator: value => ["default", "medium", "small", "mini"].indexOf(value) !== -1
- },
- headerButtonType: {
- type: String,
- default: "primary",
- validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
- }
- },
- data() {
- return {
- defaultZoom: 1,
- previewModelVisible: false,
- simulationStatus: false,
- previewResult: "",
- previewType: "xml",
- recoverable: false,
- revocable: false,
- cmOptions: {
- mode: 'xml', // 语言模式
- theme: 'monokai', // 主题
- lineNumbers: true, // 显示行号
- smartIndent: true, // 智能缩进
- readOnly: true,
- indentUnit: 2, // 智能缩进单位为4个空格长度
- foldGutter: true, // 启用行槽中的代码折叠
- styleActiveLine: true // 显示选中行的样式
- }
- };
- },
- computed: {
- additionalModules() {
- const Modules = [];
- // 仅保留用户自定义扩展模块
- if (this.onlyCustomizeAddi) {
- if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
- return this.additionalModel || [];
- }
- return [this.additionalModel];
- }
-
- // 插入用户自定义扩展模块
- if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
- Modules.push(...this.additionalModel);
- } else {
- this.additionalModel && Modules.push(this.additionalModel);
- }
-
- // 翻译模块
- const TranslateModule = {
- translate: ["value", customTranslate(this.translations || translationsCN)]
- };
- Modules.push(TranslateModule);
-
- // 模拟流转模块
- if (this.simulation) {
- Modules.push(tokenSimulation);
- }
-
- // 根据需要的流程类型设置扩展元素构建模块
- // if (this.prefix === "bpmn") {
- // Modules.push(bpmnModdleExtension);
- // }
- if (this.prefix === "camunda") {
- Modules.push(camundaModdleExtension);
- }
- if (this.prefix === "flowable") {
- Modules.push(flowableModdleExtension);
- }
- if (this.prefix === "activiti") {
- Modules.push(activitiModdleExtension);
- }
-
- return Modules;
- },
- moddleExtensions() {
- const Extensions = {};
- // 仅使用用户自定义模块
- if (this.onlyCustomizeModdle) {
- return this.moddleExtension || null;
- }
-
- // 插入用户自定义模块
- if (this.moddleExtension) {
- for (let key in this.moddleExtension) {
- Extensions[key] = this.moddleExtension[key];
- }
- }
-
- // 根据需要的 "流程类型" 设置 对应的解析文件
- if (this.prefix === "activiti") {
- Extensions.activiti = activitiModdleDescriptor;
- }
- if (this.prefix === "flowable") {
- Extensions.flowable = flowableModdleDescriptor;
- }
- if (this.prefix === "camunda") {
- Extensions.camunda = camundaModdleDescriptor;
- }
-
- return Extensions;
- }
- },
- mounted() {
- this.initBpmnModeler();
- this.createNewDiagram(this.modelValue);
- // this.$once("hook:beforeUnmount", () => {
- // if (this.bpmnModeler) this.bpmnModeler.destroy();
- // this.$emit("destroy", this.bpmnModeler);
- // this.bpmnModeler = null;
- // });
- },
- beforeUnmount() {
- if (this.bpmnModeler) this.bpmnModeler.destroy();
- this.$emit("destroy", this.bpmnModeler);
- this.bpmnModeler = null;
- },
- methods: {
- onSave () {
- return new Promise((resolve, reject) => {
- if (this.bpmnModeler == null) {
- reject();
- }
- this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- this.$emit('save', xml);
- resolve(xml);
- });
- })
- },
- initBpmnModeler() {
- if (this.bpmnModeler) return;
- this.bpmnModeler = new BpmnModeler({
- container: this.$refs["bpmn-canvas"],
- keyboard: this.keyboard ? { bindTo: document } : null,
- additionalModules: this.additionalModules,
- moddleExtensions: this.moddleExtensions,
- ...this.options
- });
- this.$emit("init-finished", this.bpmnModeler);
- this.initModelListeners();
- },
- initModelListeners() {
- const EventBus = this.bpmnModeler.get("eventBus");
- const that = this;
- // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
- this.events.forEach(event => {
- EventBus.on(event, function(eventObj) {
- let eventName = event.replace(/\./g, "-");
- let element = eventObj ? eventObj.element : null;
- that.$emit(eventName, element, eventObj);
- });
- });
- // 监听图形改变返回xml
- EventBus.on("commandStack.changed", async event => {
- try {
- this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
- this.revocable = this.bpmnModeler.get("commandStack").canUndo();
- let { xml } = await this.bpmnModeler.saveXML({ format: true });
- this.$emit("commandStack-changed", event);
- this.$emit('update:modelValue', xml);
- this.$emit("change", xml);
- } catch (e) {
- console.error(`[Process Designer Warn]: ${e.message || e}`);
- }
- });
- // 监听视图缩放变化
- this.bpmnModeler.on("canvas.viewbox.changed", ({ viewbox }) => {
- this.$emit("canvas-viewbox-changed", { viewbox });
- const { scale } = viewbox;
- this.defaultZoom = Math.floor(scale * 100) / 100;
- });
- },
- /* 创建新的流程图 */
- async createNewDiagram(xml) {
- // 将字符串转换成图显示出来
- let newId = this.processId || `Process_${new Date().getTime()}`;
- let newName = this.processName || `业务流程_${new Date().getTime()}`;
- let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
- try {
- let { warnings } = await this.bpmnModeler.importXML(xmlString);
- if (warnings && warnings.length) {
- warnings.forEach(warn => console.warn(warn));
- }
- } catch (e) {
- console.error(`[Process Designer Warn]: ${e?.message || e}`);
- }
- },
-
- // 下载流程图到本地
- /**
- * @param {string} type
- * @param {*} name
- */
- async downloadProcess(type, name) {
- try {
- const _this = this;
- // 按需要类型创建文件并下载
- if (type === "xml" || type === "bpmn") {
- const { err, xml } = await this.bpmnModeler.saveXML();
- // 读取异常时抛出异常
- if (err) {
- console.error(`[Process Designer Warn ]: ${err.message || err}`);
- }
- let { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml);
- downloadFunc(href, filename);
- } else {
- const { err, svg } = await this.bpmnModeler.saveSVG();
- // 读取异常时抛出异常
- if (err) {
- return console.error(err);
- }
- let { href, filename } = _this.setEncoded("SVG", name, svg);
- downloadFunc(href, filename);
- }
- } catch (e) {
- console.error(`[Process Designer Warn ]: ${e.message || e}`);
- }
- // 文件下载方法
- function downloadFunc(href, filename) {
- if (href && filename) {
- let a = document.createElement("a");
- a.download = filename; //指定下载的文件名
- a.href = href; // URL对象
- a.click(); // 模拟点击
- URL.revokeObjectURL(a.href); // 释放URL 对象
- }
- }
- },
-
- // 根据所需类型进行转码并返回下载地址
- setEncoded(type, filename = "diagram", data) {
- const encodedData = encodeURIComponent(data);
- return {
- filename: `${filename}.${type}`,
- href: `data:application/${type === "svg" ? "text/xml" : "bpmn20-xml"};charset=UTF-8,${encodedData}`,
- data: data
- };
- },
-
- // 加载本地文件
- importLocalFile() {
- const that = this;
- const file = this.$refs.refFile.files[0];
- const reader = new FileReader();
- reader.readAsText(file);
- reader.onload = function() {
- let xmlStr = this.result;
- that.createNewDiagram(xmlStr);
- };
- },
- /* ------------------------------------------------ refs methods ------------------------------------------------------ */
- downloadProcessAsXml() {
- this.downloadProcess("xml");
- },
- downloadProcessAsBpmn() {
- this.downloadProcess("bpmn");
- },
- downloadProcessAsSvg() {
- this.downloadProcess("svg");
- },
- processSimulation() {
- this.simulationStatus = !this.simulationStatus;
- this.simulation && this.bpmnModeler.get("toggleMode").toggleMode();
- },
- processRedo() {
- this.bpmnModeler.get("commandStack").redo();
- },
- processUndo() {
- this.bpmnModeler.get("commandStack").undo();
- },
- processZoomIn(zoomStep = 0.1) {
- let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
- if (newZoom > 4) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
- }
- this.defaultZoom = newZoom;
- this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
- },
- processZoomOut(zoomStep = 0.1) {
- let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
- if (newZoom < 0.2) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
- }
- this.defaultZoom = newZoom;
- this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
- },
- processZoomTo(newZoom = 1) {
- if (newZoom < 0.2) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
- }
- if (newZoom > 4) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
- }
- this.defaultZoom = newZoom;
- this.bpmnModeler.get("canvas").zoom(newZoom);
- },
- processReZoom() {
- this.defaultZoom = 1;
- this.bpmnModeler.get("canvas").zoom("fit-viewport", "auto");
- },
- processRestart() {
- this.recoverable = false;
- this.revocable = false;
- this.createNewDiagram(null);
- },
- elementsAlign(align) {
- const Align = this.bpmnModeler.get("alignElements");
- const Selection = this.bpmnModeler.get("selection");
- const SelectedElements = Selection.get();
- if (!SelectedElements || SelectedElements.length <= 1) {
- this.$message.warning("请按住 Ctrl 键选择多个元素对齐");
- return;
- }
- this.$confirm("自动对齐可能造成图形变形,是否继续?", "警告", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning"
- }).then(() => Align.trigger(SelectedElements, align));
- },
- /*----------------------------- 方法结束 ---------------------------------*/
- previewProcessXML() {
- this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- this.previewResult = xml;
- this.previewType = 'xml';
- //this.cmOptions.mode = 'xml'
- this.previewModelVisible = true;
- });
- },
- previewProcessJson() {
- this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- this.previewResult = convert.xml2json(xml, { spaces: 2 });
- this.previewType = "json";
- this.previewModelVisible = true;
- });
- }
- }
- };
- </script>
2、修改成vue3后的代码如下:
- <template>
- <div class="my-process-designer">
- <div class="my-process-designer__header">
- <slot name="control-header"></slot>
- <template v-if="!$slots['control-header']">
- <el-button-group key="file-control">
- <el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-edit-outline" @click="onSave">保存流程</el-button>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="FolderOpened" @click="refFile.click()">打开文件</el-button>
- <el-tooltip effect="light">
- <template #content>
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsXml()">下载为XML文件</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
- </template>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Download">下载文件</el-button>
- </el-tooltip>
- <el-tooltip effect="light">
- <template #content>
- <el-button :size="headerButtonSize" type="primary" @click="previewProcessXML">预览XML</el-button>
- <br />
- <el-button :size="headerButtonSize" type="primary" @click="previewProcessJson">预览JSON</el-button>
- </template>
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="View">预览</el-button>
- </el-tooltip>
- <el-tooltip v-if="simulation" effect="light" :content="simulationStatus ? '退出模拟' : '开启模拟'">
- <el-button :size="headerButtonSize" :type="headerButtonType" :icon="Cpu" @click="processSimulation">
- 模拟
- </el-button>
- </el-tooltip>
- </el-button-group>
- <el-button-group key="align-control">
- <el-tooltip effect="light" content="向左对齐">
- <el-button :size="headerButtonSize" class="align align-left" :icon="Histogram" @click="elementsAlign('left')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向右对齐">
- <el-button :size="headerButtonSize" class="align align-right" :icon="Histogram" @click="elementsAlign('right')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向上对齐">
- <el-button :size="headerButtonSize" class="align align-top" :icon="Histogram" @click="elementsAlign('top')" />
- </el-tooltip>
- <el-tooltip effect="light" content="向下对齐">
- <el-button :size="headerButtonSize" class="align align-bottom" :icon="Histogram" @click="elementsAlign('bottom')" />
- </el-tooltip>
- <el-tooltip effect="light" content="水平居中">
- <el-button :size="headerButtonSize" class="align align-center" :icon="Histogram" @click="elementsAlign('center')" />
- </el-tooltip>
- <el-tooltip effect="light" content="垂直居中">
- <el-button :size="headerButtonSize" class="align align-middle" :icon="Histogram" @click="elementsAlign('middle')" />
- </el-tooltip>
- </el-button-group>
- <el-button-group key="scale-control">
- <el-tooltip effect="light" content="缩小视图">
- <el-button :size="headerButtonSize" :disabled="defaultZoom < 0.2" :icon="ZoomOut" @click="processZoomOut()" />
- </el-tooltip>
- <el-button :size="headerButtonSize">{{ Math.floor(defaultZoom * 10 * 10) + "%" }}</el-button>
- <el-tooltip effect="light" content="放大视图">
- <el-button :size="headerButtonSize" :disabled="defaultZoom > 4" :icon="ZoomIn" @click="processZoomIn()" />
- </el-tooltip>
- <el-tooltip effect="light" content="重置视图并居中">
- <el-button :size="headerButtonSize" :icon="ScaleToOriginal" @click="processReZoom()" />
- </el-tooltip>
- </el-button-group>
- <el-button-group key="stack-control">
- <el-tooltip effect="light" content="撤销">
- <el-button :size="headerButtonSize" :disabled="!revocable" :icon="RefreshLeft" @click="processUndo()" />
- </el-tooltip>
- <el-tooltip effect="light" content="恢复">
- <el-button :size="headerButtonSize" :disabled="!recoverable" :icon="RefreshRight" @click="processRedo()" />
- </el-tooltip>
- <el-tooltip effect="light" content="重新绘制">
- <el-button :size="headerButtonSize" :icon="Refresh" @click="processRestart" />
- </el-tooltip>
- </el-button-group>
- </template>
- <!-- 用于打开本地文件-->
- <input type="file" id="files" ref="refFile" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
- </div>
- <div class="my-process-designer__container">
- <div class="my-process-designer__canvas" id = "bpmnCanvas" ref="bpmnCanvas"></div>
- </div>
- <el-dialog title="预览" width="60%" v-model="previewModelVisible" append-to-body destroy-on-close>
- <highlightjs :language="previewType" :code="previewResult" style="height: 80vh" />
- </el-dialog>
- <!--<el-dialog :title="`预览${previewType}`" width="60%" v-model="previewModelVisible" append-to-body destroy-on-close>
- <Codemirror
- v-model:value="previewResult"
- :options="cmOptions"
- border
- :height="700"
- />
- </el-dialog>-->
- </div>
- </template>
-
- <script lang="ts" setup>
- import { Histogram, Cpu, Refresh, RefreshLeft, RefreshRight, ZoomOut, ZoomIn, View, Download, FolderOpened, ScaleToOriginal } from '@element-plus/icons-vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import BpmnModeler from "bpmn-js/lib/Modeler";
- import DefaultEmptyXML from "./plugins/defaultEmpty";
- // 翻译方法
- import customTranslate from "./plugins/translate/customTranslate";
- import translationsCN from "./plugins/translate/zh";
- // 模拟流转流程
- import tokenSimulation from "bpmn-js-token-simulation";
- // 标签解析构建器
- // import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
- // 标签解析 Moddle
- import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json';
- import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json';
- import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json';
- // 标签解析 Extension
- import camundaModdleExtension from './plugins/extension-moddle/camunda';
- import activitiModdleExtension from './plugins/extension-moddle/activiti';
- import flowableModdleExtension from './plugins/extension-moddle/flowable';
- // 引入json转换与高亮
- // import X2JS from "x2js";
- import convert from "xml-js";
-
- import Codemirror from 'codemirror-editor-vue3';
- import 'codemirror/theme/monokai.css'
- import 'codemirror/mode/javascript/javascript.js';
- import 'codemirror/mode/xml/xml.js';
-
- defineOptions({ name: 'MyProcessDesigner' })
-
- const refFile = ref()
- const emit = defineEmits([
- 'destroy',
- 'init-finished',
- 'commandStack-changed',
- 'update:modelValue',
- 'change',
- 'canvas-viewbox-changed',
- 'element-click'
- ])
-
- const props = defineProps({
- modelValue: String, // xml 字符串
- processId: String, // 流程 key 标识
- processName: String, // 流程 name 名字
- formId: Number, // 流程 form 表单编号
- translations: {
- // 自定义的翻译文件
- type: Object,
- default: () => {}
- },
- options: {
- type: Object,
- default: () => ({})
- }, // 自定义的翻译文件
- additionalModel: [Object, Array], // 自定义model
- moddleExtension: {
- // 自定义moddle
- type: Object,
- default: () => {}
- },
- onlyCustomizeAddi: {
- type: Boolean,
- default: false
- },
- onlyCustomizeModdle: {
- type: Boolean,
- default: false
- },
- simulation: {
- type: Boolean,
- default: true
- },
- keyboard: {
- type: Boolean,
- default: true
- },
- prefix: {
- type: String,
- default: 'flowable'
- },
- events: {
- type: Array,
- default: () => ['element.click']
- },
- headerButtonSize: {
- type: String,
- default: 'small',
- validator: (value: string) => ['default', 'medium', 'small', 'mini'].indexOf(value) !== -1
- },
- headerButtonType: {
- type: String,
- default: 'primary',
- validator: (value: string) =>
- ['default', 'primary', 'success', 'warning', 'danger', 'info'].indexOf(value) !== -1
- }
- })
-
- let bpmnModeler: any = null
- const defaultZoom = ref(1)
- const previewModelVisible = ref(false)
- const simulationStatus = ref(false)
- const previewResult = ref('')
- const previewType = ref('xml')
- const recoverable = ref(false)
- const revocable = ref(false)
-
- const cmOptions = ref({
- mode: 'xml', // 语言模式
- theme: 'monokai', // 主题
- lineNumbers: true, // 显示行号
- smartIndent: true, // 智能缩进
- readOnly: true,
- indentUnit: 2, // 智能缩进单位为4个空格长度
- foldGutter: true, // 启用行槽中的代码折叠
- styleActiveLine: true // 显示选中行的样式
- })
-
- const additionalModules = computed(() => {
- const Modules: any[] = []
- // 仅保留用户自定义扩展模块
- if (props.onlyCustomizeAddi) {
- if (Object.prototype.toString.call(props.additionalModel) == '[object Array]') {
- return props.additionalModel || []
- }
- return [props.additionalModel]
- }
-
- // 插入用户自定义扩展模块
- if (Object.prototype.toString.call(props.additionalModel) == '[object Array]') {
- Modules.push(...(props.additionalModel as any[]))
- } else {
- props.additionalModel && Modules.push(props.additionalModel)
- }
-
- // 翻译模块
- const TranslateModule = {
- translate: ['value', customTranslate(props.translations || translationsCN)]
- }
- Modules.push(TranslateModule)
-
- // 模拟流转模块
- if (props.simulation) {
- Modules.push(tokenSimulation)
- }
-
- // 根据需要的流程类型设置扩展元素构建模块
- // if (this.prefix === "bpmn") {
- // Modules.push(bpmnModdleExtension);
- // }
- if (props.prefix === 'camunda') {
- Modules.push(camundaModdleExtension)
- }
- if (props.prefix === 'flowable') {
- Modules.push(flowableModdleExtension)
- }
- if (props.prefix === 'activiti') {
- Modules.push(activitiModdleExtension)
- }
-
- return Modules
- })
-
- const moddleExtensions = computed(() => {
- const Extensions: any = {}
- // 仅使用用户自定义模块
- if (props.onlyCustomizeModdle) {
- return props.moddleExtension || null
- }
-
- // 插入用户自定义模块
- if (props.moddleExtension) {
- for (let key in props.moddleExtension) {
- Extensions[key] = props.moddleExtension[key]
- }
- }
-
- // 根据需要的 "流程类型" 设置 对应的解析文件
- if (props.prefix === 'activiti') {
- Extensions.activiti = activitiModdleDescriptor
- }
- if (props.prefix === 'flowable') {
- Extensions.flowable = flowableModdleDescriptor
- }
- if (props.prefix === 'camunda') {
- Extensions.camunda = camundaModdleDescriptor
- }
- return Extensions
- })
-
- const onSave = async () => {
- return new Promise((resolve, reject) => {
- if (bpmnModeler == null) {
- reject();
- }
- bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- // 触发 save 事件
- emit('save', xml)
- resolve(xml);
- });
- })
- }
-
- const initBpmnModeler = () => {
- if (bpmnModeler) return
- let container = document.getElementById('bpmnCanvas')
-
- bpmnModeler = new BpmnModeler({
- container: container,
- keyboard: props.keyboard ? { bindTo: document } : null,
- additionalModules: additionalModules.value,
- moddleExtensions: moddleExtensions.value,
- ...props.options
- })
- console.log("initBpmnModeler bpmnModeler",bpmnModeler)
- emit('init-finished', bpmnModeler)
- initModelListeners()
- }
-
- const initModelListeners = () => {
- const EventBus = bpmnModeler.get('eventBus')
- // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
- props.events.forEach((event: any) => {
- EventBus.on(event, function (eventObj) {
- let eventName = event.replace(/\./g, '-')
- let element = eventObj ? eventObj.element : null
- emit('element-click', element, eventObj)
- // emit(eventName, element, eventObj)
- })
- })
- // 监听图形改变返回xml
- EventBus.on('commandStack.changed', async (event) => {
- try {
- recoverable.value = bpmnModeler.get('commandStack').canRedo()
- revocable.value = bpmnModeler.get('commandStack').canUndo()
- let { xml } = await bpmnModeler.saveXML({ format: true })
- emit('commandStack-changed', event)
- emit('update:modelValue', xml)
- emit('change', xml)
- } catch (e: any) {
- console.error(`[Process Designer Warn]: ${e.message || e}`)
- }
- })
- // 监听视图缩放变化
- bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
- emit('canvas-viewbox-changed', { viewbox })
- const { scale } = viewbox
- defaultZoom.value = Math.floor(scale * 100) / 100
- })
- }
- /* 创建新的流程图 */
- const createNewDiagram = async (xml) => {
- // 将字符串转换成图显示出来
- let newId = props.processId || `Process_${new Date().getTime()}`
- let newName = props.processName || `业务流程_${new Date().getTime()}`
- let xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix)
- try {
- let { warnings } = await bpmnModeler.importXML(xmlString)
- if (warnings && warnings.length) {
- warnings.forEach((warn) => console.warn(warn))
- }
- } catch (e: any) {
- console.error(`[Process Designer Warn]: ${e.message || e}`)
- }
- }
-
- // 下载流程图到本地
- /**
- * @param {string} type
- * @param {*} name
- */
- const downloadProcess = async (type, name) => {
- try {
- // 按需要类型创建文件并下载
- if (type === 'xml' || type === 'bpmn') {
- const { err, xml } = await bpmnModeler.saveXML()
- // 读取异常时抛出异常
- if (err) {
- console.error(`[Process Designer Warn ]: ${err.message || err}`)
- }
- let { href, filename } = setEncoded(type.toUpperCase(), name, xml)
- downloadFunc(href, filename)
- } else {
- const { err, svg } = await bpmnModeler.saveSVG()
- // 读取异常时抛出异常
- if (err) {
- return console.error(err)
- }
- let { href, filename } = setEncoded('SVG', name, svg)
- downloadFunc(href, filename)
- }
- } catch (e: any) {
- console.error(`[Process Designer Warn ]: ${e.message || e}`)
- }
- // 文件下载方法
- function downloadFunc(href, filename) {
- if (href && filename) {
- let a = document.createElement('a')
- a.download = filename //指定下载的文件名
- a.href = href // URL对象
- a.click() // 模拟点击
- URL.revokeObjectURL(a.href) // 释放URL 对象
- }
- }
- }
- // 根据所需类型进行转码并返回下载地址
- const setEncoded = (type, filename = 'diagram', data) => {
- const encodedData = encodeURIComponent(data)
- return {
- filename: `${filename}.${type}`,
- href: `data:application/${type === "svg" ? "text/xml" : "bpmn20-xml"};charset=UTF-8,${encodedData}`,
- data: data
- }
- }
-
- // 加载本地文件
- const importLocalFile = () => {
- const file = refFile.value.files[0]
- const reader = new FileReader()
- reader.readAsText(file)
- reader.onload = function () {
- let xmlStr = this.result
- createNewDiagram(xmlStr)
- }
- }
- /* ------------------------------------------------ refs methods ------------------------------------------------------ */
- const downloadProcessAsXml = () => {
- downloadProcess('xml')
- }
- const downloadProcessAsBpmn = () => {
- downloadProcess('bpmn')
- }
- const downloadProcessAsSvg = () => {
- downloadProcess('svg')
- }
- const processSimulation = () => {
- simulationStatus.value = !simulationStatus.value
- props.simulation && bpmnModeler.get('toggleMode').toggleMode()
- }
- const processRedo = () => {
- bpmnModeler.get('commandStack').redo()
- }
- const processUndo = () => {
- bpmnModeler.get('commandStack').undo()
- }
- const processZoomIn = (zoomStep = 0.1) => {
- let newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100
- if (newZoom > 4) {
- throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
- }
- defaultZoom.value = newZoom
- bpmnModeler.get('canvas').zoom(defaultZoom.value)
- }
- const processZoomOut = (zoomStep = 0.1) => {
- let newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100
- if (newZoom < 0.2) {
- throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
- }
- defaultZoom.value = newZoom
- bpmnModeler.get('canvas').zoom(defaultZoom.value)
- }
- const processZoomTo = (newZoom = 1) => {
- if (newZoom < 0.2) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
- }
- if (newZoom > 4) {
- throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
- }
- defaultZoom.value = newZoom;
- bpmnModeler.get("canvas").zoom(newZoom);
- }
-
- const processReZoom = () => {
- defaultZoom.value = 1
- bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
- }
-
- const processRestart = () => {
- recoverable.value = false
- revocable.value = false
- createNewDiagram(null)
- }
- const elementsAlign = (align) => {
- const Align = bpmnModeler.get('alignElements')
- const Selection = bpmnModeler.get('selection')
- const SelectedElements = Selection.get()
- if (!SelectedElements || SelectedElements.length <= 1) {
- ElMessage.warning('请按住 Shift 键选择多个元素对齐')
- return
- }
- ElMessageBox.confirm('自动对齐可能造成图形变形,是否继续?', '警告', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(() => {
- Align.trigger(SelectedElements, align)
- })
- }
- /*----------------------------- 方法结束 ---------------------------------*/
- const previewProcessXML = () => {
- bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- previewResult.value = xml
- previewType.value = 'xml'
- //cmOptions.value['mode'] = 'xml'
- previewModelVisible.value = true
- })
- }
- const previewProcessJson = () => {
- bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
- previewResult.value = convert.xml2json(xml, { spaces: 2 });
- previewType.value = "json";
- previewModelVisible.value = true;
- })
- }
-
- onMounted(() => {
- initBpmnModeler()
- createNewDiagram(props.modelValue)
- })
-
- onBeforeUnmount(() => {
- if (bpmnModeler) bpmnModeler.destroy()
- emit('destroy', bpmnModeler)
- bpmnModeler = null
- })
- </script>
3、效果图如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。