children.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <template>
  2. <div class="cu-tree-node-children">
  3. <div class="cu-tree-node-view is-leaf" v-for="item in nodes" :key="item[format.key]">
  4. <div class="cu-tree-node-label">
  5. <div class="cu-tree-node-label-text">
  6. <div class="cu-tree-node-label-name" :id="`node-tree-${item.key}`" @click="nodeLabelClick(item)" @dblclick="nodeLabelDblClick(item)" @contextmenu="nodeLabelContextMenu($event,item)">
  7. <template v-if="TreeIsColor">
  8. <el-button hc-btn color="#0081ff" :loading="item.loading" v-if="item?.colorStatus === 2">{{item.label}}</el-button>
  9. <el-button hc-btn color="#f37b1d" :loading="item.loading" v-else-if="item?.colorStatus === 3">{{item.label}}</el-button>
  10. <el-button type="primary" hc-btn :loading="item.loading" v-else-if="item?.colorStatus === 4">{{item.label}}</el-button>
  11. <el-button type="info" hc-btn :loading="item.loading" v-else>{{item.label}}</el-button>
  12. </template>
  13. <template v-if="!TreeIsColor">
  14. <el-button type="info" hc-btn :loading="item.loading">{{item.label}}</el-button>
  15. </template>
  16. </div>
  17. <span class="cu-tree-node-label-btn" :class="[ifExpanded(item)?'expanded':'']" v-if="isExpanded(item)" @click="expandedClick(item)"/>
  18. </div>
  19. </div>
  20. <TreeNodeChildren :data="item.childNodes" :format="format" :menus="menusData" :isMark="menuMark" :parentNodes="item" v-if="ifExpanded(item)" :isColor="TreeIsColor"
  21. @expandClick="ChildrenExpandedClick" @nodeClick="ChildrenNodeLabelClick" @nodeDblClick="ChildrenNodeLabelDblClick" @menuClick="ChildrenCpoverMenuClick"/>
  22. </div>
  23. </div>
  24. <!--菜单-->
  25. <n-dropdown placement="bottom" trigger="manual" :x="x" :y="y" size="huge" :options="menusData" :show="showDropdown" @clickoutside="onClickoutside" @select="handleMenuSelect"/>
  26. </template>
  27. <script setup>
  28. import {ref,watch,onMounted,nextTick} from "vue";
  29. import TreeNodeChildren from "./children.vue"
  30. import {hIconJs} from "~src/plugins/renderele";
  31. import {isType} from "vue-utils-plus"
  32. import { NDropdown } from 'naive-ui';
  33. const { isObject, isBoolean } = isType()
  34. const props = defineProps({
  35. data: {
  36. type: Array,
  37. default: () => ([])
  38. },
  39. parentNodes: {
  40. type: Object,
  41. default: () => ({})
  42. },
  43. format: {
  44. type: Object,
  45. default: () => ({
  46. key: "key",
  47. label: "label",
  48. children: "children",
  49. })
  50. },
  51. menus: {
  52. type: Array,
  53. default: () => ([])
  54. },
  55. isMark: {
  56. type: Boolean,
  57. default: false
  58. },
  59. accordion: {
  60. type: Boolean,
  61. default: false
  62. },
  63. isColor: {
  64. type: Boolean,
  65. default: false
  66. },
  67. })
  68. //初始数据
  69. const datas = ref(props.data)
  70. const parent = ref(props.parentNodes)
  71. const menusData = ref(props.menus)
  72. const menuMark = ref(props.isMark)
  73. const nodes = ref([])
  74. const TreeIsColor = ref(props.isColor)
  75. //树的菜单相关变量
  76. const showDropdown = ref(false)
  77. const menuRefItem = ref(null)
  78. const x = ref(0);
  79. const y = ref(0);
  80. //监听
  81. watch(() => [
  82. props.data,
  83. props.parentNodes,
  84. props.menus,
  85. props.isMark,
  86. props.isColor,
  87. ], ([val,Nodes,menus,isMark,isColor]) => {
  88. datas.value = val
  89. menusData.value = menus
  90. menuMark.value = isMark
  91. TreeIsColor.value = isColor
  92. if (isDataType(Nodes)) {
  93. parent.value = Nodes
  94. setDatasToNodes()
  95. } else {
  96. parent.value = {}
  97. }
  98. })
  99. //渲染完成
  100. onMounted(()=> {
  101. if (isDataType(props.parentNodes)) {
  102. parent.value = props.parentNodes
  103. setDatasToNodes()
  104. } else {
  105. parent.value = {}
  106. }
  107. })
  108. //检查数据类型
  109. const isDataType = (data) => {
  110. if (isObject(data)) {
  111. return Object.keys(data).length !== 0;
  112. } else {
  113. return false
  114. }
  115. }
  116. //处理为node节点类型的数据
  117. const setDatasToNodes = () => {
  118. let nodesArr = [];
  119. const {key,label,children} = props.format
  120. const deepData = datas.value
  121. for (let i = 0; i < deepData.length; i++) {
  122. let childNodes = deepData[i][children] //子级数据
  123. let ifChildren = !!(childNodes && childNodes.length > 0);
  124. nodesArr.push({
  125. childNodes: childNodes, //子节点原始数据
  126. childrenNodes: [], //子节点node封装数据
  127. data: deepData[i], //节点主要数据
  128. parentNodes: parent.value, //父节点数据
  129. expanded: ifChildren || deepData[i]['expanded'] || false, //是否展开
  130. isExpand: ifChildren || deepData[i]['isExpand'] || false, //是否能展开
  131. isLeaf: deepData[i]['isLeaf'] || false, //是否为最后一节
  132. loading: deepData[i]['loading'] || false, //加载状态
  133. colorStatus: deepData[i]['colorStatus'] || 1, //颜色状态
  134. key: deepData[i][key], //节点的key
  135. label: deepData[i][label] //节点显示的名称
  136. })
  137. }
  138. nodes.value = nodesArr //渲染使用
  139. parent.value['childrenNodes'] = nodes.value //把当前节点数据,存入父节点的数据里。
  140. }
  141. const emit = defineEmits(['expandClick','nodeMouseover','nodeMouseout', 'nodeClick', 'nodeDblClick', 'menuClick'])
  142. //下级数据是否存在
  143. const isChildren = (item) => {
  144. return !!(item.childNodes && item.childNodes.length > 0);
  145. }
  146. //是否显示展开和隐藏按钮
  147. const isExpanded = (item) => {
  148. let ifChildren = isChildren(item);
  149. if (isBoolean(item['expanded'])) {
  150. let res = !!(item['expanded'] && ifChildren);
  151. item['expanded'] = res;
  152. return res;
  153. } else {
  154. item['expanded'] = false;
  155. return false;
  156. }
  157. }
  158. //处理展开和隐藏的样式
  159. const ifExpanded = (item) => {
  160. let ifChildren = isChildren(item);
  161. if (isBoolean(item['isExpand'])) {
  162. let res = !!(item['isExpand'] && ifChildren);
  163. item['isExpand'] = res;
  164. return res;
  165. } else {
  166. item['isExpand'] = ifChildren;
  167. return ifChildren;
  168. }
  169. }
  170. //展开和隐藏按钮被点击
  171. const expandedClick = (item) => {
  172. emit('expandClick', {
  173. node: item,
  174. data: item.data
  175. })
  176. }
  177. const ChildrenExpandedClick = ({node,data}) => {
  178. emit('expandClick', {node,data})
  179. }
  180. //鼠标左键单击事件
  181. const nodeLabelClick = (item) => {
  182. emit('nodeClick', {
  183. node: item,
  184. data: item.data
  185. })
  186. }
  187. const ChildrenNodeLabelClick = ({node,data}) => {
  188. emit('nodeClick', {node,data})
  189. }
  190. //双击事件
  191. const nodeLabelDblClick = (item) => {
  192. emit('nodeDblClick', {
  193. node: item,
  194. data: item.data
  195. })
  196. }
  197. const ChildrenNodeLabelDblClick = ({node,data}) => {
  198. emit('nodeDblClick', {node,data})
  199. }
  200. //鼠标右键事件
  201. const nodeLabelContextMenu = (e,item) => {
  202. const rows = menusData.value || [];
  203. if (rows.length > 0) {
  204. e.preventDefault();
  205. menuRefItem.value = item;
  206. if (menuMark.value) {
  207. setMenuMarkVal(rows, item)
  208. }
  209. nextTick().then(() => {
  210. showDropdown.value = true;
  211. x.value = e.clientX;
  212. y.value = e.clientY;
  213. });
  214. }
  215. }
  216. //设置菜单标记状态
  217. const setMenuMarkVal = (rows,item) => {
  218. for (let i = 0; i < rows.length; i++) {
  219. if (rows[i].key === 'mark' || rows[i].key === 'cancel_mark') {
  220. if (item?.data?.isFirst) {
  221. menusData.value[i].label = '取消标记为首件';
  222. menusData.value[i].key = 'cancel_mark';
  223. menusData.value[i].icon = hIconJs({
  224. name: 'grade', fill: true
  225. });
  226. } else {
  227. menusData.value[i].label = '标记为首件';
  228. menusData.value[i].key = 'mark';
  229. menusData.value[i].icon = hIconJs({name: 'grade'});
  230. }
  231. break;
  232. }
  233. }
  234. }
  235. const onClickoutside = () => {
  236. menuRefItem.value = null;
  237. showDropdown.value = false;
  238. }
  239. //鼠标右键菜单被点击
  240. const handleMenuSelect = (key) => {
  241. const item = menuRefItem.value;
  242. showDropdown.value = false;
  243. emit('menuClick', {
  244. key,
  245. node: item,
  246. data: item.data
  247. })
  248. }
  249. //菜单被点击
  250. const ChildrenCpoverMenuClick = ({key,node,data}) => {
  251. emit('menuClick', {key,node,data})
  252. }
  253. </script>
  254. <style lang="scss" scoped>
  255. @import "style";
  256. </style>