WbsTree.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <template>
  2. <ElTree class="hc-tree-node" ref="ElTreeRef" :props="ElTreeProps" :load="ElTreeLoadNode" lazy highlight-current accordion node-key="primaryKeyId"
  3. :default-expanded-keys="defaultExpandedCids" @node-click="ElTreeClick" @node-contextmenu="ElTreeLabelContextMenu">
  4. <template #default="{ node, data }">
  5. <div class="data-custom-tree-node" :id="`${idPrefix}${data['primaryKeyId']}`">
  6. <!--树组件,节点名称-->
  7. <div class="label" :class="node.level === 1?'level-name':''">
  8. <span :class="data?.colorStatus === 2?'text-blue':data?.colorStatus === 3?'text-orange':data?.colorStatus === 4?'text-green':''" v-if="isColor">{{ node.label }}</span>
  9. <span v-else>{{ node.label }}</span>
  10. </div>
  11. <!--树组件,操作菜单-->
  12. <div class="menu-icon" :class="node.showTreeMenu?'show':''" v-if="node.level !== 1 && menusData.length > 0" @click.stop>
  13. <n-dropdown placement="bottom-end" trigger="click" size="huge" :options="menusData" @select="ElTreeMenuClick($event,node,data)" @update:show="ElTreeMenuShow($event,node)">
  14. <div class="cu-tree-node-popover-menu-icon">
  15. <HcIcon name="menu" ui="text-2xl"/>
  16. </div>
  17. </n-dropdown>
  18. </div>
  19. <!--树组件,操作菜单 END-->
  20. </div>
  21. </template>
  22. </ElTree>
  23. <n-dropdown placement="bottom" trigger="manual" :x="menusX" :y="menusY" size="huge" :options="menusData" :show="showDropdown" @clickoutside="onClickoutside" @select="handleMenuSelect" v-if="menusData.length > 0"/>
  24. </template>
  25. <script setup>
  26. import {ref,nextTick,watch} from "vue";
  27. import {hIconJs} from "~src/plugins/renderele";
  28. import dataFillQuery from '~api/data-fill/query';
  29. import {isItem} from "vue-utils-plus"
  30. import {NDropdown} from 'naive-ui';
  31. //参数
  32. const props = defineProps({
  33. menus: {
  34. type: Array,
  35. default: () => ([])
  36. },
  37. projectId: {
  38. type: [String,Number],
  39. default: ''
  40. },
  41. contractId: {
  42. type: [String,Number],
  43. default: ''
  44. },
  45. autoExpandKeys: {
  46. type: Array,
  47. default: () => ([])
  48. },
  49. isMark: {
  50. type: Boolean,
  51. default: false
  52. },
  53. idPrefix: {
  54. type: String,
  55. default: '`wbs-tree-'
  56. },
  57. isAutoKeys: {
  58. type: Boolean,
  59. default: true
  60. },
  61. isAutoClick: {
  62. type: Boolean,
  63. default: true
  64. },
  65. isColor: {
  66. type: Boolean,
  67. default: false
  68. },
  69. })
  70. //变量
  71. const ElTreeRef = ref(null)
  72. const showDropdown = ref(false)
  73. const treeRefNode = ref(null)
  74. const treeRefData = ref(null)
  75. const ElTreeProps = ref({
  76. label: 'title',
  77. children: 'children',
  78. isLeaf: 'notExsitChild'
  79. })
  80. const menusData = ref(props.menus)
  81. const menusX = ref(0);
  82. const menusY = ref(0);
  83. const menuMark = ref(props.isMark)
  84. const isAutoKeys = ref(props.isAutoKeys)
  85. const TreeExpandKey = ref(props.autoExpandKeys)
  86. const projectId = ref(props.projectId);
  87. const contractId = ref(props.contractId);
  88. const idPrefix = ref(props.idPrefix);
  89. //监听
  90. watch(() => [
  91. props.menus,
  92. props.isMark,
  93. props.isAutoKeys,
  94. props.autoExpandKeys,
  95. props.projectId,
  96. props.contractId,
  97. props.idPrefix,
  98. ], ([menus, isMark, AutoKeys, expandKeys, UserProjectId, UserContractId,UserIdPrefix]) => {
  99. menusData.value = menus
  100. menuMark.value = isMark
  101. isAutoKeys.value = AutoKeys
  102. TreeExpandKey.value = expandKeys
  103. projectId.value = UserProjectId
  104. contractId.value = UserContractId
  105. idPrefix.value = UserIdPrefix
  106. })
  107. //树形结构异步加载数据
  108. const defaultExpandedCids = ref([])
  109. const ElTreeLoadNode = async (node, resolve) => {
  110. let contractIdRelation = '', parentId = '';
  111. if (node.level !== 0) {
  112. const nodeData = node?.data ?? {};
  113. contractIdRelation = nodeData?.contractIdRelation ?? ''
  114. parentId = contractIdRelation ? nodeData?.primaryKeyId : nodeData?.id
  115. }
  116. //获取数据
  117. const { data } = await dataFillQuery.queryWbsTreeData({
  118. contractId: contractId.value || '',
  119. contractIdRelation,
  120. parentId
  121. })
  122. //处理数据
  123. let clickKey = '', defaultExpandedArr = [];
  124. const keys = TreeExpandKey.value || []
  125. const resData = data?.data || []
  126. if (keys.length > 0) {
  127. let lastKey = keys[keys.length-1];
  128. for (const item of resData) {
  129. //自动展开
  130. if (isItem(keys,item?.primaryKeyId)) {
  131. defaultExpandedArr.push(item?.primaryKeyId)
  132. }
  133. //最后一个,选中点击
  134. if (item?.primaryKeyId === lastKey) {
  135. clickKey = item?.primaryKeyId
  136. }
  137. }
  138. } else if (node.level === 0) {
  139. defaultExpandedArr.push(resData[0]?.primaryKeyId)
  140. }
  141. //自动展开
  142. defaultExpandedCids.value = defaultExpandedArr
  143. resolve(resData)
  144. //最后一个,执行点击
  145. if (props.isAutoClick && clickKey) {
  146. await nextTick(() => {
  147. document.getElementById(`${idPrefix.value}${clickKey}`)?.click()
  148. })
  149. }
  150. }
  151. //事件
  152. const emit = defineEmits(['menuTap','nodeTap'])
  153. //节点被点击
  154. const ElTreeClick = async (data,node) => {
  155. if (isAutoKeys.value) {
  156. let autoKeysArr = []
  157. await getNodeExpandKeys(node, autoKeysArr)
  158. const autoKeys = autoKeysArr.reverse()
  159. emit('nodeTap', {node, data, keys: autoKeys})
  160. } else {
  161. emit('nodeTap', {node, data, keys: []})
  162. }
  163. }
  164. //处理自动展开的节点KEY
  165. const getNodeExpandKeys = async (node, newKeys) => {
  166. const parent = node?.parent ?? []
  167. const primaryKeyId = node?.data?.primaryKeyId ?? ''
  168. if (primaryKeyId) {
  169. newKeys.push(primaryKeyId)
  170. await getNodeExpandKeys(parent, newKeys)
  171. }
  172. }
  173. //鼠标右键事件
  174. const ElTreeLabelContextMenu = (e,data,node) => {
  175. const rows = menusData.value || [];
  176. if (node.level !== 1 && rows.length > 0) {
  177. e.preventDefault();
  178. treeRefNode.value = node;
  179. treeRefData.value = data;
  180. if (menuMark.value) {
  181. setMenuMarkVal(rows,data)
  182. }
  183. nextTick(() => {
  184. menusX.value = e.clientX;
  185. menusY.value = e.clientY;
  186. showDropdown.value = true;
  187. });
  188. }
  189. }
  190. //设置菜单标记状态
  191. const setMenuMarkVal = (rows,item) => {
  192. for (let i = 0; i < rows.length; i++) {
  193. if (rows[i].key === 'mark' || rows[i].key === 'cancel_mark') {
  194. if (item.isFirst) {
  195. menusData.value[i].label = '取消标记为首件';
  196. menusData.value[i].key = 'cancel_mark';
  197. menusData.value[i].icon = hIconJs({
  198. name: 'grade', fill: true
  199. });
  200. } else {
  201. menusData.value[i].label = '标记为首件';
  202. menusData.value[i].key = 'mark';
  203. menusData.value[i].icon = hIconJs({name: 'grade'});
  204. }
  205. break;
  206. }
  207. }
  208. }
  209. //鼠标右键菜单被点击
  210. const handleMenuSelect = (key) => {
  211. const node = treeRefNode.value;
  212. const data = treeRefData.value;
  213. showDropdown.value = false;
  214. emit('menuTap', {key,node,data})
  215. }
  216. const onClickoutside = () => {
  217. treeRefNode.value = null;
  218. treeRefData.value = null;
  219. showDropdown.value = false;
  220. }
  221. //菜单被点击
  222. const ElTreeMenuClick = (key,node,data) => {
  223. emit('menuTap', {key,node,data})
  224. }
  225. //菜单是否显示
  226. const ElTreeMenuShow = (key,node) => {
  227. node.showTreeMenu = key
  228. }
  229. //设置树菜单的标记数据
  230. const setElTreeMenuMark = (keys,isFirst) => {
  231. keys.forEach(item => {
  232. //根据 data 或者 key 拿到 Tree 组件中的 node
  233. let node = ElTreeRef.value.getNode(item)
  234. if (!!node) node.data.isFirst = isFirst;
  235. })
  236. }
  237. //设置树菜单的标记数据
  238. const removeElTreeNode = (key) => {
  239. //根据 data 或者 key 拿到 Tree 组件中的 node
  240. let node = ElTreeRef.value.getNode(key)
  241. //删除 Tree 中的一个节点,使用此方法必须设置 node-key 属性
  242. ElTreeRef.value.remove(node)
  243. }
  244. // 暴露出去
  245. defineExpose({
  246. setElTreeMenuMark,
  247. removeElTreeNode
  248. })
  249. </script>
  250. <style lang="scss" scoped>
  251. .data-custom-tree-node {
  252. position: relative;
  253. display: flex;
  254. align-items: center;
  255. width: 100%;
  256. color: var(--ui-TC);
  257. .label {
  258. flex: auto;
  259. font-size: 16px;
  260. }
  261. .label.level-name {
  262. font-size: 18px;
  263. font-weight: bold;
  264. }
  265. .menu-icon {
  266. position: relative;
  267. font-size: 20px;
  268. opacity: 0;
  269. pointer-events: none;
  270. transition: opacity 0.2s;
  271. .cu-tree-node-popover-menu-icon {
  272. display: flex;
  273. align-items: center;
  274. justify-content: center;
  275. }
  276. }
  277. &:hover {
  278. .menu-icon {
  279. opacity: 1;
  280. pointer-events: all;
  281. cursor: context-menu;
  282. }
  283. }
  284. .menu-icon.show {
  285. opacity: 1;
  286. pointer-events: all;
  287. cursor: context-menu;
  288. }
  289. }
  290. </style>