HcTreeDataV2.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <template>
  2. <el-tree-v2
  3. ref="ElTreeRef"
  4. :class="[ui, submitCounts ? 'tree-line1' : '', isTreeShow ? 'is-tree-show': '']"
  5. :data="datas"
  6. :props="ElTreeProps"
  7. class="hc-tree-node-v2 tree-line el-radio-group"
  8. :filter-method="filterMethod"
  9. :height="treeHeight"
  10. @node-click="ElTreeClick"
  11. accordion
  12. highlight-current
  13. :default-expand-all="false"
  14. node-key="primaryKeyId"
  15. :indent="24"
  16. @node-contextmenu="ElTreeLabelContextMenu"
  17. >
  18. <template #default="{ node, data }">
  19. <div :id="`${idPrefix}${data['primaryKeyId']}`" class="data-custom-tree-node">
  20. <!--树组件,节点名称-->
  21. <div :class="node.level === 1?'level-name':''" class="label">
  22. <span
  23. v-if="(data['type'] > 0 && data['majorDataType'] > 0) || (data['type'] > 0 && data['majorDataType'] <= 0)"
  24. class="hc-tree-node-type">{{
  25. getTreeNodeType(data['type'], data['majorDataType'])
  26. }}</span>
  27. <span
  28. v-if="isColor"
  29. :class="data?.colorStatus === 2?'text-blue':data?.colorStatus === 3?'text-orange':data?.colorStatus === 4?'text-green':''">{{
  30. node.label
  31. }}</span>
  32. <span v-else>{{ node.label }}</span>
  33. </div>
  34. <div v-if="isSubmitCounts" class="text-blue submit-counts">【{{ data.submitCounts ?? 0 }}】</div>
  35. <!--树组件,操作菜单-->
  36. <div v-if="node.level !== 1 && menusData.length > 0" :class="node.showTreeMenu?'show':''"
  37. class="menu-icon1" @click.stop>
  38. <div class="cu-tree-node-popover-menu-icon"
  39. @click.prevent.stop="ElTreeLabelContextMenu($event,data,node)">
  40. <HcIcon name="apps" ui="text-2xl"/>
  41. </div>
  42. </div>
  43. <!--树组件,操作菜单 END-->
  44. </div>
  45. </template>
  46. </el-tree-v2>
  47. <!--右键菜单-->
  48. <HcContextMenu v-if="menusData.length > 0" ref="contextMenuRef" :datas="menusData" @closed="handleMenuClosed" @item-click="handleMenuSelect">
  49. <template #mark="{item}">
  50. <HcIcon :fill="treeRefData?.isFirst" :name="item.icon" class="menu-item-icon"/>
  51. <span class="menu-item-name">{{ treeRefData?.isFirst ? '取消标记为首件' : '标记为首件' }}</span>
  52. </template>
  53. <template #sort="{item}">
  54. <HcIcon :line="false" :name="item.icon" class="menu-item-icon"/>
  55. <span class="menu-item-name">{{ item.label }}</span>
  56. </template>
  57. </HcContextMenu>
  58. </template>
  59. <script setup>
  60. import {ref, watch, nextTick, onMounted, onUnmounted} from "vue";
  61. import {getTreeNodeType} from '~uti/utils';
  62. import { ElTreeV2 } from 'z-element-plus'
  63. import {isNullES} from "js-fast-way";
  64. //参数
  65. const props = defineProps({
  66. ui: {
  67. type: String,
  68. default: ''
  69. },
  70. isMark: {
  71. type: Boolean,
  72. default: false
  73. },
  74. menus: {
  75. type: Array,
  76. default: () => ([])
  77. },
  78. height: {
  79. type: [Number, String],
  80. default: 0
  81. },
  82. datas: {
  83. type: Array,
  84. default: () => ([])
  85. },
  86. submitCounts: {
  87. type: Boolean,
  88. default: false
  89. },
  90. searchTreeVal: {
  91. type: String,
  92. default: ''
  93. },
  94. ElTreeLoadNode: {
  95. type: Boolean,
  96. default: true
  97. },
  98. idPrefix: {
  99. type: String,
  100. default: 'tree-data-v2-'
  101. },
  102. isColor: {
  103. type: Boolean,
  104. default: false
  105. },
  106. isAutoKeys: {
  107. type: Boolean,
  108. default: true
  109. },
  110. isShow: {
  111. type: Boolean,
  112. default: false
  113. },
  114. })
  115. //变量
  116. const ElTreeRef = ref(null)
  117. const ElTreeProps = ref({
  118. value: 'primaryKeyId',
  119. label: 'title',
  120. children: 'children',
  121. })
  122. const treeHeight = ref(0)
  123. const menuMark = ref(props.isMark)
  124. const isSubmitCounts = ref(props.submitCounts);
  125. const searchInfo = ref(props.searchTreeVal)
  126. const idPrefix = ref(props.idPrefix);
  127. const menusData = ref(props.menus)
  128. const isAutoKeys = ref(props.isAutoKeys)
  129. const treeRefData = ref(null)
  130. const isTreeShow = ref(props.isShow)
  131. //监听
  132. watch(() => [
  133. props.height,
  134. props.submitCounts,
  135. props.idPrefix,
  136. props.menus,
  137. props.isAutoKeys,
  138. props.isMark,
  139. props.isShow,
  140. ], ([height, submitCounts,UserIdPrefix,menus,AutoKeys,isMark, isShow]) => {
  141. treeHeight.value = height
  142. isSubmitCounts.value = submitCounts
  143. idPrefix.value = UserIdPrefix
  144. menusData.value = menus
  145. isAutoKeys.value = AutoKeys
  146. menuMark.value = isMark
  147. isTreeShow.value = isShow
  148. })
  149. watch(() => [
  150. props.searchTreeVal,
  151. ], ([val]) => {
  152. searchInfo.value = val
  153. if (val) {
  154. nextTick(() => {
  155. ElTreeRef?.value.filter(val)
  156. })
  157. } else {
  158. emit('changeSearch')
  159. }
  160. })
  161. onMounted(() => {
  162. getOffsetHeight()
  163. windowResize()
  164. })
  165. //事件
  166. const emit = defineEmits(['menuTap', 'nodeTap', 'changeSearch', 'changetreelaod'])
  167. //节点被点击
  168. //处理自动展开的节点KEY
  169. const getNodeExpandKeys = async (node, newKeys) => {
  170. const parent = node?.parent ?? []
  171. const primaryKeyId = node?.data?.primaryKeyId ?? ''
  172. if (primaryKeyId) {
  173. newKeys.push(primaryKeyId)
  174. await getNodeExpandKeys(parent, newKeys)
  175. }
  176. }
  177. //节点被点击
  178. const ElTreeClick = async (data, node) => {
  179. console.log(node)
  180. if (isAutoKeys.value) {
  181. let autoKeysArr = []
  182. await getNodeExpandKeys(node, autoKeysArr)
  183. const autoKeys = autoKeysArr.reverse()
  184. emit('nodeTap', {node, data, keys: autoKeys})
  185. } else {
  186. emit('nodeTap', {node, data, keys: []})
  187. }
  188. }
  189. const getReturnNode = (node, _array, value) => {
  190. let isPass = node.data && node.data.title && node.data.title.indexOf(value) !== -1;
  191. isPass ? _array.push(isPass) : '';
  192. if (!isPass && node.level != 1 && node.parent) {
  193. getReturnNode(node.parent, _array, value);
  194. }
  195. }
  196. //筛选树节点
  197. const filterMethod = (query,data) => {
  198. if (!query) {
  199. return true;
  200. }
  201. let _array = [];//这里使用数组存储 只是为了存储值。
  202. let node=ElTreeRef?.value.getNode(data.primaryKeyId)
  203. getReturnNode(node, _array, query);
  204. let result = false;
  205. _array.forEach((item) => {
  206. result = result || item;
  207. });
  208. setTimeout(() => {
  209. emit('changetreelaod', false)
  210. }, 1000)
  211. return result;
  212. }
  213. //鼠标右键事件
  214. const contextMenuRef = ref(null)
  215. const treeRefNode = ref(null)
  216. const ElTreeLabelContextMenu = (e, data, node) => {
  217. const rows = menusData.value || [];
  218. if (node.level !== 1 && rows.length > 0) {
  219. e.preventDefault();
  220. treeRefNode.value = node;
  221. treeRefData.value = data;
  222. node.showTreeMenu = true
  223. //展开菜单
  224. contextMenuRef.value?.showMenu(e)
  225. }
  226. }
  227. //鼠标右键菜单被点击
  228. const handleMenuSelect = async ({key}) => {
  229. const node = treeRefNode.value;
  230. const data = treeRefData.value;
  231. //如果为标记菜单
  232. if (key === 'mark' && menuMark.value) {
  233. if (data.isFirst === true) {
  234. emit('menuTap', {key: 'cancel_mark', node, data})
  235. } else {
  236. emit('menuTap', {key: 'mark', node, data})
  237. }
  238. } else {
  239. // emit('menuTap', {key, node, data})
  240. if (isAutoKeys.value) {
  241. console.log(11111);
  242. let autoKeysArr = []
  243. await getNodeExpandKeys(node, autoKeysArr)
  244. const autoKeys = autoKeysArr.reverse()
  245. emit('menuTap', {key, node, data, keys: autoKeys})
  246. }
  247. }
  248. }
  249. const handleMenuClosed = () => {
  250. const node = treeRefNode.value;
  251. if (!isNullES(node)) {
  252. treeRefNode.value['showTreeMenu'] = false
  253. }
  254. }
  255. //设置树菜单的标记数据
  256. const setElTreeMenuMark = (keys, isFirst) => {
  257. keys.forEach(item => {
  258. //根据 data 或者 key 拿到 Tree 组件中的 node
  259. let node = ElTreeRef.value.getNode(item)
  260. if (!!node) node.data.isFirst = isFirst;
  261. })
  262. }
  263. //设置树菜单的标记数据
  264. const removeElTreeNode = (key) => {
  265. //根据 data 或者 key 拿到 Tree 组件中的 node
  266. let node = ElTreeRef.value.getNode(key)
  267. //删除 Tree 中的一个节点,使用此方法必须设置 node-key 属性
  268. ElTreeRef.value.remove(node)
  269. }
  270. //监听浏览器窗口变化
  271. const windowResize = () => {
  272. window.addEventListener("resize", resizeEvent);
  273. }
  274. const resizeEvent = () => {
  275. window.requestAnimationFrame(() => {
  276. getOffsetHeight()
  277. })
  278. }
  279. const getOffsetHeight = () => {
  280. try {
  281. treeHeight.value = document.getElementById('hc-tree-scrollbar').offsetHeight;
  282. } catch {
  283. console.log('hc-tree-scrollbar 元素,不存在,获取高度失败')
  284. }
  285. }
  286. //被卸载
  287. onUnmounted(() => {
  288. window.removeEventListener("resize", resizeEvent);
  289. })
  290. // 暴露出去
  291. defineExpose({
  292. setElTreeMenuMark,
  293. removeElTreeNode,
  294. filterMethod
  295. })
  296. </script>
  297. <style lang="scss" scoped>
  298. //@import "../../../styles/app/tree.scss";
  299. .el-radio-group {
  300. width: 100% !important;
  301. display: inline-grid;
  302. }
  303. .data-custom-tree-node {
  304. flex: 1;
  305. white-space: nowrap;
  306. overflow: hidden;
  307. text-overflow: ellipsis;
  308. .label {
  309. flex: 1;
  310. white-space: nowrap;
  311. overflow: hidden;
  312. text-overflow: ellipsis;
  313. }
  314. .submit-counts {
  315. position: unset;
  316. font-size: 14px;
  317. }
  318. .menu-icon1 {
  319. position: relative;
  320. vertical-align: bottom;
  321. display: inline-block;
  322. width: 0;
  323. right: -45px;
  324. border-radius: 2px;
  325. pointer-events: none;
  326. background: rgba(255, 255, 255, 0.25);
  327. transition: width 0.2s;
  328. .cu-tree-node-popover-menu-icon {
  329. display: flex;
  330. align-items: center;
  331. justify-content: center;
  332. }
  333. }
  334. &:hover {
  335. .menu-icon1 {
  336. right: 0;
  337. width: 24px;
  338. pointer-events: all;
  339. cursor: context-menu;
  340. }
  341. }
  342. .menu-icon1.show {
  343. right: 0;
  344. width: 24px;
  345. pointer-events: all;
  346. cursor: context-menu;
  347. }
  348. }
  349. </style>
  350. <style lang="scss">
  351. .hc-tree-node-v2.el-tree {
  352. display: none;
  353. .el-tree-node {
  354. white-space: nowrap;
  355. overflow: hidden;
  356. text-overflow: ellipsis;
  357. .el-tree-node_content {
  358. white-space: nowrap;
  359. overflow: hidden;
  360. text-overflow: ellipsis;
  361. }
  362. }
  363. &.is-tree-show {
  364. display: block;
  365. }
  366. }
  367. </style>