ElementTree.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <div class="elements">
  3. <div class="title" style="margin: 8px 0 0 12px">视图结构</div>
  4. <t-tree
  5. class="flex-grow"
  6. ref="tree"
  7. :data="data.tree"
  8. v-model:actived="data.actived"
  9. activable
  10. :expand-parent="true"
  11. style="padding: 0 4px 8px 8px"
  12. >
  13. <template #label="{ node }: any">
  14. <div class="flex middle" :class="{ gray: node.data.visible === false }">
  15. <template v-if="node.getChildren()">
  16. <t-icon v-if="node.expanded" name="folder-open" />
  17. <t-icon v-else name="folder" />
  18. </template>
  19. <t-icon v-else name="control-platform" />
  20. <t-input
  21. v-if="node.data.edited"
  22. v-model="node.data.label"
  23. :autofocus="true"
  24. @blur="onDescription(node)"
  25. @enter="onDescription(node)"
  26. />
  27. <span
  28. v-else
  29. @click="onActive(node.value)"
  30. @dblclick="node.data.edited = true"
  31. >
  32. {{ node.label }}
  33. </span>
  34. </div>
  35. </template>
  36. <template #operations="{ node }: any">
  37. <div
  38. class="flex middle operations"
  39. :class="{
  40. gray: node.data.visible === false,
  41. show: node.data.visible === false || node.data.locked,
  42. }"
  43. style="width: 36px; height: 16px"
  44. >
  45. <t-tag theme="primary" variant="light-outline">
  46. {{ node.data.tag }}</t-tag
  47. >
  48. <t-tooltip
  49. class="mr-4"
  50. v-if="!node.data.locked"
  51. content="可编辑"
  52. placement="top"
  53. >
  54. <svg class="l-icon" aria-hidden="true" @click="lock(node, 1)">
  55. <use xlink:href="#l-unlock"></use>
  56. </svg>
  57. </t-tooltip>
  58. <t-tooltip
  59. class="mr-4"
  60. v-else-if="node.data.locked == 1"
  61. content="禁止编辑"
  62. placement="top"
  63. >
  64. <svg class="l-icon" aria-hidden="true" @click="lock(node, 2)">
  65. <use xlink:href="#l-lock"></use>
  66. </svg>
  67. </t-tooltip>
  68. <t-tooltip
  69. class="mr-4"
  70. v-else-if="node.data.locked == 2"
  71. content="禁止编辑和移动"
  72. placement="top"
  73. >
  74. <svg class="l-icon" aria-hidden="true" @click="lock(node, 10)">
  75. <use xlink:href="#l-wufayidong"></use>
  76. </svg>
  77. </t-tooltip>
  78. <t-tooltip
  79. class="mr-4"
  80. v-else-if="node.data.locked == 10"
  81. content="禁止所有事件"
  82. placement="top"
  83. >
  84. <svg class="l-icon" aria-hidden="true" @click="lock(node, 0)">
  85. <use xlink:href="#l-jinyong"></use>
  86. </svg>
  87. </t-tooltip>
  88. <t-icon
  89. v-if="node.data.visible !== false"
  90. name="browse"
  91. @click="visible(node, false)"
  92. />
  93. <t-icon v-else name="browse-off" @click="visible(node, true)" />
  94. </div>
  95. </template>
  96. </t-tree>
  97. <div class="groups-panel" style="padding: 8px 0">
  98. <div class="flex middle between" style="padding: 0 12px">
  99. <div class="title">分组</div>
  100. <a @click="addGroup"> +新建分组</a>
  101. </div>
  102. <div class="groups">
  103. <div
  104. v-for="(item, i) in data.groups"
  105. class="flex middle between hover"
  106. :class="{ primary: i == data.activedGroup }"
  107. >
  108. <span
  109. class="flex-grow"
  110. v-if="i != data.editedGroup"
  111. @click="activeGroup(i)"
  112. @dblclick="
  113. data.activedGroup = data.editedGroup = i;
  114. data.group = item;
  115. "
  116. >
  117. {{ item }}
  118. </span>
  119. <t-input
  120. v-else
  121. v-model="data.group"
  122. :autofocus="true"
  123. @blur="setGroup"
  124. @enter="setGroup"
  125. />
  126. <t-icon
  127. v-if="!data.hiddenGroups.includes(item)"
  128. name="browse"
  129. @click="visibleGroup(item, false)"
  130. />
  131. <t-icon v-else name="browse-off" @click="visibleGroup(item, true)" />
  132. <t-popconfirm
  133. content="确认删除该分组吗?"
  134. @confirm="delGroup"
  135. @cancel="data.deleteGroup = undefined"
  136. >
  137. <t-icon
  138. name="delete"
  139. class="ml-8"
  140. :class="{ block: i == data.deleteGroup }"
  141. @click="data.deleteGroup = i"
  142. />
  143. </t-popconfirm>
  144. </div>
  145. </div>
  146. </div>
  147. </div>
  148. </template>
  149. <script lang="ts" setup>
  150. import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue';
  151. import { MessagePlugin } from 'tdesign-vue-next';
  152. import { LockState, Pen } from '@meta2d/core';
  153. import { getPenTree, inTreePanel, setChildrenVisible } from '@/services/common';
  154. const tree = ref<any>(null);
  155. const data = reactive<any>({
  156. tree: [],
  157. actived: [],
  158. groups: [],
  159. hiddenGroups: [],
  160. });
  161. onBeforeMount(() => {
  162. meta2d.on('opened', getTree);
  163. meta2d.on('add', getTree);
  164. meta2d.on('undo', getTree);
  165. meta2d.on('redo', getTree);
  166. meta2d.on('delete', getTree);
  167. meta2d.on('combine', getTree);
  168. meta2d.on('click', getActived);
  169. meta2d.on('paste', getActived);
  170. if (inTreePanel.timer) {
  171. clearTimeout(inTreePanel.timer);
  172. inTreePanel.timer = undefined;
  173. }
  174. inTreePanel.value = true;
  175. getTree();
  176. getActived();
  177. const d = meta2d.store.data as any;
  178. if (!d.groups) {
  179. d.groups = [];
  180. }
  181. data.groups = d.groups;
  182. getHiddenGroups();
  183. });
  184. const getTree = () => {
  185. data.tree = getPenTree();
  186. };
  187. const getHiddenGroups = () => {
  188. data.groups.forEach((item) => {
  189. if (
  190. meta2d.store.data.pens.some(
  191. (pen) =>
  192. !pen.parentId && pen.tags?.includes(item) && pen.visible === false
  193. )
  194. ) {
  195. data.hiddenGroups.push(item);
  196. }
  197. });
  198. };
  199. const getActived = () => {
  200. data.actived = [];
  201. if (meta2d.store.active) {
  202. for (const pen of meta2d.store.active) {
  203. data.actived.push(pen.id);
  204. }
  205. const element = document.body.querySelector(
  206. `[data-value="${data.actived[0]}"]`
  207. );
  208. if (element) {
  209. element.scrollIntoView({ block: 'center' });
  210. } else {
  211. setTimeout(() => {
  212. const element = document.body.querySelector(
  213. `[data-value="${data.actived[0]}"]`
  214. );
  215. element && element.scrollIntoView({ block: 'center' });
  216. }, 500);
  217. }
  218. }
  219. };
  220. const calcElem = (node: Pen) => {
  221. if (!node) {
  222. return;
  223. }
  224. const elem: any = {
  225. label: (node as any).description || node.name,
  226. value: node.id,
  227. locked: node.locked,
  228. visible: node.visible,
  229. tag: (node as any).tag,
  230. };
  231. if (!node.children) {
  232. return elem;
  233. }
  234. elem.children = [];
  235. for (const id of node.children) {
  236. const child = calcElem(meta2d.store.pens[id]);
  237. child && elem.children.push(child);
  238. }
  239. return elem;
  240. };
  241. const onActive = (value: any) => {
  242. if (!value) {
  243. return;
  244. }
  245. const pens: Pen[] = [];
  246. for (const item of meta2d.store.data.pens) {
  247. // for (const v of value) {
  248. if (item.id === value) {
  249. pens.push(item);
  250. }
  251. // }
  252. }
  253. meta2d.active(pens, true);
  254. meta2d.gotoView(pens[0]);
  255. meta2d.resize();
  256. meta2d.render();
  257. };
  258. const lock = (node: any, v: LockState) => {
  259. node.data.locked = v;
  260. meta2d.setValue({
  261. id: node.value,
  262. locked: v,
  263. });
  264. };
  265. const visible = (node: any, v: boolean) => {
  266. node.data.visible = v;
  267. setChildrenVisible(node, v);
  268. const pen = meta2d.findOne(node.value);
  269. pen && meta2d.setVisible(pen, v);
  270. };
  271. const visibleGroup = (item, v: boolean) => {
  272. if (v) {
  273. let index = data.hiddenGroups.indexOf(item);
  274. if (index !== -1) {
  275. data.hiddenGroups.splice(index, 1);
  276. }
  277. } else {
  278. data.hiddenGroups.push(item);
  279. }
  280. let pens = meta2d.store.data.pens.filter(
  281. (pen) => !pen.parentId && pen.tags?.includes(item)
  282. );
  283. pens.forEach((pen) => {
  284. meta2d.setValue(
  285. { id: pen.id, visible: v },
  286. { render: false, doEvent: false }
  287. );
  288. });
  289. meta2d.render();
  290. };
  291. const onDescription = (node: any) => {
  292. node.data.edited = false;
  293. node.setData({ label: node.data.label });
  294. meta2d.setValue({
  295. id: node.value,
  296. description: node.data.label,
  297. });
  298. };
  299. const addGroup = () => {
  300. const i = data.groups.length + 1;
  301. data.group = '组' + i;
  302. data.groups.push(data.group);
  303. data.activedGroup = data.editedGroup = i;
  304. };
  305. const activeGroup = (i: number) => {
  306. data.activedGroup = i;
  307. const group = data.groups[i];
  308. const pens: Pen[] = [];
  309. for (const item of meta2d.store.data.pens) {
  310. if (item.tags?.includes(group)) {
  311. pens.push(item);
  312. }
  313. }
  314. meta2d.active(pens, false);
  315. meta2d.render();
  316. };
  317. const setGroup = () => {
  318. if (data.groups[data.editedGroup] === data.group) {
  319. data.editedGroup = undefined;
  320. return;
  321. }
  322. if (data.groups.includes(data.group)) {
  323. MessagePlugin.error('已经存在相同分组!');
  324. return;
  325. }
  326. for (const item of meta2d.store.data.pens) {
  327. // @ts-ignore
  328. if (item.group === data.groups[data.editedGroup]) {
  329. // @ts-ignore
  330. item.group === data.group;
  331. }
  332. }
  333. data.groups[data.editedGroup] = data.group;
  334. data.editedGroup = undefined;
  335. };
  336. const delGroup = () => {
  337. for (const item of meta2d.store.data.pens) {
  338. // @ts-ignore
  339. if (item.group === data.groups[data.deleteGroup]) {
  340. // @ts-ignore
  341. delete item.group;
  342. }
  343. }
  344. data.groups.splice(data.deleteGroup, 1);
  345. data.deleteGroup = undefined;
  346. };
  347. onBeforeUnmount(() => {
  348. meta2d.off('opened', getTree);
  349. meta2d.off('add', getTree);
  350. meta2d.off('undo', getTree);
  351. meta2d.off('redo', getTree);
  352. meta2d.off('delete', getTree);
  353. meta2d.off('combine', getTree);
  354. meta2d.off('click', getActived);
  355. meta2d.off('paste', getActived);
  356. inTreePanel.timer = setTimeout(() => {
  357. inTreePanel.value = false;
  358. }, 500);
  359. });
  360. </script>
  361. <style lang="postcss" scoped>
  362. .elements {
  363. display: flex;
  364. flex-direction: column;
  365. height: 100%;
  366. & > * {
  367. overflow-y: auto;
  368. width: 100%;
  369. }
  370. .t-tree {
  371. .t-tag {
  372. background-color: #4583ff33;
  373. position: absolute;
  374. right: 45px;
  375. width: 45.6px;
  376. height: 21.6px;
  377. font-size: 10px;
  378. line-height: 14px;
  379. transform: scale(0.83333);
  380. transform-origin: 0 0;
  381. }
  382. }
  383. .groups-panel {
  384. flex-shrink: 0;
  385. height: 240px;
  386. border-top: 1px solid var(--color-border-input);
  387. padding: 8px 12px;
  388. }
  389. .groups {
  390. height: calc(100% - 24px);
  391. overflow-y: auto;
  392. padding: 0 12px 16px 12px;
  393. & > div {
  394. height: 30px;
  395. line-height: 30px;
  396. svg {
  397. display: none;
  398. &.block {
  399. display: block;
  400. }
  401. }
  402. &:hover {
  403. svg {
  404. display: block;
  405. }
  406. }
  407. }
  408. }
  409. }
  410. </style>