看我怎么拿捏Vue中drawio的二开~

1,692 阅读6分钟

draw.io是一款免费的开源绘图软件,主要用于创建各种类型的图表、流程图、组织结构图、UML图和网络拓扑图等

某一天,作为牛马的我被领导叫到了办公室,说小伙子,有一个工作需要你来做一下,好好干,干好了面包牛奶要啥有啥😯。 牛马像是被换了新的鞭子抽打,立马感觉到了浑身有劲,干就完了!

具体内容就是不知道哪里来的大聪明,提出了需要对drawio进行二次开发的需求,在其中需要根据具体业务需要进行一番修改。了解需求的我立马就开始吭哧吭哧的劳作。

一、下载代码

二开源码可点击查看 版本是真忘记了😅

drawio 源码 github

├── CITATION.cff
├── CODE_OF_CONDUCT.md
├── ChangeLog
├── LICENSE
├── README.md
├── SECURITY.md
├── VERSION
├── docs
│   └── code-review.md
├── etc
├── src
│   └── main
│       ├── java
│       └── webapp
└── teams.html

其中src/main/webapp 是需要改的前端代码

├── META-INF
├── WEB-INF
├── clear.html
├── connect
├── dropbox.html
├── export-fonts.css
├── export3.html
├── favicon.ico
├── github.html
├── gitlab.html
├── images
├── img
├── index.html
├── js
├── math
├── monday-app-association.json
├── mxgraph
├── onedrive3.html
├── open.html
├── package.json
├── plugins
├── resources
├── service-worker.js
├── service-worker.js.map
├── shapes
├── shortcuts.svg
├── stencils
├── styles
├── teams.html
├── templates
├── vsdxImporter.html
├── workbox-12cca165.js
├── workbox-12cca165.js.map
├── workbox-4768a546.js
├── workbox-4768a546.js.map
├── workbox-50a29d49.js
├── workbox-50a29d49.js.map
├── workbox-5490a1bd.js
├── workbox-5490a1bd.js.map
├── workbox-7a2a8380.js
├── workbox-7a2a8380.js.map
├── workbox-99ba3a23.js
├── workbox-99ba3a23.js.map
├── workbox-9fe249eb.js
├── workbox-9fe249eb.js.map
├── workbox-bed83ea8.js
├── workbox-bed83ea8.js.map
├── workbox-d4d5b410.js
├── workbox-d4d5b410.js.map
├── workbox-dfbb910f.js
├── workbox-dfbb910f.js.map
├── workbox-eb9c7348.js
├── workbox-eb9c7348.js.map
├── workbox-f163abaa.js
├── workbox-f163abaa.js.map
├── workbox-fa8c4ce5.js
└── workbox-fa8c4ce5.js.map

二、本地运行

2.1 修改index.html

文件路径:src/mian/webapp/index.html (269行)

		if (urlParams['dev'] == '1')
		{
			// Used to request grapheditor/mxgraph sources in dev mode
			var mxDevUrl = '';
			
			// Used to request draw.io sources in dev mode
			var drawDevUrl = '';
			var geBasePath = 'js/grapheditor';
			var mxBasePath = 'mxgraph/src';
			
			if (document.location.protocol == 'file:')
			{
				// Forces includes for dev environment in node.js
				mxForceIncludes = true;
			}

      
      // ------增加如下代码--------------
			// 本地开发环境
			if (location.hostname == "localhost" || location.hostname == "127.0.0.1") {
          drawDevUrl = `http://${location.host}/`;
          geBasePath = `http://${location.host}/js/grapheditor`;
          mxBasePath = `http://${location.host}/mxgraph`;
          mxForceIncludes = true;
      }
      // ------------------------------

			mxForceIncludes = false;

			mxscript(drawDevUrl + 'js/PreConfig.js');
			mxscript(drawDevUrl + 'js/diagramly/Init.js');
			mxscript(geBasePath + '/Init.js');
			mxscript(mxBasePath + '/mxClient.js');

      // 其他代码...
		}

2.2 本地安装serve

在终端前端项目的根目录下新建package.json文件 并安装serve依赖

➜  drawio:  cd src/main/webapp
➜  webapp:  npm init -y
➜  webapp:  npm i serve -D

修改package.json

{
  "name": "webapp",
  "version": "1.0.0",
  "description": "",
  "main": "service-worker.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    // 新增
    "dev": "serve -p 8888"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "serve": "^14.2.3"
  }
}

命令行执行 npm run dev 打开浏览器 http://localhost:8888 即可看到drawio页面

