View.vue 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384
  1. <template>
  2. <div class="meta2d">
  3. <div class="tools">
  4. <t-tooltip content="新建" placement="bottom">
  5. <a><t-icon name="add" @click="newFile" /></a>
  6. </t-tooltip>
  7. <t-tooltip content="保存" placement="bottom">
  8. <a>
  9. <t-badge dot :showZero="false" :count="dot ? 1 : 0">
  10. <t-icon name="save" @click="save(SaveType.Save)" /></t-badge
  11. ></a>
  12. </t-tooltip>
  13. <t-tooltip content="保存为我的组件" placement="bottom">
  14. <a><t-icon name="layers" @click="save(SaveType.Save, true)" /></a>
  15. </t-tooltip>
  16. <t-tooltip content="格式化(双击可连续使用)" placement="bottom">
  17. <a
  18. @click="oneFormat"
  19. @dblclick="alwaysFormat"
  20. :style="{
  21. color: one || always ? ' #1677ff' : '',
  22. }"
  23. >
  24. <svg
  25. width="1em"
  26. height="1em"
  27. viewBox="0 0 256 256"
  28. xmlns="http://www.w3.org/2000/svg"
  29. xmlns:xlink="http://www.w3.org/1999/xlink"
  30. >
  31. <defs>
  32. <path id="77456364" d="M0 0h256v256H0z"></path>
  33. </defs>
  34. <g fill="none" fill-rule="evenodd">
  35. <mask id="3804093554b" fill="#fff">
  36. <use xlink:href="#77456364"></use>
  37. </mask>
  38. <path
  39. d="M213 77c0 14.36-11.64 26-26 26H69c-14.36 0-26-11.64-26-26V51c0-14.36 11.64-26 26-26h118c14.36 0 26 11.64 26 26v2h9.125c9.83 0 17.82 7.88 17.997 17.67l.003.33v50c0 9.83-7.88 17.82-17.67 17.997l-.33.003h-84.626v18H139c9.83 0 17.82 7.88 17.997 17.67l.003.33v35c0 16.016-12.984 29-29 29-15.856 0-28.74-12.725-28.996-28.52L99 210v-35c0-9.83 7.88-17.82 17.67-17.997L117 157h.499l.001-20c0-9.83 7.88-17.82 17.67-17.997l.33-.003h84.625V73H213Zm-76 100h-18v33a9 9 0 0 0 8.471 8.985l.264.011.265.004a9 9 0 0 0 8.996-8.735L137 210v-33Zm50-132H69a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h118a6 6 0 0 0 6-6V51a6 6 0 0 0-6-6Z"
  40. fill="currentColor"
  41. fill-rule="nonzero"
  42. mask="url(#3804093554b)"
  43. ></path>
  44. </g>
  45. </svg>
  46. </a>
  47. </t-tooltip>
  48. <t-tooltip content="清除格式" placement="bottom">
  49. <a @click="clearFormat">
  50. <svg
  51. width="1em"
  52. height="1em"
  53. viewBox="0 0 1024 1024"
  54. xmlns="http://www.w3.org/2000/svg"
  55. >
  56. <path
  57. d="M889.186 384.07 677.671 172.56c-53.063-53.063-139.094-53.063-192.157 0L134.617 523.457c-53.063 53.063-53.063 139.099 0 192.158l170.196 170.2a41.354 41.354 0 0 0 29.243 12.11h215.001a41.354 41.354 0 0 0 29.184-12.05L889.155 576.26c53.094-53.09 53.094-139.126.031-192.19zM830.7 442.558c20.48 20.472 20.764 53.492.855 74.319l-.961.984-298.618 297.358H351.185l-158.09-158.086c-20.76-20.763-20.76-54.43 0-75.193l350.901-350.897c20.764-20.764 54.43-20.764 75.193 0l211.515 211.511z"
  58. fill="currentColor"
  59. ></path>
  60. <path
  61. d="m685.505 678.754-58.19 58.77-317.587-314.43 58.191-58.774zm197.55 136.508c23.46 0 42.483 18.514 42.483 41.353 0 22.45-18.38 40.724-41.294 41.338l-1.19.016h-454.6c-23.462 0-42.485-18.514-42.485-41.354 0-22.449 18.381-40.723 41.295-41.338l1.19-.015h454.6z"
  62. fill="currentColor"
  63. ></path>
  64. </svg>
  65. </a>
  66. </t-tooltip>
  67. <div class="flex-grow"></div>
  68. <t-tooltip content="直线" placement="bottom">
  69. <a
  70. :draggable="true"
  71. @dragstart="onAddShape($event, 'line')"
  72. @click.stop="onAddShape($event, 'line')"
  73. ><t-icon name="slash"
  74. /></a>
  75. </t-tooltip>
  76. <t-tooltip content="文字" placement="bottom">
  77. <a
  78. :draggable="true"
  79. @dragstart="onAddShape($event, 'text')"
  80. @click.stop="onAddShape($event, 'text')"
  81. >T</a
  82. >
  83. </t-tooltip>
  84. <t-tooltip content="连线(双击可连续使用)" placement="bottom">
  85. <a
  86. @click="oneDraw"
  87. @dblclick="alwaysDraw"
  88. :style="{
  89. color: oneD || alwaysD ? ' #1677ff' : '',
  90. }"
  91. >
  92. <svg
  93. width="1em"
  94. height="1em"
  95. viewBox="0 0 1024 1024"
  96. xmlns="http://www.w3.org/2000/svg"
  97. >
  98. <path
  99. d="M192 64a128 128 0 0 1 123.968 96H384a160 160 0 0 1 159.68 149.504L544 320v384a96 96 0 0 0 86.784 95.552L640 800h68.032a128 128 0 1 1 0 64.064L640 864a160 160 0 0 1-159.68-149.504L480 704V320a96 96 0 0 0-86.784-95.552L384 224l-68.032 0.064A128 128 0 1 1 192 64z m640 704a64 64 0 1 0 0 128 64 64 0 0 0 0-128zM192 128a64 64 0 1 0 0 128 64 64 0 0 0 0-128z"
  100. fill="currentColor"
  101. ></path>
  102. </svg>
  103. </a>
  104. </t-tooltip>
  105. <!-- <t-tooltip content="连线" placement="top"> -->
  106. <t-dropdown
  107. :minColumnWidth="200"
  108. :maxHeight="560"
  109. :delay2="[10, 150]"
  110. overlayClassName="header-dropdown"
  111. >
  112. <a>
  113. <svg class="l-icon" aria-hidden="true">
  114. <use
  115. :xlink:href="
  116. lineTypes.find((item) => item.value === currentLineType)?.icon
  117. "
  118. ></use>
  119. </svg>
  120. </a>
  121. <t-dropdown-menu>
  122. <t-dropdown-item v-for="item in lineTypes">
  123. <div class="flex middle" @click="changeLineType(item.value)">
  124. {{ item.name }} <span class="flex-grow"></span>
  125. <!-- <t-icon v-show="item.value === currentLineType" name="check" /> -->
  126. <svg class="l-icon" aria-hidden="true">
  127. <use :xlink:href="item.icon"></use>
  128. </svg>
  129. </div>
  130. </t-dropdown-item>
  131. </t-dropdown-menu>
  132. </t-dropdown>
  133. <t-dropdown
  134. :minColumnWidth="200"
  135. :maxHeight="560"
  136. :delay2="[10, 150]"
  137. overlayClassName="header-dropdown"
  138. >
  139. <a>
  140. <svg class="l-icon" aria-hidden="true">
  141. <use
  142. :xlink:href="
  143. fromArrows.find((item) => item.value === fromArrow)?.icon
  144. "
  145. ></use>
  146. </svg>
  147. </a>
  148. <t-dropdown-menu>
  149. <t-dropdown-item v-for="item in fromArrows">
  150. <div
  151. class="flex middle"
  152. style="height: 30px"
  153. @click="changeFromArrow(item.value)"
  154. >
  155. <svg class="l-icon" aria-hidden="true">
  156. <use :xlink:href="item.icon"></use>
  157. </svg>
  158. </div>
  159. </t-dropdown-item>
  160. </t-dropdown-menu>
  161. </t-dropdown>
  162. <t-dropdown
  163. :minColumnWidth="200"
  164. :maxHeight="560"
  165. :delay2="[10, 150]"
  166. overlayClassName="header-dropdown"
  167. >
  168. <a>
  169. <svg class="l-icon" aria-hidden="true">
  170. <use
  171. :xlink:href="
  172. toArrows.find((item) => item.value === toArrow)?.icon
  173. "
  174. ></use>
  175. </svg>
  176. </a>
  177. <t-dropdown-menu>
  178. <t-dropdown-item v-for="item in toArrows">
  179. <div
  180. class="flex middle"
  181. style="height: 30px"
  182. @click="changeToArrow(item.value)"
  183. >
  184. <svg class="l-icon" aria-hidden="true">
  185. <use :xlink:href="item.icon"></use>
  186. </svg>
  187. </div>
  188. </t-dropdown-item>
  189. </t-dropdown-menu>
  190. </t-dropdown>
  191. <!-- </t-tooltip> -->
  192. <t-tooltip content="视图大小" placement="bottom">
  193. <div style="line-height: 40px; margin-left: 8px">{{ scale }}%</div>
  194. </t-tooltip>
  195. <t-tooltip content="100%视图" placement="bottom">
  196. <a @click="onScaleView"><t-icon name="refresh" /></a>
  197. </t-tooltip>
  198. <t-tooltip content="窗口大小" placement="bottom">
  199. <a @click="onScaleWindow"><t-icon name="minus-rectangle" /></a>
  200. </t-tooltip>
  201. <t-tooltip content="数据源" placement="bottom">
  202. <a @click="connectShow"><t-icon name="server" /></a>
  203. </t-tooltip>
  204. <div class="flex-grow"></div>
  205. <t-tooltip
  206. :content="isLock === 2 ? '锁定' : isLock === 1 ? '预览' : '编辑'"
  207. placement="bottom"
  208. >
  209. <a>
  210. <!-- <t-icon name="caret-right" /> -->
  211. <svg
  212. v-if="isLock === 1"
  213. class="l-icon"
  214. aria-hidden="true"
  215. @click="onLock"
  216. >
  217. <use xlink:href="#l-lock"></use>
  218. </svg>
  219. <svg
  220. v-else-if="isLock === 2"
  221. class="l-icon"
  222. aria-hidden="true"
  223. @click="onLock"
  224. >
  225. <use xlink:href="#l-wufayidong"></use>
  226. </svg>
  227. <svg v-else class="l-icon" aria-hidden="true" @click="onLock">
  228. <use xlink:href="#l-unlock"></use>
  229. </svg>
  230. </a>
  231. </t-tooltip>
  232. <t-tooltip content="运行(在新页面全屏查看)" placement="bottom">
  233. <a @click="preview"><t-icon name="caret-right" /></a>
  234. </t-tooltip>
  235. <t-tooltip content="手机查看" placement="bottom">
  236. <a><t-icon name="qrcode" /></a>
  237. </t-tooltip>
  238. <t-tooltip content="分享" placement="bottom">
  239. <a><t-icon name="share" /></a>
  240. </t-tooltip>
  241. <t-tooltip content="发布" placement="bottom">
  242. <a><t-icon name="cloud" /></a>
  243. </t-tooltip>
  244. </div>
  245. <div id="meta2d"></div>
  246. <ContextMenu
  247. :style="{
  248. left: contextMenuValue.left,
  249. top: contextMenuValue.top,
  250. }"
  251. v-show="contextMenuVisible"
  252. :type="contextMenuType"
  253. @changeVisible="changeContextMenuVisible"
  254. />
  255. <t-dialog
  256. width="800px"
  257. header="数据源管理"
  258. v-model:visible="connectVisible"
  259. :footer="false"
  260. >
  261. <t-tabs :default-value="1">
  262. <t-tab-panel :value="1" label="通信" :destroy-on-hide="false">
  263. <template #panel>
  264. <div v-show="comShow">
  265. <t-row class="mt-8" justify="end">
  266. <t-space :size="8">
  267. <t-select-input
  268. placeholder="搜索我的数据源"
  269. allow-input
  270. clearable
  271. :popup-visible="popupVisible"
  272. :popup-props="{ overlayInnerStyle: { padding: '6px' } }"
  273. @input-change="onInputChange"
  274. @popup-visible-change="onPopupVisibleChange"
  275. >
  276. <template #panel>
  277. <ul>
  278. <li
  279. style="line-height: 14px; margin: 8px 4px"
  280. v-for="item in comOptions"
  281. :key="item.url"
  282. @click="() => onOptionClick(item)"
  283. >
  284. 名称: {{ item.name }}<br />
  285. 地址: {{ item.url }}
  286. </li>
  287. </ul>
  288. </template>
  289. <template #suffixIcon>
  290. <t-icon name="search"></t-icon
  291. ></template>
  292. </t-select-input>
  293. <t-button style="height: 30px" @click="addCom"
  294. >添加数据源</t-button
  295. >
  296. </t-space>
  297. </t-row>
  298. <t-table
  299. class="mt-8"
  300. row-key="id"
  301. :data="comData"
  302. :columns="comColumns"
  303. >
  304. <template #operation="{ row, rowIndex }">
  305. <t-link theme="primary" hover="color" @click="editCom(row)">
  306. 编辑
  307. </t-link>
  308. <t-divider layout="vertical" />
  309. <t-popconfirm
  310. content="确认删除吗"
  311. @confirm="deleteCom(rowIndex)"
  312. >
  313. <t-link theme="primary" hover="color"> 删除 </t-link>
  314. </t-popconfirm>
  315. </template>
  316. </t-table>
  317. </div>
  318. <div v-show="!comShow">
  319. <t-row class="mt-8">
  320. <t-col :span="4">
  321. <t-button
  322. theme="primary"
  323. variant="text"
  324. ghost
  325. @click="comBack"
  326. >
  327. <template #icon>
  328. <t-icon name="rollback"></t-icon>
  329. </template>
  330. 返回
  331. </t-button>
  332. </t-col>
  333. <t-col :span="4" :offset="4">
  334. <t-button @click="saveForCurrent" theme="primary"
  335. >仅当前页面使用</t-button
  336. >
  337. <t-button @click="saveToServer" theme="primary" class="ml-4"
  338. >保存到服务器</t-button
  339. >
  340. </t-col>
  341. </t-row>
  342. <div class="form-item mt-16">
  343. <label>名称</label>
  344. <t-input v-model="com.name" style="width: 200px" />
  345. </div>
  346. <div class="form-item">
  347. <label>连接类型</label>
  348. <t-select
  349. v-model="com.type"
  350. placeholder="请选择"
  351. style="width: 200px"
  352. :popup-props="{ overlayInnerStyle: { width: '200px' } }"
  353. :disabled="comType === 'edit'"
  354. @change="comTypeChange"
  355. >
  356. <t-option key="mqtt" value="mqtt" class="overlay-options">
  357. </t-option>
  358. <t-option
  359. key="websocket"
  360. value="websocket"
  361. class="overlay-options"
  362. >
  363. </t-option>
  364. <t-option key="http" value="http" class="overlay-options">
  365. </t-option>
  366. </t-select>
  367. </div>
  368. <template v-if="com.type === 'mqtt'">
  369. <div class="form-item">
  370. <label>接口地址</label>
  371. <t-input v-model="com.url" style="width: 200px" />
  372. </div>
  373. <div class="form-item">
  374. <label>Client Id</label>
  375. <t-input
  376. v-model="com.options.clientId"
  377. style="width: 200px"
  378. />
  379. </div>
  380. <div class="form-item">
  381. <label>关闭自动生成</label>
  382. <t-switch v-model="com.options.customClientId" />
  383. </div>
  384. <div class="form-item">
  385. <label>用户名</label>
  386. <t-input
  387. v-model="com.options.username"
  388. style="width: 200px"
  389. />
  390. </div>
  391. <div class="form-item">
  392. <label>密码</label>
  393. <t-input
  394. v-model="com.options.password"
  395. style="width: 200px"
  396. />
  397. </div>
  398. <div class="form-item">
  399. <label>Topics</label>
  400. <t-input v-model="com.topics" style="width: 200px" />
  401. </div>
  402. </template>
  403. <template v-else-if="com.type === 'http'">
  404. <div class="form-item">
  405. <label>请求方式</label>
  406. <t-select
  407. v-model="com.method"
  408. placeholder="请选择"
  409. style="width: 200px"
  410. :popup-props="{ overlayInnerStyle: { width: '200px' } }"
  411. @change="comHttpMethodChange"
  412. >
  413. <t-option key="GET" value="GET"> </t-option>
  414. <t-option key="POST" value="POST"> </t-option>
  415. </t-select>
  416. </div>
  417. <div class="form-item">
  418. <label>请求头</label>
  419. <t-textarea
  420. v-model="com.httpHeaders"
  421. placeholder="请输入请求头"
  422. name="description"
  423. :autosize="{ minRows: 3, maxRows: 5 }"
  424. />
  425. </div>
  426. <div v-if="com.method === 'POST'" class="form-item">
  427. <label>请求体</label>
  428. <t-textarea
  429. v-model="com.body"
  430. placeholder="请输入请求体"
  431. name="description"
  432. :autosize="{ minRows: 3, maxRows: 5 }"
  433. />
  434. </div>
  435. </template>
  436. <template v-else>
  437. <div class="form-item">
  438. <label>protocol</label>
  439. <t-input v-model="com.protocols" style="width: 200px" />
  440. </div>
  441. </template>
  442. </div>
  443. </template>
  444. </t-tab-panel>
  445. <t-tab-panel :value="2" :destroy-on-hide="false">
  446. <template #label
  447. >数据集 <span><label class="vip-label">VIP</label></span></template
  448. >
  449. <template #panel>
  450. <t-row class="mt-8" style="height: 32px; line-height: 32px">
  451. <t-col flex="60px"> 网络接口 </t-col>
  452. <t-col flex="auto">
  453. <t-input class="ml-8" style="width: 200px" />
  454. </t-col>
  455. </t-row>
  456. <t-row class="mt-8">
  457. <t-col flex="60px">自定义</t-col>
  458. <t-col flex="auto">
  459. <t-button
  460. @click="importDataSet"
  461. class="ml-8"
  462. style="height: 30px"
  463. >从Excel导入</t-button
  464. >
  465. <t-button @click="downloadDataSet" variant="text">
  466. <template #icon><t-icon name="download"></t-icon> </template>
  467. 下载示例
  468. </t-button>
  469. </t-col>
  470. </t-row>
  471. <t-table
  472. class="mt-8"
  473. row-key="id"
  474. :data="dsData"
  475. :columns="dsColumns"
  476. >
  477. <template #label="{ row }">
  478. {{ `${row.label}(${row.key})` }}
  479. </template>
  480. <template #operation="{ row }"> </template>
  481. </t-table>
  482. </template>
  483. </t-tab-panel>
  484. </t-tabs>
  485. </t-dialog>
  486. </div>
  487. </template>
  488. <script lang="ts" setup>
  489. import {
  490. Meta2d,
  491. Options,
  492. Pen,
  493. deepClone,
  494. LockState,
  495. PenType,
  496. HoverType,
  497. } from "@meta2d/core";
  498. import { onMounted, onUnmounted, watch, ref, reactive } from "vue";
  499. import { registerBasicDiagram } from "@/services/register";
  500. import { useRouter, useRoute } from "vue-router";
  501. import { useUser } from "@/services/user";
  502. import { getLe5le2d } from "@/services/api";
  503. import { useDot } from "@/services/common";
  504. import {
  505. save,
  506. newFile,
  507. SaveType,
  508. onScaleView,
  509. onScaleWindow,
  510. } from "@/services/common";
  511. import { useSelection, SelectionMode } from "@/services/selections";
  512. import { defaultFormat } from "@/services/defaults";
  513. import { MessagePlugin } from "tdesign-vue-next";
  514. import { localMeta2dDataName } from "@/services/utils";
  515. import localforage from "localforage";
  516. import { checkData, Meta2dBackData } from "@/services/utils";
  517. import { cdn } from "@/services/api";
  518. import dayjs from "dayjs";
  519. import ContextMenu from "./ContextMenu.vue";
  520. import { importExcel, saveAsExcel } from "@/services/excel";
  521. import { it } from "node:test";
  522. const router = useRouter();
  523. const route = useRoute();
  524. const { user, message, getUser, getMessage, signout } = useUser();
  525. const { dot, setDot, getDot } = useDot();
  526. const { select } = useSelection();
  527. const meta2dOptions: Options = {
  528. cdn,
  529. rule: true,
  530. background: "#1e2430",
  531. x: 32,
  532. y: 32,
  533. width: 1920,
  534. height: 1080,
  535. color: "#bdc7db",
  536. disableAnchor: true,
  537. defaultFormat: { ...defaultFormat },
  538. };
  539. onMounted(() => {
  540. meta2d = new Meta2d("meta2d", meta2dOptions);
  541. registerBasicDiagram();
  542. open();
  543. // @ts-ignore
  544. meta2d.on("active", active);
  545. // @ts-ignore
  546. meta2d.on("inactive", inactive);
  547. // @ts-ignore
  548. meta2d.on("scale", scaleListener);
  549. // @ts-ignore
  550. meta2d.on("add", lineAdd);
  551. meta2d.on("opened", openedListener);
  552. meta2d.on("undo", autoSave);
  553. meta2d.on("redo", autoSave);
  554. meta2d.on("add", autoSave);
  555. meta2d.on("delete", autoSave);
  556. meta2d.on("rotatePens", autoSave);
  557. meta2d.on("translatePens", autoSave);
  558. //TODO所有编辑栏所做修改
  559. meta2d.on("components-update-value", autoSave);
  560. // @ts-ignore
  561. meta2d.on("contextmenu", contextmenu);
  562. meta2d.on("click", canvasClick);
  563. meta2d.on("opened", dataSourceManage);
  564. });
  565. const watcher = watch(
  566. () => route.query,
  567. async () => {
  568. open();
  569. }
  570. );
  571. const open = async () => {
  572. if (route.query.id) {
  573. const ret: any = getLe5le2d(route.query.id + "");
  574. ret && meta2d.open(ret);
  575. } else {
  576. meta2d.open();
  577. }
  578. meta2d.store.data.x = meta2d.store.options.x || 0;
  579. meta2d.store.data.y = meta2d.store.options.y || 0;
  580. };
  581. const openedListener = () => {
  582. const {
  583. locked,
  584. scale: canvasScale,
  585. fromArrow: canvasFromArrow,
  586. toArrow: canvasToArrow,
  587. } = meta2d.store.data;
  588. isLock.value = locked || 0;
  589. scale.value = Math.round(canvasScale * 100);
  590. fromArrow.value = canvasFromArrow || "";
  591. toArrow.value = canvasToArrow || "";
  592. };
  593. onUnmounted(() => {
  594. watcher();
  595. if (meta2d) {
  596. // @ts-ignore
  597. meta2d.off("active", active);
  598. // @ts-ignore
  599. meta2d.off("inactive", inactive);
  600. // @ts-ignore
  601. meta2d.off("scale", scaleListener);
  602. // @ts-ignore
  603. meta2d.off("add", lineAdd);
  604. meta2d.on("opened", openedListener);
  605. meta2d.off("undo", autoSave);
  606. meta2d.off("redo", autoSave);
  607. meta2d.off("add", autoSave);
  608. meta2d.off("delete", autoSave);
  609. meta2d.off("rotatePens", autoSave);
  610. meta2d.off("translatePens", autoSave);
  611. meta2d.off("components-update-value", autoSave);
  612. // @ts-ignore
  613. meta2d.off("contextmenu", contextmenu);
  614. meta2d.off("click", canvasClick);
  615. meta2d.off("opened", dataSourceManage);
  616. meta2d.destroy();
  617. }
  618. });
  619. let localSaveTimer: any = 0;
  620. let saveTimer: any = 0;
  621. const autoSave = () => {
  622. setDot(true);
  623. localSaveTimer && clearTimeout(localSaveTimer);
  624. localSaveTimer = setTimeout(() => {
  625. const data: Meta2dBackData = meta2d.data();
  626. let _localMeta2dDataName = data._id
  627. ? localMeta2dDataName + "-" + data._id
  628. : localMeta2dDataName;
  629. (data as any).localSaveAt = dayjs().format();
  630. localforage.setItem(_localMeta2dDataName, JSON.stringify(data));
  631. localSaveTimer = undefined;
  632. }, 3000);
  633. autoSaveServer();
  634. };
  635. //运行 在新标签页查看
  636. function autoSaveServer() {
  637. //会员享受自动保存
  638. if (saveTimer) {
  639. return;
  640. }
  641. saveTimer = setTimeout(() => {
  642. const data: Meta2dBackData = meta2d.data();
  643. if (
  644. user &&
  645. user.id &&
  646. user.vipExpired &&
  647. data._id &&
  648. !data.component &&
  649. data.owner &&
  650. data.owner?.id === user.id
  651. ) {
  652. save(SaveType.Save);
  653. }
  654. saveTimer = null;
  655. }, 60000);
  656. }
  657. const inactive = () => {
  658. select();
  659. };
  660. const active = (pens: Pen[]) => {
  661. select(pens);
  662. //格式刷处理
  663. if (one.value || always.value) {
  664. meta2d.formatPainter();
  665. one.value = false;
  666. }
  667. };
  668. const one = ref(false);
  669. const always = ref(false);
  670. const oneFormat = () => {
  671. if (one.value) {
  672. one.value = false;
  673. } else {
  674. one.value = true;
  675. meta2d.setFormatPainter();
  676. }
  677. if (always.value) {
  678. always.value = false;
  679. one.value = false;
  680. }
  681. };
  682. const alwaysFormat = () => {
  683. always.value = true;
  684. };
  685. const clearFormat = () => {
  686. always.value = false;
  687. one.value = false;
  688. meta2d.clearFormatPainter();
  689. };
  690. const scale = ref(100);
  691. function scaleListener(newScale: number) {
  692. scale.value = Math.round(newScale * 100);
  693. }
  694. const connectVisible = ref(false);
  695. const connectShow = () => {
  696. connectVisible.value = true;
  697. };
  698. const currentLineType = ref("curve");
  699. const lineTypes = reactive([
  700. { name: "曲线", icon: "#l-curve2", value: "curve" },
  701. { name: "线段", icon: "#l-polyline", value: "polyline" },
  702. { name: "直线", icon: "#l-line", value: "line" },
  703. { name: "脑图曲线", icon: "#l-mind", value: "mind" },
  704. ]);
  705. const changeLineType = (value: string) => {
  706. currentLineType.value = value;
  707. if (meta2d) {
  708. meta2d.store.options.drawingLineName = value;
  709. meta2d.canvas.drawingLineName && (meta2d.canvas.drawingLineName = value);
  710. meta2d.store.active?.forEach((pen) => {
  711. meta2d.updateLineType(pen, value);
  712. });
  713. }
  714. };
  715. const fromArrow = ref("");
  716. const fromArrows = [
  717. { icon: "#l-line", value: "" },
  718. { icon: "#l-from-triangle", value: "triangle" },
  719. { icon: "#l-from-diamond", value: "diamond" },
  720. { icon: "#l-from-circle", value: "circle" },
  721. { icon: "#l-from-lineDown", value: "lineDown" },
  722. { icon: "#l-from-lineUp", value: "lineUp" },
  723. { icon: "#l-from-triangleSolid", value: "triangleSolid" },
  724. { icon: "#l-from-diamondSolid", value: "diamondSolid" },
  725. { icon: "#l-from-circleSolid", value: "circleSolid" },
  726. { icon: "#l-from-line", value: "line" },
  727. ];
  728. const toArrow = ref("");
  729. const toArrows = [
  730. { icon: "#l-line", value: "" },
  731. { icon: "#l-to-triangle", value: "triangle" },
  732. { icon: "#l-to-diamond", value: "diamond" },
  733. { icon: "#l-to-circle", value: "circle" },
  734. { icon: "#l-to-lineDown", value: "lineDown" },
  735. { icon: "#l-to-lineUp", value: "lineUp" },
  736. { icon: "#l-to-triangleSolid", value: "triangleSolid" },
  737. { icon: "#l-to-diamondSolid", value: "diamondSolid" },
  738. { icon: "#l-to-circleSolid", value: "circleSolid" },
  739. { icon: "#l-to-line", value: "line" },
  740. ];
  741. const changeFromArrow = (value: string) => {
  742. fromArrow.value = value;
  743. // 画布默认值
  744. meta2d.store.data.fromArrow = value;
  745. // 活动层的箭头都变化
  746. if (meta2d.store.active) {
  747. meta2d.store.active.forEach((pen: Pen) => {
  748. if (pen.type === PenType.Line) {
  749. pen.fromArrow = value;
  750. meta2d.setValue(
  751. {
  752. id: pen.id,
  753. fromArrow: pen.fromArrow,
  754. },
  755. {
  756. render: false,
  757. }
  758. );
  759. }
  760. });
  761. meta2d.render();
  762. }
  763. };
  764. const changeToArrow = (value: string) => {
  765. toArrow.value = value;
  766. // 画布默认值
  767. meta2d.store.data.toArrow = value;
  768. // 活动层的箭头都变化
  769. if (meta2d.store.active) {
  770. meta2d.store.active.forEach((pen: Pen) => {
  771. if (pen.type === PenType.Line) {
  772. pen.toArrow = value;
  773. meta2d.setValue(
  774. {
  775. id: pen.id,
  776. toArrow: pen.toArrow,
  777. },
  778. {
  779. render: false,
  780. }
  781. );
  782. }
  783. });
  784. meta2d.render();
  785. }
  786. };
  787. const oneD = ref<boolean>(false);
  788. const alwaysD = ref<boolean>(false);
  789. const oneDraw = () => {
  790. if (oneD.value) {
  791. oneD.value = false;
  792. if (!alwaysD.value) {
  793. meta2d.finishDrawLine();
  794. meta2d.drawLine();
  795. meta2d.store.options.disableAnchor = true;
  796. }
  797. } else {
  798. oneD.value = true;
  799. meta2d.drawLine(meta2d.store.options.drawingLineName);
  800. meta2d.store.options.disableAnchor = false;
  801. }
  802. if (alwaysD.value) {
  803. meta2d.finishDrawLine();
  804. meta2d.drawLine();
  805. oneD.value = false;
  806. alwaysD.value = false;
  807. meta2d.store.options.disableAnchor = true;
  808. }
  809. };
  810. const alwaysDraw = () => {
  811. alwaysD.value = true;
  812. meta2d.drawLine(meta2d.store.options.drawingLineName);
  813. meta2d.store.options.disableAnchor = false;
  814. };
  815. const lineAdd = (pens: Pen[]) => {
  816. if (pens.length === 1 && pens[0].name === "line") {
  817. //连线类型
  818. if (oneD.value && !alwaysD.value) {
  819. if (meta2d.canvas.drawingLineName) {
  820. oneD.value = false;
  821. setTimeout(() => {
  822. meta2d.finishDrawLine();
  823. meta2d.drawLine();
  824. meta2d.store.options.disableAnchor = true;
  825. }, 100);
  826. }
  827. }
  828. }
  829. };
  830. const onAddShape = (event: DragEvent | MouseEvent, name: string) => {
  831. let data: any;
  832. if (name === "text") {
  833. data = {
  834. text: "text",
  835. width: 100,
  836. height: 20,
  837. name: "text",
  838. };
  839. } else if (name === "line") {
  840. data = {
  841. anchors: [
  842. { id: "0", x: 0, y: 0.5 },
  843. { id: "1", x: 1, y: 0.5 },
  844. ],
  845. width: 100,
  846. height: 1,
  847. name: "line",
  848. lineName: "line",
  849. type: 1,
  850. };
  851. }
  852. if (!(event as DragEvent).dataTransfer) {
  853. meta2d.canvas.addCaches = deepClone([data]);
  854. } else {
  855. (event as DragEvent).dataTransfer?.setData("Meta2d", JSON.stringify(data));
  856. }
  857. event.stopPropagation();
  858. };
  859. const isLock = ref(0);
  860. function onLock() {
  861. !isLock.value && (isLock.value = 0);
  862. if (isLock.value === LockState.DisableMove) {
  863. isLock.value = LockState.None;
  864. } else {
  865. isLock.value++;
  866. }
  867. meta2d.lock(isLock.value);
  868. meta2d.hideInput();
  869. }
  870. const preview = async () => {
  871. meta2d.stopAnimate();
  872. // @ts-ignore
  873. const data: Meta2dBackData = meta2d.data();
  874. checkData(data);
  875. if (dot && user && data._id) {
  876. // 有 id ,是修改后保存
  877. await save(SaveType.Save);
  878. }
  879. if (!data._id) {
  880. await localforage.setItem(localMeta2dDataName, JSON.stringify(data));
  881. }
  882. router.push({
  883. path: "/preview",
  884. query: {
  885. r: Date.now() + "",
  886. id: data._id,
  887. },
  888. });
  889. };
  890. const contextMenuVisible = ref(false);
  891. const contextMenuValue = ref({
  892. left: "0px",
  893. top: "0px",
  894. });
  895. const contextMenuType = ref("");
  896. const contextmenu = ({ e, rect }: { e: any; rect: any }) => {
  897. contextMenuType.value = "";
  898. contextMenuValue.value.left = e.clientX + "px";
  899. contextMenuValue.value.top = e.clientY + "px";
  900. if (
  901. meta2d.store.hoverAnchor &&
  902. meta2d.canvas.hoverType === HoverType.LineAnchor
  903. ) {
  904. contextMenuType.value = "anchor";
  905. } else {
  906. //其他右键菜单
  907. }
  908. if (!contextMenuVisible.value && contextMenuType.value) {
  909. contextMenuVisible.value = true;
  910. }
  911. if (!contextMenuType.value) {
  912. contextMenuVisible.value = false;
  913. }
  914. };
  915. const canvasClick = () => {
  916. contextMenuVisible.value = false;
  917. };
  918. const changeContextMenuVisible = (e: boolean) => {
  919. contextMenuVisible.value = e;
  920. };
  921. // const comConfirm = () => {
  922. // connectVisible.value = false;
  923. // };
  924. interface Com {
  925. name?: string;
  926. type: "mqtt" | "websocket" | "http";
  927. url?: string;
  928. //websocket
  929. protocols?: string;
  930. //mqtt
  931. topics?: string;
  932. options?: {
  933. clientId?: string;
  934. username?: string;
  935. password?: string;
  936. customClientId?: boolean;
  937. };
  938. //http
  939. http?: string; // http 请求 Url
  940. httpTimeInterval?: number; // http 请求间隔
  941. httpHeaders?: HeadersInit; //请求头
  942. method?: string;
  943. body?: BodyInit | null;
  944. }
  945. const comData = ref<Com[]>([
  946. {
  947. name: "连接1",
  948. type: "mqtt",
  949. url: "wss://1",
  950. topics: "",
  951. options: {
  952. clientId: "",
  953. username: "",
  954. password: "",
  955. customClientId: false,
  956. },
  957. },
  958. {
  959. name: "连接2",
  960. type: "websocket",
  961. url: "wss://2",
  962. },
  963. {
  964. name: "连接3",
  965. type: "http",
  966. url: "wss://3",
  967. },
  968. ]);
  969. const comColumns = ref([
  970. {
  971. colKey: "name",
  972. title: "名称",
  973. ellipsis: true,
  974. },
  975. {
  976. colKey: "type",
  977. title: "类型",
  978. ellipsis: true,
  979. },
  980. {
  981. colKey: "url",
  982. title: "接口地址",
  983. ellipsis: true,
  984. },
  985. { colKey: "operation", title: "操作", width: 120, foot: "-" },
  986. ]);
  987. const comOptions = ref<Com[]>([
  988. {
  989. name: "连接A",
  990. type: "mqtt",
  991. url: "ws://A",
  992. },
  993. {
  994. name: "连接B",
  995. type: "mqtt",
  996. url: "ws://B",
  997. },
  998. {
  999. name: "连接C",
  1000. type: "mqtt",
  1001. url: "ws://C",
  1002. },
  1003. {
  1004. name: "连接D",
  1005. type: "mqtt",
  1006. url: "ws://D",
  1007. },
  1008. {
  1009. name: "连接E",
  1010. type: "mqtt",
  1011. url: "ws://E",
  1012. },
  1013. ]);
  1014. const dataSourceManage = () => {
  1015. comData.value = (meta2d.store.data as any).comData; //确定字段
  1016. dsData.value = (meta2d.store.data as any).dsData;
  1017. };
  1018. const onOptionClick = (item: Com) => {
  1019. comData.value.push(item);
  1020. popupVisible.value = false;
  1021. };
  1022. const popupVisible = ref(false);
  1023. const onInputChange = (e) => {
  1024. console.log("change", e);
  1025. };
  1026. const onPopupVisibleChange = (val: boolean) => {
  1027. popupVisible.value = val;
  1028. };
  1029. const comType = ref("add"); //or edit
  1030. const addCom = () => {
  1031. comType.value = "add";
  1032. com.value = {
  1033. name: "",
  1034. type: "mqtt", //默认
  1035. url: "",
  1036. options: {
  1037. clientId: "",
  1038. username: "",
  1039. password: "",
  1040. customClientId: false,
  1041. },
  1042. };
  1043. comShow.value = false;
  1044. };
  1045. const editCom = (row: Com) => {
  1046. comType.value = "edit";
  1047. com.value = row;
  1048. comShow.value = false;
  1049. };
  1050. const deleteCom = (index: number) => {
  1051. comData.value.splice(index, 1);
  1052. dataToCom(comData.value);
  1053. };
  1054. //将配置的通信列表转成核心库能识别的格式
  1055. const dataToCom = (data: Com[]) => {
  1056. const mqtts: any = [];
  1057. const https: any = [];
  1058. const websockets: any = [];
  1059. data.forEach((item) => {
  1060. if (item.type === "http") {
  1061. https.push({
  1062. http: item.http,
  1063. httpTimeInterval: item.httpTimeInterval,
  1064. httpHeaders: item.httpHeaders,
  1065. method: item.method,
  1066. body: item.body,
  1067. });
  1068. } else if (item.type === "mqtt") {
  1069. mqtts.push({
  1070. url: item.url,
  1071. options: {
  1072. clientId: item.options?.clientId,
  1073. username: item.options?.username,
  1074. password: item.options?.password,
  1075. customClientId: item.options?.customClientId,
  1076. },
  1077. topics: item.topics,
  1078. });
  1079. } else if (item.type === "websocket") {
  1080. websockets.push({
  1081. url: item.url,
  1082. protocols: item.protocols,
  1083. });
  1084. }
  1085. });
  1086. meta2d.store.data.mqtts = mqtts;
  1087. meta2d.store.data.https = https;
  1088. meta2d.store.data.websockets = websockets;
  1089. //TODO 建立通信连接
  1090. meta2d.connectSocket();
  1091. };
  1092. const comShow = ref(true);
  1093. const comBack = () => {
  1094. comShow.value = true;
  1095. };
  1096. const com = ref<Com>({
  1097. name: "连接E",
  1098. type: "mqtt",
  1099. url: "ws://E",
  1100. options: {
  1101. clientId: "",
  1102. username: "",
  1103. password: "",
  1104. customClientId: false,
  1105. },
  1106. });
  1107. const comTypeChange = (e: string) => {
  1108. console.log(e, com.value.type);
  1109. if (e === "mqtt") {
  1110. Object.assign(com.value, {
  1111. options: {
  1112. clientId: "",
  1113. username: "",
  1114. password: "",
  1115. customClientId: false,
  1116. },
  1117. });
  1118. } else if (e === "websocket") {
  1119. Object.assign(com.value, {
  1120. protocols: "",
  1121. });
  1122. } else {
  1123. Object.assign(com.value, {
  1124. http: "",
  1125. httpTimeInterval: 1000,
  1126. httpHeaders: "",
  1127. method: "GET",
  1128. body: "",
  1129. });
  1130. }
  1131. };
  1132. const comHttpMethodChange = (e: string) => {
  1133. if (e === "GET") {
  1134. com.value.body = null;
  1135. }
  1136. };
  1137. const dsData = ref([
  1138. {
  1139. label: "数据点1",
  1140. key: "a-1",
  1141. value: "12",
  1142. type: "number/select/bool/color",
  1143. },
  1144. {
  1145. label: "数据点2",
  1146. key: "a-2",
  1147. value: "12",
  1148. type: "number/select/bool/color",
  1149. },
  1150. {
  1151. label: "数据点3",
  1152. key: "a-3",
  1153. value: "12",
  1154. type: "number/select/bool/color",
  1155. },
  1156. ]);
  1157. const dsColumns = ref([
  1158. {
  1159. colKey: "label",
  1160. title: "名称(数据点ID)",
  1161. ellipsis: true,
  1162. },
  1163. {
  1164. colKey: "type",
  1165. title: "类型",
  1166. ellipsis: true,
  1167. },
  1168. {
  1169. colKey: "value",
  1170. title: "值",
  1171. ellipsis: true,
  1172. },
  1173. ]);
  1174. const saveForCurrent = () => {
  1175. comData.value.push(com.value);
  1176. comShow.value = true;
  1177. dataToCom(comData.value);
  1178. };
  1179. const saveToServer = () => {
  1180. //TODO 将com保存到服务器
  1181. comData.value.push(com.value);
  1182. comShow.value = true;
  1183. dataToCom(comData.value);
  1184. };
  1185. const importDataSet = async () => {
  1186. let columns: any = [
  1187. {
  1188. header: "名称",
  1189. key: "label",
  1190. },
  1191. {
  1192. header: "数据ID",
  1193. key: "key",
  1194. },
  1195. {
  1196. header: "类型",
  1197. key: "type",
  1198. },
  1199. {
  1200. header: "值",
  1201. key: "value",
  1202. },
  1203. ];
  1204. // dsData.value = [];
  1205. let data = await importExcel(columns);
  1206. console.log("data", data);
  1207. // setTimeout(() => {
  1208. dsData.value = data;
  1209. // }, 2000);
  1210. };
  1211. const downloadDataSet = () => {
  1212. let name = "数据集示例";
  1213. let columns = [
  1214. {
  1215. header: "名称",
  1216. key: "label",
  1217. },
  1218. {
  1219. header: "数据ID",
  1220. key: "id",
  1221. },
  1222. {
  1223. header: "类型",
  1224. key: "type",
  1225. },
  1226. {
  1227. header: "值",
  1228. key: "value",
  1229. },
  1230. ];
  1231. let data = [
  1232. ["数据集1", "dataId-1", "string", "zzz"],
  1233. ["数据集2", "dataId-2", "boolean", true],
  1234. ["数据集3", "dataId-3", "number", 12],
  1235. ];
  1236. saveAsExcel(name, columns, data);
  1237. };
  1238. </script>
  1239. <style lang="postcss" scoped>
  1240. .meta2d {
  1241. display: flex;
  1242. flex-direction: column;
  1243. background-color: var(--color-background-editor);
  1244. border-left: 1px solid var(--color-border);
  1245. .tools {
  1246. display: flex;
  1247. font-size: 12px;
  1248. background-color: var(--color-background);
  1249. height: 40px;
  1250. flex-shrink: 0;
  1251. padding: 0 12px;
  1252. a {
  1253. display: flex;
  1254. align-items: center;
  1255. height: 100%;
  1256. padding: 0 10px;
  1257. color: var(--color);
  1258. text-decoration: none;
  1259. .l-icon {
  1260. width: 16px;
  1261. height: 16px;
  1262. }
  1263. &:hover {
  1264. color: var(--color-primary);
  1265. }
  1266. }
  1267. .t-icon {
  1268. font-size: 16px;
  1269. }
  1270. }
  1271. #meta2d {
  1272. border-top: 1px solid var(--color-background-input);
  1273. height: calc(100vh - 81px);
  1274. :deep(.meta2d-map) {
  1275. background: var(--color-background);
  1276. }
  1277. }
  1278. }
  1279. </style>
  1280. <style lang="postcss">
  1281. .t-dialog {
  1282. .t-dialog__body {
  1283. height: 450px;
  1284. /* overflow: auto; */
  1285. }
  1286. .form-item {
  1287. display: flex;
  1288. margin-bottom: 8px;
  1289. label {
  1290. width: 100px;
  1291. }
  1292. }
  1293. .t-textarea {
  1294. width: calc(100% - 150px);
  1295. }
  1296. .vip-label {
  1297. font-size: 10px;
  1298. background-color: #ff400030;
  1299. color: var(--color-bland);
  1300. padding: 0 6px;
  1301. margin-left: 4px;
  1302. border-radius: 2px;
  1303. }
  1304. .t-tab-panel {
  1305. overflow: auto;
  1306. height: 395px;
  1307. }
  1308. }
  1309. </style>