ElementTree.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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. @active="onActive"
  12. style="padding: 0 4px 8px 8px"
  13. >
  14. <template #label="{ node }: any">
  15. <div class="flex middle" :class="{ gray: node.data.visible === false }">
  16. <template v-if="node.getChildren()">
  17. <t-icon v-if="node.expanded" name="folder-open" />
  18. <t-icon v-else name="folder" />
  19. </template>
  20. <t-icon v-else name="control-platform" />
  21. <t-input
  22. v-if="node.data.edited"
  23. v-model="node.data.label"
  24. :autofocus="true"
  25. @blur="onDescription(node)"
  26. @enter="onDescription(node)"
  27. />
  28. <span v-else @dblclick="node.data.edited = true">
  29. {{ node.label }}
  30. </span>
  31. </div>
  32. </template>
  33. <template #operations="{ node }: any">
  34. <div
  35. class="flex middle operations"
  36. :class="{
  37. gray: node.data.visible === false,
  38. show: node.data.visible === false || node.data.locked,
  39. }"
  40. style="width: 36px; height: 16px"
  41. >
  42. <t-tooltip
  43. class="mr-4"
  44. v-if="!node.data.locked"
  45. content="可编辑"
  46. placement="top"
  47. >
  48. <svg class="l-icon" aria-hidden="true" @click="lock(node, 1)">
  49. <use xlink:href="#l-unlock"></use>
  50. </svg>
  51. </t-tooltip>
  52. <t-tooltip
  53. class="mr-4"
  54. v-else-if="node.data.locked == 1"
  55. content="禁止编辑"
  56. placement="top"
  57. >
  58. <svg class="l-icon" aria-hidden="true" @click="lock(node, 2)">
  59. <use xlink:href="#l-lock"></use>
  60. </svg>
  61. </t-tooltip>
  62. <t-tooltip
  63. class="mr-4"
  64. v-else-if="node.data.locked == 2"
  65. content="禁止编辑和移动"
  66. placement="top"
  67. >
  68. <svg class="l-icon" aria-hidden="true" @click="lock(node, 10)">
  69. <use xlink:href="#l-wufayidong"></use>
  70. </svg>
  71. </t-tooltip>
  72. <t-tooltip
  73. class="mr-4"
  74. v-else-if="node.data.locked == 10"
  75. content="禁止所有事件"
  76. placement="top"
  77. >
  78. <svg class="l-icon" aria-hidden="true" @click="lock(node, 0)">
  79. <use xlink:href="#l-jinyong"></use>
  80. </svg>
  81. </t-tooltip>
  82. <t-icon
  83. v-if="node.data.visible !== false"
  84. name="browse"
  85. @click="visible(node, false)"
  86. />
  87. <t-icon v-else name="browse-off" @click="visible(node, true)" />
  88. </div>
  89. </template>
  90. </t-tree>
  91. <div class="groups-panel" style="padding: 8px 0">
  92. <div class="flex middle between" style="padding: 0 12px">
  93. <div class="title">分组</div>
  94. <a @click="addGroup"> +新建分组</a>
  95. </div>
  96. <div class="groups">
  97. <div
  98. v-for="(item, i) in data.groups"
  99. class="flex middle between hover"
  100. :class="{ primary: i == data.activedGroup }"
  101. >
  102. <span
  103. class="flex-grow"
  104. v-if="i != data.editedGroup"
  105. @click="activeGroup(i)"
  106. @dblclick="
  107. data.activedGroup = data.editedGroup = i;
  108. data.group = item;
  109. "
  110. >
  111. {{ item }}
  112. </span>
  113. <t-input
  114. v-else
  115. v-model="data.group"
  116. :autofocus="true"
  117. @blur="setGroup"
  118. @enter="setGroup"
  119. />
  120. <t-popconfirm
  121. content="确认删除该分组吗?"
  122. @confirm="delGroup"
  123. @cancel="data.deleteGroup = undefined"
  124. >
  125. <t-icon
  126. name="delete"
  127. class="ml-8"
  128. :class="{ block: i == data.deleteGroup }"
  129. @click="data.deleteGroup = i"
  130. />
  131. </t-popconfirm>
  132. </div>
  133. </div>
  134. </div>
  135. </div>
  136. </template>
  137. <script lang="ts" setup>
  138. import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue';
  139. import { MessagePlugin } from 'tdesign-vue-next';
  140. import { LockState, Pen } from '@meta2d/core';
  141. import { getPenTree, setChildrenVisible } from '@/services/common';
  142. const tree = ref<any>(null);
  143. const data = reactive<any>({
  144. tree: [],
  145. actived: [],
  146. groups: [],
  147. });
  148. onBeforeMount(() => {
  149. meta2d.on('opened', getTree);
  150. meta2d.on('add', getTree);
  151. meta2d.on('undo', getTree);
  152. meta2d.on('redo', getTree);
  153. meta2d.on('delete', getTree);
  154. meta2d.on('combine', getTree);
  155. getTree();
  156. const d = meta2d.store.data as any;
  157. if (!d.groups) {
  158. d.groups = [];
  159. }
  160. data.groups = d.groups;
  161. });
  162. const getTree = () => {
  163. data.tree = getPenTree();
  164. };
  165. const calcElem = (node: Pen) => {
  166. if (!node) {
  167. return;
  168. }
  169. const elem: any = {
  170. label: (node as any).description || node.name,
  171. value: node.id,
  172. locked: node.locked,
  173. visible: node.visible,
  174. };
  175. if (!node.children) {
  176. return elem;
  177. }
  178. elem.children = [];
  179. for (const id of node.children) {
  180. const child = calcElem(meta2d.store.pens[id]);
  181. child && elem.children.push(child);
  182. }
  183. return elem;
  184. };
  185. const onActive = (value: string[]) => {
  186. if (!value.length) {
  187. return;
  188. }
  189. const pens: Pen[] = [];
  190. for (const item of meta2d.store.data.pens) {
  191. for (const v of value) {
  192. if (item.id === v) {
  193. pens.push(item);
  194. }
  195. }
  196. }
  197. meta2d.active(pens, false);
  198. meta2d.render();
  199. };
  200. const lock = (node: any, v: LockState) => {
  201. node.data.locked = v;
  202. meta2d.setValue({
  203. id: node.value,
  204. locked: v,
  205. });
  206. };
  207. const visible = (node: any, v: boolean) => {
  208. node.data.visible = v;
  209. setChildrenVisible(node, v);
  210. const pen = meta2d.findOne(node.value);
  211. pen && meta2d.setVisible(pen, v);
  212. };
  213. const onDescription = (node: any) => {
  214. node.data.edited = false;
  215. node.setData({ label: node.data.label });
  216. meta2d.setValue({
  217. id: node.value,
  218. description: node.data.label,
  219. });
  220. };
  221. const addGroup = () => {
  222. const i = data.groups.length + 1;
  223. data.group = '组' + i;
  224. data.groups.push(data.group);
  225. data.activedGroup = data.editedGroup = i;
  226. };
  227. const activeGroup = (i: number) => {
  228. data.activedGroup = i;
  229. const group = data.groups[i];
  230. const pens: Pen[] = [];
  231. for (const item of meta2d.store.data.pens) {
  232. if (item.tags?.includes(group)) {
  233. pens.push(item);
  234. }
  235. }
  236. meta2d.active(pens, false);
  237. meta2d.render();
  238. };
  239. const setGroup = () => {
  240. if (data.groups[data.editedGroup] === data.group) {
  241. data.editedGroup = undefined;
  242. return;
  243. }
  244. if (data.groups.includes(data.group)) {
  245. MessagePlugin.error('已经存在相同分组!');
  246. return;
  247. }
  248. for (const item of meta2d.store.data.pens) {
  249. // @ts-ignore
  250. if (item.group === data.groups[data.editedGroup]) {
  251. // @ts-ignore
  252. item.group === data.group;
  253. }
  254. }
  255. data.groups[data.editedGroup] = data.group;
  256. data.editedGroup = undefined;
  257. };
  258. const delGroup = () => {
  259. for (const item of meta2d.store.data.pens) {
  260. // @ts-ignore
  261. if (item.group === data.groups[data.deleteGroup]) {
  262. // @ts-ignore
  263. delete item.group;
  264. }
  265. }
  266. data.groups.splice(data.deleteGroup, 1);
  267. data.deleteGroup = undefined;
  268. };
  269. onBeforeUnmount(() => {
  270. meta2d.off('opened', getTree);
  271. meta2d.off('add', getTree);
  272. meta2d.off('undo', getTree);
  273. meta2d.off('redo', getTree);
  274. meta2d.off('delete', getTree);
  275. meta2d.off('combine', getTree);
  276. });
  277. </script>
  278. <style lang="postcss" scoped>
  279. .elements {
  280. display: flex;
  281. flex-direction: column;
  282. height: 100%;
  283. & > * {
  284. overflow-y: auto;
  285. width: 100%;
  286. }
  287. .groups-panel {
  288. flex-shrink: 0;
  289. height: 240px;
  290. border-top: 1px solid var(--color-border-input);
  291. padding: 8px 12px;
  292. }
  293. .groups {
  294. height: calc(100% - 24px);
  295. overflow-y: auto;
  296. padding: 0 12px 16px 12px;
  297. & > div {
  298. height: 30px;
  299. line-height: 30px;
  300. svg {
  301. display: none;
  302. &.block {
  303. display: block;
  304. }
  305. }
  306. &:hover {
  307. svg {
  308. display: block;
  309. }
  310. }
  311. }
  312. }
  313. }
  314. </style>