这样启动修改代码不会同步更新 设置同步更新的方法

  1. 在访问链接后加上url参数 ?dev=1 即访问 http://localhost:8888?devb=1
  2. 修改index.html (39行)设置默认开发环境启动
var urlParams = (function(){
	var result = new Object();
	var params = window.location.search.slice(1).split('&');
	
	for (var i = 0; i < params.length; i++){
			var idx = params[i].indexOf('=');
	
		if (idx > 0){
			result[params[i].substring(0, idx)] = params[i].substring(idx + 1);
		}
	}
		
	// ----------新增如下代码-------
	if (!result.hasOwnProperty('dev')) {
      result['dev'] = 1
  }
	// ----------新增代码结束-------
		
	return result;
})();

三、vue项目使用

3.1 iframe嵌入二开后的drawio页面

<template>
    <div style="width: 100%;height: 100%;">
        <!-- 使用iframe嵌入外部页面 -->  
        <iframe  
            :src="iframeSrc"
            width="100%"  
            height="100%"  
            frameborder="0"  
            sandbox="allow-scripts allow-same-origin"  
        ></iframe>  
    </div>
</template>
<script>
export default {
     computed: {
        iframeSrc() {
            const hostname = window.location.hostname;
            const isDev = hostname.includes('localhost');
            let baseUrl = window.location.origin + '/drawio/index.html';
            if (isDev) {
                baseUrl = 'http://localhost:8888/drawio/index.html';
            }
            if (this.isCreate) {
                return (
                    baseUrl +
                    '?pro_id=' +
                    this.$route.query.pro_id +
                    '&pro_name=' +
                    encodeURIComponent(this.$route.query.pro_name) +
                    '&task_name=' +
                    encodeURIComponent(this.$route.query.task_name)
                );
            }
            return (
                baseUrl +
                '?pro_id=' +
                this.$route.query.pro_id +
                '&pro_name=' +
                encodeURIComponent(this.$route.query.pro_name) +
                '&id=' +
                this.$route.query.id +
                '&drawio_url=' +
                this.$route.query.drawio_url
            );
        },
    },
}
</script>

四、初始化编辑器

4.1 设置默认语言、模式等

修改文件PreConfig.js 文件路径:src/main/webapp/js/PreConfig.js

// 新增如下代码
urlParams.lang = 'zh'; // 国际化默认中文
urlParams['browser'] = 1;
urlParams['gapi'] = 0;
urlParams['db'] = 0;
urlParams['od'] = 0; 
urlParams['tr'] = 0;
urlParams['gh'] =0; 
urlParams['gl'] =0;
urlParams['mode'] = 'browser';

4.2 修改语言选项

修改语言选项只有 英文与简体中文

修改文件Init.js (124行) 文件路径:src/main/webapp/js/diagramly/Init.js

// 注释或删除不需要的选项
window.mxLanguageMap = window.mxLanguageMap ||
{
	// 'i18n': '',
	// 'id' : 'Bahasa Indonesia',
	// 'ms' : 'Bahasa Melayu',
	// 'bs' : 'Bosanski',
	// 'bg' : 'Bulgarian',
	// 'ca' : 'Català',
	// 'cs' : 'Čeština',
	// 'da' : 'Dansk',
	// 'de' : 'Deutsch',
	// 'et' : 'Eesti',
	'en' : 'English',
	// 'es' : 'Español',
	// 'eu' : 'Euskara',
	// 'fil' : 'Filipino',
	// 'fr' : 'Français',
	// 'gl' : 'Galego',
	// 'it' : 'Italiano',
	// 'hu' : 'Magyar',
	// 'lt' : 'Lietuvių',
	// 'lv' : 'Latviešu',
	// 'nl' : 'Nederlands',
	// 'no' : 'Norsk',
	// 'pl' : 'Polski',
	// 'pt-br' : 'Português (Brasil)',
	// 'pt' : 'Português (Portugal)',
	// 'ro' : 'Română',
	// 'fi' : 'Suomi',
	// 'sv' : 'Svenska',
	// 'vi' : 'Tiếng Việt',
	// 'tr' : 'Türkçe',
	// 'el' : 'Ελληνικά',
	// 'ru' : 'Русский',
	// 'sr' : 'Српски',
	// 'uk' : 'Українська',
	// 'he' : 'עברית',
	// 'ar' : 'العربية',
	// 'fa' : 'فارسی',
	// 'th' : 'ไทย',
	// 'ko' : '한국어',
	// 'ja' : '日本語',
	'zh' : '简体中文',
	// 'zh-tw' : '繁體中文'
};

