index.vue 14 KB

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