ly-tree.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <template>
  2. <view>
  3. <template v-if="showLoading">
  4. <view class="ly-loader ly-flex-center">
  5. <view class="ly-loader-inner">加载中...</view>
  6. </view>
  7. </template>
  8. <template v-else>
  9. <view v-if="isEmpty" class="ly-empty bg-white padding" style="margin-top:0px">{{emptyText}}</view>
  10. <view class="ly-tree " :class="{ 'ly-tree--highlight-current': highlightCurrent }" role="tree" name="LyTreeExpand">
  11. <ly-tree-node class="text-black" v-for="nodeId in childNodesId"
  12. :nodeId="nodeId"
  13. :render-after-expand="renderAfterExpand"
  14. :show-checkbox="showCheckbox"
  15. :show-radio="showRadio"
  16. :check-only-leaf="checkOnlyLeaf"
  17. :key="getNodeKey(nodeId)"
  18. :indent="indent"
  19. :icon-class="iconClass">
  20. </ly-tree-node>
  21. </view>
  22. </template>
  23. </view>
  24. </template>
  25. <script>
  26. import TreeStore from './model/tree-store.js';
  27. import {getNodeKey} from './tool/util.js';
  28. import LyTreeNode from './ly-tree-node.vue';
  29. export default {
  30. name: 'LyTree',
  31. componentName: 'LyTree',
  32. components: {
  33. LyTreeNode
  34. },
  35. data() {
  36. return {
  37. elId: `ly_${Math.ceil(Math.random() * 10e5).toString(36)}`,
  38. store: {
  39. ready: false
  40. },
  41. currentNode: null,
  42. childNodesId: []
  43. };
  44. },
  45. provide() {
  46. return {
  47. tree: this
  48. }
  49. },
  50. props: {
  51. // 展示数据
  52. treeData: Array,
  53. // 自主控制loading加载,避免数据还没获取到的空档出现“暂无数据”字样
  54. ready: {
  55. type: Boolean,
  56. default: true
  57. },
  58. // 内容为空的时候展示的文本
  59. emptyText: {
  60. type: String,
  61. default: '暂无数据'
  62. },
  63. // 是否在第一次展开某个树节点后才渲染其子节点
  64. renderAfterExpand: {
  65. type: Boolean,
  66. default: true
  67. },
  68. // 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
  69. nodeKey: String,
  70. // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
  71. checkStrictly: Boolean,
  72. // 是否默认展开所有节点
  73. defaultExpandAll: Boolean,
  74. // 切换全部展开、全部折叠
  75. toggleExpendAll: Boolean,
  76. // 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点
  77. expandOnClickNode: {
  78. type: Boolean,
  79. default: true
  80. },
  81. // 选中的时候展开节点
  82. expandOnCheckNode: {
  83. type: Boolean,
  84. default: false
  85. },
  86. // 是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点
  87. checkOnClickNode: Boolean,
  88. checkDescendants: {
  89. type: Boolean,
  90. default: false
  91. },
  92. // 展开子节点的时候是否自动展开父节点
  93. autoExpandParent: {
  94. type: Boolean,
  95. default: true
  96. },
  97. // 默认勾选的节点的 key 的数组
  98. defaultCheckedKeys: Array,
  99. // 默认展开的节点的 key 的数组
  100. defaultExpandedKeys: Array,
  101. // 当前选中的节点
  102. currentNodeKey: [String, Number],
  103. // 是否最后一层叶子节点才显示单选/多选框
  104. checkOnlyLeaf: {
  105. type: Boolean,
  106. default: false
  107. },
  108. // 节点是否可被选择
  109. showCheckbox: {
  110. type: Boolean,
  111. default: false
  112. },
  113. // 节点单选
  114. showRadio: {
  115. type: Boolean,
  116. default: false
  117. },
  118. // 配置选项
  119. props: {
  120. type: [Object, Function],
  121. default () {
  122. return {
  123. children: 'children', // 指定子树为节点对象的某个属性值
  124. label: 'name', // 指定节点标签为节点对象的某个属性值
  125. disabled: 'disabled' // 指定节点选择框是否禁用为节点对象的某个属性值
  126. };
  127. }
  128. },
  129. // 是否懒加载子节点,需与 load 方法结合使用
  130. lazy: {
  131. type: Boolean,
  132. default: false
  133. },
  134. // 是否高亮当前选中节点,默认值是 false
  135. highlightCurrent: Boolean,
  136. // 加载子树数据的方法,仅当 lazy 属性为true 时生效
  137. load: Function,
  138. // 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
  139. filterNodeMethod: Function,
  140. // 搜索时是否展示匹配项的所有子节点
  141. childVisibleForFilterNode: {
  142. type: Boolean,
  143. default: false
  144. },
  145. // 是否每次只打开一个同级树节点展开
  146. accordion: Boolean,
  147. // 相邻级节点间的水平缩进,单位为像素
  148. indent: {
  149. type: Number,
  150. default: 18
  151. },
  152. // 自定义树节点的展开图标
  153. iconClass: String,
  154. // 是否显示节点图标,如果配置为true,需要配置props中对应的图标属性名称
  155. showNodeIcon: {
  156. type: Boolean,
  157. default: false
  158. }
  159. },
  160. computed: {
  161. children: {
  162. set(value) {
  163. this.treeData = value;
  164. },
  165. get() {
  166. return this.treeData;
  167. }
  168. },
  169. isEmpty() {
  170. if (this.store.root) {
  171. const childNodes = this.store.root.getChildNodes(this.childNodesId);
  172. return !childNodes || childNodes.length === 0 || childNodes.every(({visible}) => !visible);
  173. }
  174. return true;
  175. },
  176. showLoading() {
  177. return !(this.store.ready && this.ready);
  178. }
  179. },
  180. watch: {
  181. toggleExpendAll(newVal) {
  182. this.store.toggleExpendAll(newVal);
  183. },
  184. defaultCheckedKeys(newVal) {
  185. this.store.setDefaultCheckedKey(newVal);
  186. },
  187. defaultExpandedKeys(newVal) {
  188. this.store.defaultExpandedKeys = newVal;
  189. this.store.setDefaultExpandedKeys(newVal);
  190. },
  191. treeData(newVal) {
  192. this.store.setData(newVal);
  193. },
  194. checkStrictly(newVal) {
  195. this.store.checkStrictly = newVal || this.checkOnlyLeaf;
  196. },
  197. 'store.root.childNodesId'(newVal) {
  198. this.childNodesId = newVal;
  199. },
  200. childNodesId(){
  201. this.$nextTick(() => {
  202. this.$emit('ly-tree-render-completed');
  203. });
  204. }
  205. },
  206. methods: {
  207. /*
  208. * @description 对树节点进行筛选操作
  209. * @method filter
  210. * @param {all} value 在 filter-node-method 中作为第一个参数
  211. * @param {Object} data 搜索指定节点的节点数据,不传代表搜索所有节点,假如要搜索A节点下面的数据,那么nodeData代表treeData中A节点的数据
  212. */
  213. filter(value, data) {
  214. if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
  215. this.store.filter(value, data);
  216. },
  217. /*
  218. * @description 获取节点的唯一标识符
  219. * @method getNodeKey
  220. * @param {String, Number} nodeId
  221. * @return {String, Number} 匹配到的数据中的某一项数据
  222. */
  223. getNodeKey(nodeId) {
  224. let node = this.store.root.getChildNodes([nodeId])[0];
  225. return getNodeKey(this.nodeKey, node.data);
  226. },
  227. /*
  228. * @description 获取节点路径
  229. * @method getNodePath
  230. * @param {Object} data 节点数据
  231. * @return {Array} 路径数组
  232. */
  233. getNodePath(data) {
  234. return this.store.getNodePath(data);
  235. },
  236. /*
  237. * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
  238. * @method getCheckedNodes
  239. * @param {Boolean} leafOnly 是否只是叶子节点,默认false
  240. * @param {Boolean} includeHalfChecked 是否包含半选节点,默认false
  241. * @return {Array} 目前被选中的节点所组成的数组
  242. */
  243. getCheckedNodes(leafOnly, includeHalfChecked) {
  244. return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
  245. },
  246. /*
  247. * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
  248. * @method getCheckedKeys
  249. * @param {Boolean} leafOnly 是否只是叶子节点,默认false,若为 true 则仅返回被选中的叶子节点的 keys
  250. * @return {Array} 目前被选中的节点所组成的数组
  251. */
  252. getCheckedKeys(leafOnly) {
  253. return this.store.getCheckedKeys(leafOnly);
  254. },
  255. /*
  256. * @description 获取当前被选中节点的 data,若没有节点被选中则返回 null
  257. * @method getCurrentNode
  258. * @return {Object} 当前被选中节点的 data,若没有节点被选中则返回 null
  259. */
  260. getCurrentNode() {
  261. const currentNode = this.store.getCurrentNode();
  262. return currentNode ? currentNode.data : null;
  263. },
  264. /*
  265. * @description 获取当前被选中节点的 key,若没有节点被选中则返回 null
  266. * @method getCurrentKey
  267. * @return {all} 当前被选中节点的 key, 若没有节点被选中则返回 null
  268. */
  269. getCurrentKey() {
  270. const currentNode = this.getCurrentNode();
  271. return currentNode ? currentNode[this.nodeKey] : null;
  272. },
  273. /*
  274. * @description 设置全选/取消全选
  275. * @method setCheckAll
  276. * @param {Boolean} isCheckAll 选中状态,默认为true
  277. */
  278. setCheckAll(isCheckAll = true) {
  279. if (this.showRadio) throw new Error('You set the "show-radio" property, so you cannot select all nodes');
  280. if (!this.showCheckbox) console.warn('You have not set the property "show-checkbox". Please check your settings');
  281. this.store.setCheckAll(isCheckAll);
  282. },
  283. /*
  284. * @description 设置目前勾选的节点
  285. * @method setCheckedNodes
  286. * @param {Array} nodes 接收勾选节点数据的数组
  287. * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
  288. */
  289. setCheckedNodes(nodes, leafOnly) {
  290. this.store.setCheckedNodes(nodes, leafOnly);
  291. },
  292. /*
  293. * @description 通过 keys 设置目前勾选的节点
  294. * @method setCheckedKeys
  295. * @param {Array} keys 勾选节点的 key 的数组
  296. * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
  297. */
  298. setCheckedKeys(keys, leafOnly) {
  299. if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
  300. this.store.setCheckedKeys(keys, leafOnly);
  301. },
  302. /*
  303. * @description 通过 key / data 设置某个节点的勾选状态
  304. * @method setChecked
  305. * @param {all} data 勾选节点的 key 或者 data
  306. * @param {Boolean} checked 节点是否选中
  307. * @param {Boolean} deep 是否设置子节点 ,默认为 false
  308. */
  309. setChecked(data, checked, deep) {
  310. this.store.setChecked(data, checked, deep);
  311. },
  312. /*
  313. * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前半选中的节点所组成的数组
  314. * @method getHalfCheckedNodes
  315. * @return {Array} 目前半选中的节点所组成的数组
  316. */
  317. getHalfCheckedNodes() {
  318. return this.store.getHalfCheckedNodes();
  319. },
  320. /*
  321. * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前半选中的节点的 key 所组成的数组
  322. * @method getHalfCheckedKeys
  323. * @return {Array} 目前半选中的节点的 key 所组成的数组
  324. */
  325. getHalfCheckedKeys() {
  326. return this.store.getHalfCheckedKeys();
  327. },
  328. /*
  329. * @description 通过 node 设置某个节点的当前选中状态
  330. * @method setCurrentNode
  331. * @param {Object} node 待被选节点的 node
  332. */
  333. setCurrentNode(node) {
  334. if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
  335. this.store.setUserCurrentNode(node);
  336. },
  337. /*
  338. * @description 通过 key 设置某个节点的当前选中状态
  339. * @method setCurrentKey
  340. * @param {all} key 待被选节点的 key,若为 null 则取消当前高亮的节点
  341. */
  342. setCurrentKey(key) {
  343. if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
  344. this.store.setCurrentNodeKey(key);
  345. },
  346. /*
  347. * @description 根据 data 或者 key 拿到 Tree 组件中的 node
  348. * @method getNode
  349. * @param {all} data 要获得 node 的 key 或者 data
  350. */
  351. getNode(data) {
  352. return this.store.getNode(data);
  353. },
  354. /*
  355. * @description 删除 Tree 中的一个节点
  356. * @method remove
  357. * @param {all} data 要删除的节点的 data 或者 node
  358. */
  359. remove(data) {
  360. this.store.remove(data);
  361. },
  362. /*
  363. * @description 为 Tree 中的一个节点追加一个子节点
  364. * @method append
  365. * @param {Object} data 要追加的子节点的 data
  366. * @param {Object} parentNode 子节点的 parent 的 data、key 或者 node
  367. */
  368. append(data, parentNode) {
  369. this.store.append(data, parentNode);
  370. },
  371. /*
  372. * @description 为 Tree 的一个节点的前面增加一个节点
  373. * @method insertBefore
  374. * @param {Object} data 要增加的节点的 data
  375. * @param {all} refNode 要增加的节点的后一个节点的 data、key 或者 node
  376. */
  377. insertBefore(data, refNode) {
  378. this.store.insertBefore(data, refNode);
  379. },
  380. /*
  381. * @description 为 Tree 的一个节点的后面增加一个节点
  382. * @method insertAfter
  383. * @param {Object} data 要增加的节点的 data
  384. * @param {all} refNode 要增加的节点的前一个节点的 data、key 或者 node
  385. */
  386. insertAfter(data, refNode) {
  387. this.store.insertAfter(data, refNode);
  388. },
  389. /*
  390. * @description 通过 keys 设置节点子元素
  391. * @method updateKeyChildren
  392. * @param {String, Number} key 节点 key
  393. * @param {Object} data 节点数据的数组
  394. */
  395. updateKeyChildren(key, data) {
  396. if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
  397. this.store.updateChildren(key, data);
  398. }
  399. },
  400. created() {
  401. this.isTree = true;
  402. let props = this.props;
  403. if (typeof this.props === 'function') props = this.props();
  404. if (typeof props !== 'object') throw new Error('props must be of object type.');
  405. this.store = new TreeStore({
  406. key: this.nodeKey,
  407. data: this.treeData,
  408. lazy: this.lazy,
  409. props: props,
  410. load: this.load,
  411. showCheckbox: this.showCheckbox,
  412. showRadio: this.showRadio,
  413. currentNodeKey: this.currentNodeKey,
  414. checkStrictly: this.checkStrictly || this.checkOnlyLeaf,
  415. checkDescendants: this.checkDescendants,
  416. defaultCheckedKeys: this.defaultCheckedKeys,
  417. defaultExpandedKeys: this.defaultExpandedKeys,
  418. autoExpandParent: this.autoExpandParent,
  419. defaultExpandAll: this.defaultExpandAll,
  420. filterNodeMethod: this.filterNodeMethod,
  421. childVisibleForFilterNode: this.childVisibleForFilterNode,
  422. showNodeIcon: this.showNodeIcon
  423. });
  424. this.childNodesId = this.store.root.childNodesId;
  425. },
  426. beforeDestroy() {
  427. if (this.accordion) {
  428. uni.$off(`${this.elId}-tree-node-expand`)
  429. }
  430. }
  431. };
  432. </script>