4.3 删除共享、帮助、同步以及创建副本等功能

  • 删除共享

修改文件App.js (6045行) 文件路径:src/main/webapp/js/diagramly/App.js

注释或删除如下代码 可删除右上角分享按钮

// // Share
// if (this.getServiceName() == 'draw.io' &&
// 	urlParams['embed'] != '1' &&
// 	!this.isStandaloneApp())
// {
// 	if (file != null)
// 	{
// 		if (this.shareButton == null && Editor.currentTheme != 'atlas')
// 		{
// 			this.shareButton = document.createElement('button');
// 			this.shareButton.className = 'geBtn geShareBtn';
// 			this.shareButton.style.display = 'inline-block';
// 			this.shareButton.style.position = 'relative';
// 			this.shareButton.style.backgroundImage = 'none';
// 			this.shareButton.style.padding = '2px 10px 0 10px';
// 			this.shareButton.style.marginTop = '-10px';
// 			this.shareButton.style.cursor = 'pointer';
// 			this.shareButton.style.height = '32px';
// 			this.shareButton.style.minWidth = '0px';
// 			this.shareButton.style.top = '-2px';
// 			this.shareButton.setAttribute('title', mxResources.get('share'));

// 			var icon = document.createElement('img');
// 			icon.className = 'geInverseAdaptiveAsset';
// 			icon.setAttribute('src', this.shareImage);
// 			icon.setAttribute('align', 'absmiddle');
// 			icon.style.marginRight = '4px';
// 			icon.style.marginTop = '-3px';
// 			this.shareButton.appendChild(icon);

// 			if (Editor.currentTheme != 'atlas')
// 			{
// 				icon.style.filter = 'invert(100%)';
// 			}

// 			mxUtils.write(this.shareButton, mxResources.get('share'));

// 			mxEvent.addListener(this.shareButton, 'click', mxUtils.bind(this, function()
// 			{
// 				this.actions.get('share').funct();
// 			}));

// 			this.buttonContainer.appendChild(this.shareButton);
// 		}

// 		if (this.shareButton != null)
// 		{
// 			this.shareButton.style.display = (Editor.currentTheme == 'simple' ||
// 				Editor.currentTheme == 'sketch' || Editor.currentTheme == 'min')
// 				? 'none' : 'inline-block';

// 			// Hides parent element if empty for flex layout gap to work
// 			if (Editor.currentTheme == 'simple' ||
// 				Editor.currentTheme == 'sketch')
// 			{
// 				this.shareButton.parentNode.style.display =
// 					(this.shareButton.parentNode.clientWidth == 0)
// 					? 'none' : '';
// 			}
// 		}
// 	}
// 	else if (this.shareButton != null)
// 	{
// 		this.shareButton.parentNode.removeChild(this.shareButton);
// 		this.shareButton = null;
// 	}

// 	// Fetch notifications
// 	if (urlParams['extAuth'] != '1' && 
// 		Editor.currentTheme != 'atlas') //Disable notification with external auth (e.g, Teams app)
// 	{
// 		this.fetchAndShowNotification('online', this.mode);
// 	}
// }
// else
// {
// 	if (urlParams['notif'] != null) //Notif for embed mode
// 	{
// 		this.fetchAndShowNotification(urlParams['notif']);
// 	}

// 	// Hides button container if empty for flex layout gap to work
// 	if (this.isStandaloneApp() &&
// 		(Editor.currentTheme == 'simple' ||
// 		Editor.currentTheme == 'sketch'))
// 	{
// 		this.buttonContainer.style.display =
// 			(this.buttonContainer.clientWidth == 0)
// 			? 'none' : '';
// 	}
// }

修改文件Menus.js (3504行) 文件路径:src/main/webapp/js/diagramly/Menus.js

注释或删除如下代码 可删除点击文件菜单内的分享

// this.editorUi.actions.addAction('share...', mxUtils.bind(this, function()
// {
// 	try
// 	{
// 		var file = editorUi.getCurrentFile();

// 		if (file != null)
// 		{
// 			file.share();
// 		}
// 	}
// 	catch (e)
// 	{
// 		editorUi.handleError(e);
// 	}
// })).isEnabled = isGraphEnabled;
  • 删除帮助

修改文件Menus.js (33行) 文件路径:src/main/webapp/js/grapheditor/Menus.js

/**
 * Sets the default font size.
 */
// Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras', 'help'];
Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras'];
  • 删除同步

修改文件Menus.js (582行) 文件路径:src/main/webapp/js/diagramly/Menus.js

