index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <template>
  2. <div class="sand-flow-container">
  3. <div class="left-wrapper" v-if="!isView">
  4. <tool-bar :graph="graph"/>
  5. </div>
  6. <div class="right-wrapper">
  7. <header class="sand-top">
  8. <ul class="form-ul">
  9. <li style="margin-right:30px;">
  10. 沙盘名称:
  11. <el-input
  12. v-model="sandObj.name"
  13. placeholder="请输入沙盘名称"
  14. clearable
  15. style="width:300px">
  16. </el-input>
  17. </li>
  18. <li v-permission="permissionBtn.sandboxPermission.sandbox_variety">
  19. 品种:
  20. <el-cascader
  21. :options="classifyArr"
  22. :props="classifyProps"
  23. v-model="sandObj.classify"
  24. clearable
  25. placeholder="选择品种"
  26. size="medium"
  27. @change="changeClassify"
  28. />
  29. </li>
  30. </ul>
  31. <div>
  32. <el-button v-permission="permissionBtn.sandboxPermission.sandbox_saveView"
  33. type="primary" size="medium" @click="saveChart(null)" v-if="!isView">保存</el-button>
  34. <el-button v-permission="permissionBtn.sandboxPermission.sandbox_addMy"
  35. type="primary" size="medium" @click="copySandHandle">复制图片</el-button>
  36. </div>
  37. </header>
  38. <div class="flow-wrapper">
  39. <div id="flow-container"></div>
  40. <div class="flow-source">来源:弘则研究</div>
  41. <!-- 右键菜单 -->
  42. <div class="contextMenu-wrapper" id="contextMenu-wrapper" @mouseleave="hideContextMenu">
  43. <el-dropdown-menu size="medium">
  44. <el-dropdown-item v-for="menu in contextMenuOption" :key="menu.key" @click.native="handleContext(menu.key)">
  45. <i :class="menu.icon" v-if="menu.icon"/>
  46. {{menu.label}}
  47. </el-dropdown-item>
  48. </el-dropdown-menu>
  49. </div>
  50. <!-- 提示区 -->
  51. <div class="tip-wrapper" v-if="isShowTip">
  52. <i class="el-icon-d-arrow-left" style="font-size: 60px;"></i>拖拽图形开始绘制
  53. </div>
  54. <!-- 缩略图 -->
  55. <div id="minimap" class="minimap"></div>
  56. </div>
  57. </div>
  58. </div>
  59. </template>
  60. <script>
  61. import { ElDropdownMenu } from 'element-ui';
  62. import { sandInterface,customInterence,dataBaseInterface } from '@/api/api.js';
  63. import { myGraph } from '../common/gragh';
  64. import toolBar from './toolBar';
  65. import { contextMenuOption } from '../common/options';
  66. import { contextEvent } from '../common/events';
  67. import { copyBlob,svgToBase64 } from '@/utils/svgToblob'
  68. export default {
  69. name:'',
  70. components: { toolBar,ElDropdownMenu },
  71. data () {
  72. return {
  73. graph: null,
  74. sand_id: this.$route.query.id || '',
  75. contextMenuOption,
  76. sandObj: {
  77. name: '',
  78. classify: ''
  79. },
  80. editable_id: '',
  81. classifyArr:[],
  82. classifyProps: {
  83. children: 'Items',
  84. label: 'ClassifyName',
  85. value: 'ChartPermissionId'
  86. },
  87. initData: {},
  88. sandInfo:{},
  89. loopTimer: null,
  90. lockLoding: null,
  91. isShowTip: this.$route.query.id ? false : true,
  92. };
  93. },
  94. watch: {
  95. initData(newval) {
  96. console.log(newval)
  97. this.graph.fromJSON(newval);
  98. },
  99. },
  100. computed: {
  101. isView() {
  102. return this.$route.query.type === 'view';
  103. }
  104. },
  105. methods: {
  106. /* 获取品种 */
  107. getClassify() {
  108. customInterence.getvariety({
  109. CompanyType: 'ficc'
  110. }).then(res => {
  111. if(res.Ret !== 200) return
  112. this.classifyArr = res.Data.List;
  113. })
  114. },
  115. /* 获取画布内容 */
  116. getGraphData(SandboxVersionCode,modifyType) {
  117. const { id,type } = this.$route.query;
  118. const sandbox_versioncode = id||SandboxVersionCode
  119. const modify_type = type||modifyType
  120. // type === 'edit' && sandInterface.sandData({
  121. // SandboxId: id
  122. // }).then(res => {
  123. // if(res.Ret !== 200) return
  124. // const { Name,ChartPermissionId,Content } = res.Data;
  125. // this.sandObj = {
  126. // name: Name,
  127. // classify: ChartPermissionId
  128. // }
  129. // this.initData = JSON.parse(Content);
  130. // this.sandInfo = res.Data;
  131. // // 轮询
  132. // this.$route.query.type === 'edit' && this.autoSave();
  133. // })
  134. if(!sandbox_versioncode) return
  135. sandInterface.versionData({
  136. SandboxVersionCode: id||SandboxVersionCode
  137. }).then(res => {
  138. if(res.Ret !== 200) return
  139. const { Name,ChartPermissionId,Content } = res.Data;
  140. this.sandObj = {
  141. name: Name,
  142. classify: ChartPermissionId
  143. }
  144. this.initData = JSON.parse(Content);
  145. this.$nextTick(()=>{
  146. this.graph.centerContent()
  147. })
  148. if(modify_type === 'edit') {
  149. this.sandInfo = res.Data;
  150. // 轮询
  151. this.autoSave();
  152. //this.graph.centerContent();
  153. }
  154. })
  155. },
  156. /* 保存 */
  157. saveChart: _.debounce( function(callback=null) {
  158. if(!this.sandObj.name)
  159. return this.$message.warning(`'请填写沙盘名称`);
  160. if(!this.graph.toJSON().cells.length) return this.$message.warning('请绘制画布内容');
  161. const { name, classify } = this.sandObj;
  162. this.lockLoding = this.$loading({
  163. lock: true,
  164. text: '保存中...',
  165. target: '.right-wrapper',
  166. spinner: 'el-icon-loading',
  167. background: 'rgba(255, 255, 255, 0.8)'
  168. });
  169. const { cells } = this.graph.toJSON();
  170. this.graph.toSVG(async (dataUri) => {
  171. const params = new FormData();
  172. params.append('Img',dataUri)
  173. const { Data } = await dataBaseInterface.uploadImgSvg(params);
  174. //let SandboxVersionCode = (this.sand_id && classify === this.sandInfo.ChartPermissionId) ? this.sand_id : '';
  175. let SandboxVersionCode = this.sand_id
  176. const { Ret , Data : sandData} = await sandInterface.sandSave({
  177. SandboxVersionCode,
  178. Name: name,
  179. ChartPermissionId: Number(classify),
  180. Content: JSON.stringify(this.graph.toJSON()),
  181. PicUrl: Data.ResourceUrl,
  182. SvgData: dataUri
  183. })
  184. if(Ret !== 200) return;
  185. this.$message.success(`${SandboxVersionCode ? '编辑成功' : '保存成功'}`);
  186. this.lockLoding.close();
  187. //!SandboxVersionCode && window.close();
  188. //如果是新增,直接跳转到编辑页面
  189. if(!SandboxVersionCode&&sandData.VersionCode){
  190. this.sand_id = sandData.VersionCode
  191. this.$router.push({
  192. path: '/sandflow',
  193. query: {
  194. id: sandData.VersionCode,
  195. type:'edit',
  196. },
  197. });
  198. this.getGraphData(sandData.VersionCode,'edit');
  199. return
  200. }
  201. callback && callback();
  202. },{
  203. preserveDimensions:true,//让svg为实际图片大小
  204. beforeSerialize:(svg)=>{
  205. const {x,y,width,height} = this.graph.getContentBBox(cells)
  206. let {tx,ty} = this.graph.translate() // 画布偏移量
  207. //给导出的svg增加一点宽高
  208. svg.setAttribute('width',width+50)
  209. svg.setAttribute('height',height+50)
  210. //设置viewBox使图像居中
  211. svg.setAttribute('viewBox',`${x-25} ${y-25} ${width+50} ${height+50}`)
  212. // 在图表右下方 加上"来源:弘则研究"字样
  213. let gNode = svg.getElementsByClassName('x6-graph-svg-viewport')[0]
  214. let textNode = document.createElement('text')
  215. textNode.setAttribute('x',x-tx+width-90)
  216. textNode.setAttribute('y',y-ty+height+22)
  217. textNode.setAttribute('font-size','16px')
  218. textNode.setAttribute('font-style','italic')
  219. textNode.innerText = '来源:弘则研究'
  220. gNode.appendChild(textNode)
  221. },
  222. copyStyles:false,
  223. stylesheet: `
  224. svg{
  225. background-color:white;
  226. }
  227. .x6-port {
  228. visibility: hidden;
  229. }
  230. `
  231. })
  232. },500),
  233. /* 复制图片 */
  234. copySandHandle: _.debounce(function() {
  235. const { cells } = this.graph.toJSON();
  236. if(!cells.length) return this.$message.warning('当前画布无可复制内容');
  237. this.lockLoding = this.$loading({
  238. lock: true,
  239. text: '复制图片中...',
  240. target: '.right-wrapper',
  241. spinner: 'el-icon-loading',
  242. background: 'rgba(255, 255, 255, 0.8)'
  243. });
  244. this.graph.toSVG(async(dataUri) => {
  245. /* const params = new FormData();
  246. params.append('Img',dataUri)
  247. const { Data } = await dataBaseInterface.uploadImgSvg(params);
  248. const copyImg = document.createElement('img');
  249. $('.sand-flow-container')[0].appendChild(copyImg);
  250. copyImg.src = Data.ResourceUrl;
  251. this.getSelect(copyImg);
  252. setTimeout(() => {
  253. document.execCommand('copy');
  254. $('.sand-flow-container')[0].removeChild(copyImg);
  255. this.lockLoding && this.lockLoding.close();
  256. this.$message.success('复制成功');
  257. }); */
  258. const canvas = document.createElement("canvas");
  259. const ctx = canvas.getContext("2d");
  260. const img = new Image();
  261. img.crossOrigin = "Anonymous";
  262. img.src = svgToBase64(dataUri);
  263. img.onload = ()=>{
  264. canvas.width = img.width;
  265. canvas.height = img.height;
  266. console.log('width',img.width)
  267. console.log('height',img.height)
  268. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  269. ctx.fillStyle="#fff";
  270. ctx.fillRect(0, 0, img.width, img.height);
  271. ctx.drawImage(img, 0, 0);
  272. if(window.ClipboardItem) {
  273. canvas.toBlob(async (blob) => {
  274. const data = [new ClipboardItem({ [blob.type]: blob })];
  275. await navigator.clipboard.write(data).then(
  276. () => {
  277. this.$message.success('复制成功!')
  278. },
  279. () => {
  280. this.$message.warning('复制失败,稍后再试')
  281. }
  282. ).finally(()=>{
  283. this.lockLoding && this.lockLoding.close();
  284. });
  285. });
  286. }else {
  287. this.lockLoding && this.lockLoding.close();
  288. this.$message.warning('当前协议暂不支持,仅支持https协议')
  289. }
  290. }
  291. },{
  292. preserveDimensions:true,//让svg为实际图片大小
  293. beforeSerialize:(svg)=>{
  294. const {x,y,width,height} = this.graph.getContentBBox(cells)
  295. let {tx,ty} = this.graph.translate()
  296. //给导出的svg增加一点宽高
  297. svg.setAttribute('width',width+50)
  298. svg.setAttribute('height',height+50)
  299. //设置viewBox使图像居中
  300. svg.setAttribute('viewBox',`${x-25} ${y-25} ${width+50} ${height+50}`)
  301. let gNode = svg.getElementsByClassName('x6-graph-svg-viewport')[0]
  302. let textNode = document.createElement('text')
  303. textNode.setAttribute('x',x-tx+width-90)
  304. textNode.setAttribute('y',y-ty+height+22)
  305. textNode.setAttribute('font-size','16px')
  306. textNode.setAttribute('font-style','italic')
  307. textNode.innerText = '来源:弘则研究'
  308. gNode.appendChild(textNode)
  309. },
  310. copyStyles:false,
  311. stylesheet: `
  312. svg{
  313. background-color:white;
  314. }
  315. .x6-port {
  316. visibility: hidden;
  317. }`
  318. })
  319. },500),
  320. /* 右键事件 */
  321. handleContext(key) {
  322. contextEvent(this.graph, key);
  323. this.hideContextMenu();
  324. },
  325. /* 隐藏右键menu */
  326. hideContextMenu() {
  327. const dom = $('#contextMenu-wrapper')[0];
  328. dom.style.left = '-9999px';
  329. dom.style.top = '-9999px';
  330. },
  331. // 选择
  332. getSelect(targetNode) {
  333. if (window.getSelection) {
  334. //chrome等主流浏览器
  335. var selection = window.getSelection();
  336. var range = document.createRange();
  337. range.selectNode(targetNode);
  338. selection.removeAllRanges();
  339. selection.addRange(range);
  340. } else if (document.body.createTextRange) {
  341. //ie
  342. var range = document.body.createTextRange();
  343. range.moveToElementText(targetNode);
  344. range.select();
  345. }
  346. },
  347. changeClassify(e) {
  348. this.sandObj.classify = e.length ? e[1] : '';
  349. },
  350. /* 编辑页 自动保存 */
  351. autoSave() {
  352. this.loopTimer = setInterval(() => {
  353. if(!this.sandObj.name || !this.sandObj.classify) return;
  354. const { name, classify } = this.sandObj;
  355. sandInterface.draftSave({
  356. SandboxVersionCode: this.sand_id,
  357. Name: name,
  358. ChartPermissionId: classify,
  359. Content: JSON.stringify(this.graph.toJSON())
  360. }).then((res) => {
  361. if(res.Ret === 202) return clearInterval(this.loopTimer);
  362. // if (res.Ret !== 200) return;
  363. });
  364. }, 10000);
  365. },
  366. /* init画布 */
  367. init() {
  368. const graph = new myGraph('flow-container');
  369. //graph.centerContent();
  370. // graph.fromJSON(this.initData);
  371. this.graph = graph;
  372. }
  373. },
  374. beforeRouteLeave(to,from,next) {
  375. // 编辑页 添加页切换路由提示保存
  376. if(this.$route.query.type !== 'view') {
  377. this.$confirm("在离开页面之前,是否保存当前内容?", "保存提示", {
  378. type: "warning"
  379. }).then(() => {
  380. this.saveChart(() => {
  381. this.$route.query.type === 'edit' && sandInterface.mark({ SandboxId:this.sandInfo.SandboxId,Status:2 });
  382. next();
  383. });
  384. }).catch(() => {
  385. this.$route.query.type === 'edit' && sandInterface.mark({ SandboxId:this.sandInfo.SandboxId,Status:2 });
  386. this.$route.query.type === 'edit' && sandInterface.cancelSave({ SandboxId:this.sandInfo.SandboxId });
  387. next();
  388. })
  389. }else {
  390. next();
  391. }
  392. },
  393. mounted() {
  394. this.init();
  395. this.getClassify();
  396. this.$route.query.id && this.getGraphData();
  397. },
  398. destroyed() {
  399. if(this.loopTimer) clearInterval(this.loopTimer);
  400. }
  401. }
  402. </script>
  403. <style lang='scss'>
  404. .sand-flow-container {
  405. display: flex;
  406. height: calc(100vh - 120px);
  407. * { box-sizing: border-box;}
  408. .left-wrapper {
  409. width: 300px;
  410. min-width: 300px;
  411. height: 100%;
  412. margin-right: 20px;
  413. background-color: #fff;
  414. border: 1px solid #ececec;
  415. box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.05);
  416. }
  417. .right-wrapper {
  418. flex: 1;
  419. height: 100%;
  420. border: 1px solid #ececec;
  421. position: relative;
  422. overflow: hidden;
  423. .sand-top {
  424. padding: 10px 20px;
  425. background: #fff;
  426. position: absolute;
  427. top: 0;
  428. left: 0;
  429. right: 0;
  430. display: flex;
  431. border-bottom: 1px solid #ECECEC;
  432. z-index: 1;
  433. .form-ul {
  434. flex: 1;
  435. display: flex;
  436. }
  437. }
  438. .flow-wrapper {
  439. position: relative;
  440. height: 100%;
  441. display: flex;
  442. /* overflow: auto; */
  443. padding-top: 60px;
  444. // padding-bottom:30px;
  445. .flow-source{
  446. position: absolute;
  447. right: 10px;
  448. bottom: 8px;
  449. font-size: 20px;
  450. font-family: PingFang SC-Regular, PingFang SC;
  451. font-weight: 400;
  452. color: #666666;
  453. }
  454. #flow-container {
  455. flex: 1;
  456. }
  457. .x6-graph-scroller {
  458. flex: 1;
  459. }
  460. .x6-port-body {
  461. display: none;
  462. }
  463. /* reseize 框样式 */
  464. .x6-widget-transform {
  465. .x6-widget-transform-resize {
  466. border-radius: 0;
  467. }
  468. }
  469. .contextMenu-wrapper {
  470. position: fixed;
  471. z-index: 99;
  472. top: -9999px;
  473. left: -9999px;
  474. background: #fff;
  475. padding: 10px 0;
  476. /* border: 1px solid #999; */
  477. box-shadow: 0 1px 4px #999;
  478. }
  479. .editable-wrapper {
  480. position: fixed;
  481. top: -999px;
  482. left: -999px;
  483. outline: none;
  484. z-index: 99;
  485. padding: 5px;
  486. }
  487. .tip-wrapper {
  488. position: absolute;
  489. top: 50%;
  490. left: 10%;
  491. z-index: 99;
  492. font-size: 40px;
  493. color: #bbb;
  494. display: flex;
  495. align-items: center;
  496. }
  497. .minimap{
  498. position:absolute;
  499. right:6px;
  500. bottom:6px;
  501. box-sizing: border-box;
  502. .x6-widget-minimap-viewport{
  503. border-color: red;
  504. .x6-widget-minimap-viewport-zoom{
  505. border-color: red;
  506. }
  507. }
  508. .x6-widget-minimap{
  509. width: auto !important;
  510. height: auto !important;
  511. }
  512. }
  513. }
  514. }
  515. }
  516. </style>