Tree.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <script setup lang="tsx">
  2. import { Icon } from '@/components/Icon'
  3. import { Tree } from '@/components/Tree'
  4. import { ContentWrap } from '@/components/ContentWrap'
  5. import { useI18n } from 'vue-i18n'
  6. import { ElMessage, ElMessageBox } from 'element-plus'
  7. import { ref } from 'vue'
  8. const { t } = useI18n()
  9. const treeData = ref([
  10. {
  11. id: 1,
  12. name: '北京',
  13. children: [
  14. {
  15. id: 5,
  16. name: '朝阳',
  17. children: [
  18. {
  19. id: 17,
  20. name: '双塔',
  21. children: []
  22. },
  23. {
  24. id: 18,
  25. name: '龙城',
  26. children: []
  27. }
  28. ]
  29. },
  30. {
  31. id: 6,
  32. name: '丰台',
  33. children: [
  34. {
  35. id: 19,
  36. name: '新村',
  37. children: []
  38. },
  39. {
  40. id: 20,
  41. name: '大红门',
  42. children: []
  43. },
  44. {
  45. id: 21,
  46. name: '长辛店',
  47. children: [
  48. {
  49. id: 22,
  50. name: '东山坡',
  51. children: []
  52. },
  53. {
  54. id: 23,
  55. name: '北关',
  56. children: []
  57. },
  58. {
  59. id: 24,
  60. name: '光明里',
  61. children: []
  62. },
  63. {
  64. id: 25,
  65. name: '赵辛店',
  66. children: []
  67. },
  68. {
  69. id: 26,
  70. name: '西峰寺',
  71. children: []
  72. }
  73. ]
  74. }
  75. ]
  76. },
  77. {
  78. id: 7,
  79. name: '海淀',
  80. children: []
  81. },
  82. {
  83. id: 8,
  84. name: '房山',
  85. children: []
  86. },
  87. {
  88. id: 10,
  89. name: '顺义',
  90. children: []
  91. }
  92. ]
  93. },
  94. {
  95. id: 2,
  96. name: '上海',
  97. children: [
  98. {
  99. id: 11,
  100. name: '黄埔',
  101. children: []
  102. },
  103. {
  104. id: 12,
  105. name: '徐汇',
  106. children: []
  107. }
  108. ]
  109. },
  110. {
  111. id: 3,
  112. name: '广州',
  113. children: [
  114. {
  115. id: 13,
  116. name: '荔湾',
  117. children: []
  118. },
  119. {
  120. id: 14,
  121. name: '白云',
  122. children: []
  123. },
  124. {
  125. id: 15,
  126. name: '越秀',
  127. children: []
  128. },
  129. {
  130. id: 16,
  131. name: '南沙',
  132. children: []
  133. }
  134. ]
  135. }
  136. ])
  137. const handleNodeClick = (data: any) => {
  138. console.log('Node clicked:', data)
  139. }
  140. const addOrg = (node: any) => {
  141. ElMessageBox.prompt('请输入分组名称', '添加子分组', {
  142. confirmButtonText: '确定',
  143. cancelButtonText: '取消',
  144. inputPattern: /\S/,
  145. inputErrorMessage: '分组名称不能为空'
  146. }).then(({ value }) => {
  147. node.children.push({
  148. id: node.children.length + 1,
  149. name: value,
  150. children: []
  151. })
  152. ElMessage.success('添加成功')
  153. })
  154. }
  155. const editOrg = (node: any) => {
  156. ElMessageBox.prompt('请输入新的分组名称', '修改分组名称', {
  157. confirmButtonText: '确定',
  158. cancelButtonText: '取消',
  159. inputValue: node.name,
  160. inputPattern: /\S/,
  161. inputErrorMessage: '分组名称不能为空'
  162. }).then(({ value }) => {
  163. node.name = value
  164. ElMessage.success('修改成功')
  165. })
  166. }
  167. const deleteOrg = (node: any) => {
  168. ElMessageBox.confirm(`删除 [${node.name}] 分组、下级子分组 <br>是否继续?`, '提示', {
  169. dangerouslyUseHTMLString: true,
  170. confirmButtonText: '确定',
  171. cancelButtonText: '取消',
  172. type: 'warning',
  173. center: true
  174. }).then(() => {
  175. const id = node.id
  176. // 查找 treeData 中对应的节点,并删除
  177. const deleteNode = (data: any) => {
  178. for (let i = 0; i < data.length; i++) {
  179. if (data[i].id === id) {
  180. data.splice(i, 1)
  181. return
  182. }
  183. if (data[i].children) {
  184. deleteNode(data[i].children)
  185. }
  186. }
  187. }
  188. deleteNode(treeData.value)
  189. ElMessage.success('删除成功')
  190. })
  191. }
  192. </script>
  193. <template>
  194. <ContentWrap :title="t('treeDemo.treeTitle')" :message="t('qrcodeDemo.qrcodeDes')">
  195. <Tree
  196. :data="treeData"
  197. :tree-props="{
  198. highlightCurrent: true,
  199. nodeKey: 'id',
  200. props: {
  201. children: 'children',
  202. label: 'name'
  203. }
  204. }"
  205. width="300px"
  206. height="400px"
  207. @node-click="handleNodeClick"
  208. >
  209. <!-- 自定义右键菜单 -->
  210. <template #context-menu="{ node }">
  211. <div class="menuItem" @click="addOrg(node)">
  212. <Icon icon="ep:plus" style="color: #1e9fff" />
  213. <span>添加子分组</span>
  214. </div>
  215. <div class="menuItem" @click="editOrg(node)">
  216. <Icon icon="ep:edit-pen" style="color: #1e9fff" />
  217. 修改分组名称
  218. </div>
  219. <div class="menuItem" @click="deleteOrg(node)">
  220. <Icon icon="ep:delete" style="color: #1e9fff" />
  221. 删除分组及子分组
  222. </div>
  223. </template>
  224. <!-- 自定义节点显示 -->
  225. <!-- <template #render-node="{ node }">
  226. <span v-if="node.isLeaf">[FILE] {{ node.label }}</span>
  227. <span v-else>[FOLDER] {{ node.label }}</span>
  228. </template> -->
  229. </Tree>
  230. </ContentWrap>
  231. </template>
  232. <style lang="less" scoped>
  233. .menuItem {
  234. display: flex;
  235. padding: 2px 10px;
  236. text-align: left;
  237. box-sizing: border-box;
  238. align-items: center; /* 垂直居中 */
  239. gap: 5px; /* 图标和文字之间的间距,可根据需要调整 */
  240. }
  241. .menuItem:hover {
  242. cursor: pointer;
  243. background-color: #eee;
  244. }
  245. </style>