注释或删除如下代码 可删除点击文件菜单内的同步菜单

// var action = editorUi.actions.addAction('synchronize', function()
// {
// 	editorUi.synchronizeCurrentFile(DrawioFile.SYNC == 'none');
// }, null, null, 'Alt+Shift+S');
  • 删除创建副本

修改文件Menus.js (3363行) 文件路径:src/main/webapp/js/diagramly/Menus.js

// editorUi.actions.addAction('makeCopy...', mxUtils.bind(this, function()
// {
// 	var file = editorUi.getCurrentFile();

// 	if (file != null)
// 	{
// 		var title = editorUi.getCopyFilename(file);

// 		if (file.constructor == DriveFile)
// 		{
// 			var dlg = new CreateDialog(editorUi, title, mxUtils.bind(this, function(newTitle, mode)
// 			{
// 				if (mode == '_blank')
// 				{
// 					editorUi.editor.editAsNew(editorUi.getFileData(), newTitle);
// 				}
// 				else
// 				{
// 					// Mode is "download" if Create button is pressed, means use Google Drive
// 					if (mode == 'download')
// 					{
// 						mode = App.MODE_GOOGLE;
// 					}

// 					if (newTitle != null && newTitle.length > 0)
// 					{
// 						if (mode == App.MODE_GOOGLE)
// 						{
// 							if (editorUi.spinner.spin(document.body, mxResources.get('saving')))
// 							{
// 								// Saveas does not update the file descriptor in Google Drive
// 								file.saveAs(newTitle, mxUtils.bind(this, function(resp)
// 								{
// 									// Replaces file descriptor in-place and saves
// 									file.desc = resp;

// 									// Makes sure the latest XML is in the file
// 									file.save(false, mxUtils.bind(this, function()
// 									{
// 										editorUi.spinner.stop();
// 										file.setModified(false);
// 										file.addAllSavedStatus();
// 									}), mxUtils.bind(this, function(resp)
// 									{
// 										editorUi.handleError(resp);
// 									}));
// 								}), mxUtils.bind(this, function(resp)
// 								{
// 									editorUi.handleError(resp);
// 								}));
// 							}
// 						}
// 						else
// 						{
// 							editorUi.createFile(newTitle, editorUi.getFileData(true), null, mode);
// 						}
// 					}
// 				}
// 			}), mxUtils.bind(this, function()
// 			{
// 				editorUi.hideDialog();
// 			}), mxResources.get('makeCopy'), mxResources.get('create'), null,
// 				null, true, null, true, null, null, null, null,
// 				editorUi.editor.fileExtensions);
// 			editorUi.showDialog(dlg.container, 420, 380, true, true);
// 			dlg.init();
// 		}
// 		else
// 		{
// 			// Creates a copy with no predefined storage
// 			editorUi.editor.editAsNew(this.editorUi.getFileData(true), title);
// 		}
// 	}
// }));

修改文件EditorUi.js 文件路径:src/main/webapp/js/diagramly/EditorUi.js

// 18638行
// this.actions.get('makeCopy').setEnabled(!restricted);

// 18817行
// this.actions.get('makeCopy').setEnabled(file != null && !file.isRestricted());

五、自定义编辑器

5.1 修改保存至弹窗内容

因为对我们的需求来说,只需要一个自定义的保存和保存至Device设备就可以了。所以这里会删除其他的,并且新增加一种方式。

  • 删除对应的图标,新增自定义图标

修改文件Dialogs.js (206行) 文件路径:src/main/webapp/js/diagramly/Dialogs.js


var addButtons = function()
	{
		count = 0;
		/*
		* 新增自定义图标
		* 需要在国际化语言文件内新增对应的自定义功能的多语言
		* 并且需要在App.js内新增新的mode App.MODE_NEW_BTN
		*/
		addLogo(IMAGE_PATH + '/osa_drive-harddisk.png', mxResources.get('new btn'), App.MODE_NEW_BTN);

		

		// 删除无用的图标
		
		// if (typeof window.DriveClient === 'function')
		// {
		// 	addLogo(IMAGE_PATH + '/google-drive-logo.svg', mxResources.get('googleDrive'), App.MODE_GOOGLE, 'drive');
		// }

		// if (typeof window.OneDriveClient === 'function')
		// {
		// 	addLogo(IMAGE_PATH + '/onedrive-logo.svg', mxResources.get('oneDrive'), App.MODE_ONEDRIVE, 'oneDrive');
		// }

		if (urlParams['noDevice'] != '1')
		{
			addLogo(IMAGE_PATH + '/osa_drive-harddisk.png', mxResources.get('device'), App.MODE_DEVICE);			
		}

		// if (isLocalStorage && (urlParams['browser'] == '1' || urlParams['offline'] == '1'))
		// {
		// 	addLogo(IMAGE_PATH + '/osa_database.png', mxResources.get('browser'), App.MODE_BROWSER);
		// }

		// if (typeof window.DropboxClient === 'function')
		// {
		// 	addLogo(IMAGE_PATH + '/dropbox-logo.svg', mxResources.get('dropbox'), App.MODE_DROPBOX, 'dropbox');
		// }

		// if (editorUi.gitHub != null)
		// {
		// 	addLogo(IMAGE_PATH + '/github-logo.svg', mxResources.get('github'), App.MODE_GITHUB, 'gitHub');
		// }

		// if (editorUi.gitLab != null)
		// {
		// 	addLogo(IMAGE_PATH + '/gitlab-logo.svg', mxResources.get('gitlab'), App.MODE_GITLAB, 'gitLab');
		// }
	};

  • 修改相关的后续方法

修改文件Dialogs.js (311行) 文件路径:src/main/webapp/js/diagramly/Dialogs.js

if (editorUi.mode == App.MODE_NEW_BTN) {
	logo.src = IMAGE_PATH + '/onedrive-connector-atlas.png';
	buttons.style.paddingBottom = '10px';
	buttons.style.paddingTop = '30px';
	service = mxResources.get('new btn')
} else {
	logo.src = IMAGE_PATH + "/osa_drive-harddisk.png";
	buttons.style.paddingBottom = "10px";
	buttons.style.paddingTop = "30px";
	service = mxResources.get("device");
}

修改文件Dialogs.js (403行) 文件路径:src/main/webapp/js/diagramly/Dialogs.js

if (editorUi.mode == App.MODE_NEW_BTN) {
	storage = mxResources.get("new btn");
} else if (editorUi.mode == App.MODE_DEVICE) {
	storage = mxResources.get("device");
}

修改文件App.js (4113行) 文件路径:src/main/webapp/js/diagramly/App.js

// 修改pickFile 方法 要不点击打开现有绘图会无反应
App.prototype.pickFile = function(mode)
{
	try
	{
		mode = (mode != null) ? mode : this.mode;
		
		if (mode == App.MODE_GOOGLE)
		{
			if (this.drive != null && typeof(google) != 'undefined' && typeof(google.picker) != 'undefined')
			{
				this.drive.pickFile();
			}
			else
			{
				this.openLink('https://drive.google.com');
			}
		}
		else
		{
			var peer = this.getServiceForName(mode);
			
			if (peer != null)
			{
				peer.pickFile();
			}
			else if (mode == App.MODE_DEVICE && EditorUi.nativeFileSupport)
			{
			// ...
			}
			// 修改此行 !!!!!!
			// else if (mode == App.MODE_DEVICE && Graph.fileSupport){
			else if ([App.MODE_DEVICE, App.MODE_NEW_BTN].includes(mode) && Graph.fileSupport){
			
				// ....
			}
	
};

5.2 修改保存弹窗

我们的需求是

1、首先保存类型在自定义的情况下,只支持xml类型的文件

修改文件Dialogs.js (5005行) 文件路径:src/main/webapp/js/diagramly/Dialogs.js

typeSelect = FilenameDialog.createFileTypes(editorUi, saveAsInput,
																						editorUi.editor.diagramFileTypes);
typeSelect.style.boxSizing = 'border-box';
typeSelect.style.width = '100%';

// 新增如下判断
if (editorUi.mode != App.MODE_NEW_BTN) {
	typeSelect.disabled = true;
}

right.appendChild(typeSelect);

2、保存位置不需要其他的 只需要设备、下载、以及自定义三种方式

修改文件App.js (4545行) 文件路径:src/main/webapp/js/diagramly/App.js

// 修改 saveFile 方法 取消根据类型判断展示不同弹窗,而是直接显示选择弹窗
App.prototype.saveFile = function (forceDialog, success) {
	var file = this.getCurrentFile();
	var prev = this.mode;
  
	if (file != null) {
	  // FIXME: Invoke for local files
	  var done = mxUtils.bind(this, function () {
		if (EditorUi.enableDrafts) {
		  file.removeDraft();
		}
  
		if (this.getCurrentFile() != file && !file.isModified()) {
		  // Workaround for possible status update while save as dialog is showing
		  // is to show no saved status for device files
		  if (file.getMode() != App.MODE_DEVICE) {
			this.editor.setStatus(
			  mxUtils.htmlEntities(mxResources.get("allChangesSaved"))
			);
		  } else {
			this.editor.setStatus("");
		  }
		}
  
		if (success != null) {
		  success();
		}
	  });
  
		var filename =
		  file.getTitle() != null ? file.getTitle() : this.defaultFilename;
  
		var saveFunction = mxUtils.bind(
		  this,
		  function (name, mode, input, folderId) {
			if (name != null && name.length > 0) {
			  // Handles special case where PDF export is detected
			  if (/(.pdf)$/i.test(name)) {
				this.confirm(
				  mxResources.get("didYouMeanToExportToPdf"),
				  mxUtils.bind(this, function () {
					this.hideDialog();
					this.actions.get("exportPdf").funct();
				  }),
				  mxUtils.bind(this, function () {
					input.value = name.split(".").slice(0, -1).join(".");
					input.focus();
  
					if (
					  mxClient.IS_GC ||
					  mxClient.IS_FF ||
					  document.documentMode >= 5
					) {
					  input.select();
					} else {
					  document.execCommand("selectAll", false, null);
					}
				  }),
				  mxResources.get("yes"),
				  mxResources.get("no")
				);
			  } else {
				this.hideDialog();
  
				if (prev == null && mode == App.MODE_DEVICE) {
				  if (file != null && EditorUi.nativeFileSupport) {
					this.showSaveFilePicker(
					  mxUtils.bind(this, function (fileHandle, desc) {
						file.fileHandle = fileHandle;
						file.mode = App.MODE_DEVICE;
						file.title = desc.name;
						file.desc = desc;
  
						this.setMode(App.MODE_DEVICE);
						this.save(desc.name, done);
					  }),
					  mxUtils.bind(this, function (e) {
						if (e.name != "AbortError") {
						  this.handleError(e);
						}
					  }),
					  this.createFileSystemOptions(name)
					);
				  } else {
					this.setMode(App.MODE_DEVICE);
					this.save(name, done);
				  }
				} else if (mode == "download") {
				  var tmp = new LocalFile(this, null, name);
				  tmp.save();
				} else if (mode == "_blank") {
				  window.openFile = new OpenFile(function () {
					window.openFile = null;
				  });
  
				  // Do not use a filename to use undefined mode
				  window.openFile.setData(this.getFileData(true));
				  this.openLink(
					this.getUrl(window.location.pathname),
					null,
					true
				  );
				} else if (prev != mode) {
				  var createFile = mxUtils.bind(this, function (folderId) {
					var graph = this.editor.graph;
					var selection = graph.getSelectionCells();
					var viewState = graph.getViewState();
					var page = this.currentPage;
  
					this.createFile(
					  name,
					  this.getFileData(
						/(.xml)$/i.test(name) ||
						  name.indexOf(".") < 0 ||
						  /(.drawio)$/i.test(name),
						/(.svg)$/i.test(name),
						/(.html)$/i.test(name)
					  ),
					  null,
					  mode,
					  done,
					  this.mode == null,
					  folderId,
					  null,
					  null,
					  mxUtils.bind(this, function () {
						this.restoreViewState(page, viewState, selection);
					  })
					);
				  });
  
				  if (folderId != null) {
					createFile(folderId);
				  } else {
					this.pickFolder(mode, createFile);
				  }
				} else if (mode != null) {
				  this.save(name, done);
				}
			  }
			}
		  }
		);
  
		var allowTab = !mxClient.IS_IOS || !navigator.standalone;
  
		var dlg = new SaveDialog(
		  this,
		  filename,
		  mxUtils.bind(this, function (input, mode, folderId) {
			saveFunction(input.value, mode, input, folderId);
			this.hideDialog();
		  }),
		  allowTab ? null : ["_blank"]
		);
  
		this.showDialog(
		  dlg.container,
		  420,
		  150,
		  true,
		  false,
		  mxUtils.bind(this, function () {
			this.hideDialog();
		  })
		);
		dlg.init();
	  }
	
  };

修改文件Dialogs.js (5206、5032) 文件路径:src/main/webapp/js/diagramly/Dialogs.js

// 新增保存至自定义
	function addStorageEntries() {
		// 保存至自定义
		addStorageEntry("saveToCustomer");
	
		var allowDevice =
		  !Editor.useLocalStorage ||
		  urlParams["storage"] == "device" ||
		  (editorUi.getCurrentFile() != null && urlParams["noDevice"] != "1");
	
	
		// 保存至设备
		if (EditorUi.nativeFileSupport && allowDevice) {
		  addStorageEntry(
			App.MODE_DEVICE,
			null,
			null,
			editorUi.mode == App.MODE_DEVICE ||
			  (disabledModes != null &&
				mxUtils.indexOf(disabledModes, App.MODE_BROWSER) >= 0)
			  ? true
			  : null
		  );
		}
		// 下载保存
		if (allowDevice) {
		  addStorageEntry("download");
		}
		defaultValue = "saveToCustomer";
	  }

// 修改保存选项
// var localServices = ['browser', 'device', 'download', '_blank'];
var localServices = ['saveToCustomer', 'device', 'download'];

六、自定义业务逻辑

我的编辑器每次打开会有两种情况,分为

  • 新增,新增的情况下,url上会带有name参数
  • 编辑,编辑的情况下,url上会带有id、link参数。

6.1 根据url参数加载业务对应的drawio图

新增只需要在保存的时候单独处理,编辑需要根据link参数来解析出drawio图来正确显示

修改文件App.js (3817) 文件路径:src/main/webapp/js/diagramly/App.js

// 新增判断是编辑情况  增加如下代码
//-------------------------------------------------
else if (urlParams["id"] || urlParams["link"]) {
	// 更新图信息
	this.spinner.spin(document.body, mxResources.get("loading"));
	this.getDrawioFileByUrl();
}
// ------------------------------------------------

// 根据drawio文件链接解析
App.prototype.getDrawioFileByUrl = function () {
	const drawioFileUrl = urlParams["link"];
	var fileCreated = mxUtils.bind(this,  (file)=> {
		this.spinner.stop();
		this.fileCreated(file, null, null, null, null, null);
	});
	if (drawioFileUrl) {
		// 从 URL 加载 draw.io 文件
            mxUtils.get(
              drawioFileUrl,
              mxUtils.bind(this, function (req) {
                const xml = req.getText(); // 获取到 .drawio 文件的内容
                fileCreated(
                  new LocalFile(this, xml, this.defaultFilename, this.mode == null),
                  null,
                  null,
                  null,
                  null,
                  null
                )
              }),
              function () {
                this.handleError("drawio 图加载失败");
              }
            );
	} else {
		this.handleError("No drawio file URL received");
	}
};

6.2 自定义业务保存(新增、编辑)

修改文件App.js (6524) 文件路径:src/main/webapp/js/diagramly/App.js

try
{
	this.editor.setStatus('');

	if (this.editor.graph.isEditing())
	{
		this.editor.graph.stopEditing();
	}

	// if (name == file.getTitle())
	// {
	// 	file.save(true, success, error);
	// }
	// else
	// {
	// 	file.saveAs(name, success, error)
	// }

	// -----------------
	// 自定义保存
	if (mode === "saveToCustomer") {
		this.saveCustomerFunction(name, success, error);
	} else {
		if (name == file.getTitle()) {
			file.save(true, success, error);
		} else {
			file.saveAs(name, success, error);
		}
	}

	// -----------------


}
catch (err)
{
	error(err);
}

// 自定义保存逻辑
App.prototype.saveCustomerFunction = function (name, success, error) {
  var upload_success = (url) => {
    if (urlParams["name"] !== undefined) {
      // 创建任务
			// 根据drawio图信息、url上的name字段来调用新增接口保存
      const data = {
        name: decodeURIComponent(urlParams["name"]),
        draw_url: url,
      };
      const API = "api_url_add";
      this.costomerXHR(data, API, success, error);
    } else if (urlParams["id"] !== undefined) {
      // 更新drawio图
			// 根据更新后的drawio图信息、url上的id字段来调用更新接口进行保存
      const data = {
        id: Number(urlParams["id"]),
        draw_url: url,
      };
      const API = "api_url_update";
      this.costomerXHR(data, API, success, error);
    }
    success();
  };
  this.uploadCustomerFileFunction(name, upload_success, error);
};

因为当前需求接口需要的是将画完的图上传后得到的url,所以还需要封装一个上传方法,将图上传。

如果直接需要得到图数据而非上传,可通过this.getFileData(true)获取到图数据后自行处理。

不过需注意this指向问题

修改文件App.js 文件路径:src/main/webapp/js/diagramly/App.js

此处使用的原生的XMLHttpRequest 也可使用fetch、axios等请求

// 新增如下代码,位置任意

// 上传文件
App.prototype.uploadCustomerFileFunction = function (name, success, error) {
	// 创建一个XMLHttpRequest对象
	var xhr = new XMLHttpRequest();
	const url = '上传文件接口'
	// 配置请求
	xhr.open(
		"POST",
		url,
		true
	);
	// 设置请求头
	// xhr.setRequestHeader("Content-Type", "application/json");
	// 获取上传的文件
	var file_data = this.getFileData(true);
	// 创建一个Blob对象,指定MIME类型为text/xml
	const blob = new Blob([file_data], { type: "text/xml" });
	// 使用Blob来“模拟”一个File对象,因为File是Blob的一个子类
	const file = new File([blob], name, { type: "text/xml" });
	// 创建FormData对象并添加文件
	const formData = new FormData();
	formData.append("file", file);
	// 发送请求
	xhr.send(formData);
	// 处理响应
	xhr.onload = () => {
		if (xhr.status === 200) {
			const resp = JSON.parse(xhr.response);
			if (resp.ErrNo === 0) {
				success(resp.Data.filePath);
			} else {
				this.handleError(resp.Msg);
			}
		} else {
			this.handleError("请求失败,状态码:" + xhr.status);
			// 请求失败,处理错误情况
			console.error("请求失败,状态码:" + xhr.status);
		}
	};
	xhr.onerror = () => {
		console.error("网络错误");
		this.handleError("网络错误");
	};
};

还需封装新增与编辑的接口请求方法

修改文件App.js 文件路径:src/main/webapp/js/diagramly/App.js

// 新增如下代码,位置任意

// XHR请求
App.prototype.costomerXHR = function (data, api, success) {
	// 创建一个XMLHttpRequest对象
	var xhr = new XMLHttpRequest();
	// 配置请求
	xhr.open("POST", api, true);
	// 设置请求头
	xhr.setRequestHeader("Content-Type", "application/json");
	// 发送请求
	xhr.send(JSON.stringify(data));
	// 处理响应
	xhr.onload = () => {
		if (xhr.status === 200) {
			const resp = JSON.parse(xhr.response);
			if (resp.ErrNo === 0) {
				success(resp.Msg);

				// 接口请求成功后回调 、部署部分会说到-----
				const url= '1111'			
				window.parent.postMessage(
					"navigate-to-success",
					url
				);
				// -------------------
				
			} else {
				this.handleError(resp.Msg);
			}
		} else {
			this.handleError("请求失败,状态码:" + xhr.status);
			// 请求失败,处理错误情况
			console.error("请求失败,状态码:" + xhr.status);
		}
	};
	xhr.onerror = () => {
		console.error("网络错误");
		this.handleError("网络错误");
	};
};

七、部署

目前drawio的部署有

  • ant打包部署 需要java环境
  • 通过启动node服务的方式部署(npm run dev)
  • 静态部署 即直接部署整套前端代码,以静态访问index.html的方式完成部署

我的需求是在其他平台内内嵌了iframe来实现访问drawio,所以采用了第三种部署方式

具体部署方式,将整个webapp文件夹上传至服务器,通过nginx代理或者后端起服务通过path指定路由访问index.html

父子页面通信

由于使用了iframe,且在画图新增或者更新完成后,需要回调给父页面结果,所以使用postMessage实现父子页面通信

// 处理响应
	xhr.onload = () => {
		if (xhr.status === 200) {
			const resp = JSON.parse(xhr.response);
			if (resp.ErrNo === 0) {
				success(resp.Msg);

				// 成功后向父页面发送消息 通知父页面
				const url= '1111'			
				window.parent.postMessage(
					"navigate-to-success",
					url
				);
				// -------------------
				
			} else {
				this.handleError(resp.Msg);
			}
		} else {
			this.handleError("请求失败,状态码:" + xhr.status);
			// 请求失败,处理错误情况
			console.error("请求失败,状态码:" + xhr.status);
		}
	};
<template>
	// ...
</template>
<script>
	export default {
		data() {
			return {};
		},
		mounted() {
			window.addEventListener('message', this.handleIframeMessage);
		},
		beforeDestroy() {
			window.removeEventListener('message', this.handleIframeMessage);
		},
		methods: {
			onIframeLoad() {
				this.iframeLoaded = true;
			},
			handleIframeMessage(event) {
				// 检查消息来源,避免接收不安全的消息
				if (event.origin !== window.location.origin) {
					return;
				}

				if (event.data === 'navigate-to-success') {
					this.$message.success('成功!');
					// 获取回调后 后续逻辑处理(页面跳转等)
				}
			},
		},
	};
</script>
<style lang="less" scoped></style>

至此就搞完了,笑嘻嘻的交给了领导~

参考文章 blog.csdn.net/MrLiuSixsix…