1
0

myMosoweTreeList.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. <!--
  2. 树形结构数据展示
  3. @Author: mosowe
  4. @Date:2023-05-11 11:17:58
  5. -->
  6. <template>
  7. <view class="" v-if="treeData.length">
  8. <view class="mosowe-tree-list-component" v-for="(item, index) in treeData" :key="index">
  9. <view class="mosowe-tree-title-wrap">
  10. <view class="mosowe-tree-title-icon">
  11. <view class="icon" v-if="item[children] && item[children].length" :class="{
  12. open: showChidren.includes(item[nodeKey]),
  13. close: !showChidren.includes(item[nodeKey])
  14. }" @click="toggleChildrens(item[nodeKey])"></view>
  15. </view>
  16. <view class="mosowe-tree-title-selection" :class="{
  17. disabled: item.disabled,
  18. 'part-select':
  19. item[children] &&
  20. treeToArray(JSON.parse(JSON.stringify(item[children])), children).filter((ci) =>
  21. selectionList.includes(ci[nodeKey])
  22. ).length > 0 &&
  23. !selectionList.includes(item[nodeKey]),
  24. 'all-select': selectionList.includes(item[nodeKey])
  25. }" v-if="multiple" @click="selectionClick(item)"></view>
  26. <view class="mosowe-tree-title-name" :class="{'mosowe-tree-title-name-disable':!item.enable}" @click.stop="nodeClick(item)">
  27. {{ item[text] }}
  28. </view>
  29. </view>
  30. <template v-if="item[children] && item[children].length">
  31. <template v-if="showChidren.indexOf(item[nodeKey]) > -1">
  32. <view class="mosowe-tree-children-wrap">
  33. <!-- <my-mosowe-tree-list v-model="selectionList" :treeData="item[children]" :text="text" :children="children"
  34. :multiple="multiple" :nodeKey="nodeKey" :defaultShowChildren="defaultShowChildren"
  35. :textClickToggle="textClickToggle" :accordion="accordion" :index="index + 1" :isOnceShow="onceShow"
  36. @nodeClick="nodeClick" @childCancel="childCancel(item)" @childChoosed="childChoosed(item)">
  37. {{ node[text] }}
  38. </my-mosowe-tree-list> -->
  39. <view style="text-align: left;line-height: 70rpx;" class="">
  40. <span style="font-weight: 400;display: inline-block;padding: 0rpx 20rpx;border: 1rpx solid #bdbdbe;margin: 10rpx 20rpx;font-size: 30rpx;border-radius: 10rpx;" v-for="(iitem,iindex) in item[children]" class="">
  41. {{iitem.text}}
  42. </span>
  43. </view>
  44. </view>
  45. </template>
  46. </template>
  47. </view>
  48. </view>
  49. </template>
  50. <script>
  51. export default {
  52. name: 'my-mosowe-tree-list',
  53. emits: ['nodeClick', 'childCancel', 'childChoosed', 'update:modelValue'],
  54. props: {
  55. modelValue: {
  56. type: Array,
  57. default: () => []
  58. },
  59. value: {
  60. type: Array,
  61. default: () => []
  62. },
  63. treeData: {
  64. // 数据
  65. type: Array,
  66. default: () => []
  67. },
  68. text: {
  69. // 显示的文案标题字段
  70. type: String,
  71. default: 'text'
  72. },
  73. nodeKey: {
  74. // 目标字段
  75. type: String,
  76. default: 'value'
  77. },
  78. children: {
  79. // 子集字段
  80. type: String,
  81. default: 'childrens'
  82. },
  83. multiple: {
  84. // 显示多选框
  85. type: Boolean,
  86. default: false
  87. },
  88. textClickToggle: {
  89. // 标题点击触发子集列表显隐
  90. type: Boolean,
  91. default: false
  92. },
  93. defaultShowChildren: {
  94. // 默认展开所有,或展开几级
  95. type: [Boolean, Number],
  96. default: false
  97. },
  98. accordion: {
  99. // 手风琴模式
  100. type: Boolean,
  101. default: false
  102. },
  103. index: {
  104. // 当前层级
  105. type: Number,
  106. default: 1
  107. },
  108. isOnceShow: {
  109. // 是否显示过了
  110. type: Boolean,
  111. default: false
  112. }
  113. },
  114. data() {
  115. return {
  116. showChidren: [],
  117. once: false,
  118. onceShow: false
  119. };
  120. },
  121. computed: {
  122. selectionList: {
  123. get() {
  124. let list = [];
  125. // TODO 兼容 vue2
  126. // #ifdef VUE2
  127. list = this.value;
  128. // #endif
  129. // TODO 兼容 vue3
  130. // #ifdef VUE3
  131. list = this.modelValue;
  132. // #endif
  133. if (!this.once && list.length !== 0) {
  134. // 只执行一次,初始化时
  135. const flatArr = this.getValueByKeyForObject({
  136. value: list,
  137. source: this.treeData,
  138. valueKey: this.nodeKey,
  139. targetKey: this.children,
  140. treeDataAll: false,
  141. treeChildKey: this.children
  142. })
  143. .flat(Infinity)
  144. .filter((item) => item)
  145. .map((item) => {
  146. return item[this.nodeKey];
  147. });
  148. this.once = true;
  149. const r = [...list, ...flatArr];
  150. return [...new Set(r)];
  151. }
  152. return list;
  153. },
  154. set(value) {
  155. // TODO 兼容 vue2
  156. // #ifdef VUE2
  157. this.$emit('input', value);
  158. // #endif
  159. // TODO 兼容 vue3
  160. // #ifdef VUE3
  161. this.$emit('update:modelValue', value);
  162. // #endif
  163. }
  164. }
  165. },
  166. watch: {
  167. defaultShowChildren: {
  168. handler() {
  169. if (!this.isOnceShow) {
  170. if (this.defaultShowChildren === true) {
  171. this.showChidren = this.treeToArray(
  172. JSON.parse(JSON.stringify(this.treeData)),
  173. this.children
  174. ).map((item) => item[this.nodeKey]);
  175. } else if (typeof this.defaultShowChildren === 'number') {
  176. if (this.index < this.defaultShowChildren) {
  177. const findIndex = (treeData, index) => {
  178. let r = [];
  179. treeData.forEach((item) => {
  180. r.push(item[this.nodeKey]);
  181. if (
  182. index < this.defaultShowChildren &&
  183. item[this.children] &&
  184. item[this.children].length
  185. ) {
  186. r.push(...findIndex(item[this.children], index + 1));
  187. }
  188. });
  189. return r;
  190. };
  191. const list = findIndex(JSON.parse(JSON.stringify(this.treeData)), 0);
  192. this.showChidren = list;
  193. }
  194. }
  195. let t = setTimeout(() => {
  196. clearTimeout(t);
  197. t = null;
  198. this.onceShow = this.index === 1 ? true : this.isOnceShow;
  199. }, 10);
  200. }
  201. },
  202. immediate: true
  203. }
  204. },
  205. methods: {
  206. nodeClick(e) {
  207. //this.$emit('nodeClick', e);
  208. console.log(e);
  209. this.toggleChildrens(e[this.nodeKey]);
  210. },
  211. // 选择
  212. selectionClick(node) {
  213. const list = [...this.selectionList];
  214. const value = node[this.nodeKey];
  215. if (node[this.children] && node[this.children].length) {
  216. // 此项有子集
  217. if (list.includes(value)) {
  218. // 已选->改取消选择
  219. const index = list.findIndex((item) => item === value);
  220. list.splice(index, 1);
  221. // 子集的也要取消掉
  222. const childrenData = JSON.parse(JSON.stringify(node[this.children]));
  223. this.treeToArray(childrenData, this.children).forEach((item) => {
  224. const findex = list.findIndex((fitem) => fitem === item[this.nodeKey]);
  225. list.splice(findex, 1);
  226. });
  227. // 父级也要取消
  228. this.$emit('childCancel');
  229. } else {
  230. // 加入已选
  231. list.push(value);
  232. // 子集的也要全部加入
  233. const childrenData = JSON.parse(JSON.stringify(node[this.children]));
  234. this.treeToArray(childrenData, this.children).forEach((item) => {
  235. list.push(item[this.nodeKey]);
  236. });
  237. // 父级看情况选择不选择
  238. this.$emit('childChoosed');
  239. }
  240. } else {
  241. // 没有子集
  242. if (list.includes(value)) {
  243. // 已选->改取消选择
  244. const index = list.findIndex((item) => item === value);
  245. list.splice(index, 1);
  246. // 父级也要取消
  247. this.$emit('childCancel');
  248. } else {
  249. // 加入已选
  250. list.push(value);
  251. // 父级看情况选择不选择
  252. this.$emit('childChoosed');
  253. }
  254. }
  255. this.selectionList = [...new Set(list)];
  256. },
  257. childChoosed(node) {
  258. let t = setTimeout(() => {
  259. clearTimeout(t);
  260. t = null;
  261. const list = [...this.selectionList];
  262. const value = node[this.nodeKey];
  263. let join = true;
  264. node[this.children].forEach((item) => {
  265. if (!list.includes(item[this.nodeKey])) {
  266. join = false;
  267. }
  268. });
  269. if (join) {
  270. list.push(value);
  271. }
  272. // 父级也要考虑下选择不
  273. this.selectionList = [...new Set(list)];
  274. this.$emit('childChoosed');
  275. }, 10);
  276. },
  277. childCancel(node) {
  278. let t = setTimeout(() => {
  279. clearTimeout(t);
  280. t = null;
  281. const list = [...this.selectionList];
  282. const value = node[this.nodeKey];
  283. const index = list.findIndex((item) => item === value);
  284. if (index > -1) {
  285. list.splice(index, 1);
  286. this.selectionList = [...new Set(list)];
  287. } // 父级也要取消
  288. this.$emit('childCancel');
  289. }, 10);
  290. },
  291. // 展开收起
  292. toggleChildrens(key) {
  293. if (this.showChidren.includes(key)) {
  294. const index = this.showChidren.findIndex((item) => item === key);
  295. this.showChidren.splice(index, 1);
  296. } else {
  297. if (this.accordion) {
  298. this.showChidren = [key];
  299. } else {
  300. this.showChidren.push(key);
  301. }
  302. }
  303. },
  304. // 树转数组
  305. treeToArray(nodes, childKey) {
  306. var r = [];
  307. if (Array.isArray(nodes)) {
  308. for (var i = 0, l = nodes.length; i < l; i++) {
  309. r.push(nodes[i]); // 取每项数据放入一个新数组
  310. if (Array.isArray(nodes[i][childKey]) && nodes[i][childKey].length > 0)
  311. // 若存在leaf则递归调用,把数据拼接到新数组中,并且删除该leaf
  312. r = r.concat(this.treeToArray(nodes[i][childKey]));
  313. delete nodes[i][childKey];
  314. }
  315. }
  316. return r;
  317. },
  318. getValueByKeyForObject(options) {
  319. if (!options) {
  320. return [];
  321. }
  322. const {
  323. value,
  324. source,
  325. valueKey,
  326. targetKey,
  327. treeDataAll = false,
  328. treeChildKey = 'leaf'
  329. } = options;
  330. let treePrefix = '';
  331. let result = [];
  332. if (!(value ?? '') || !source || !valueKey || !targetKey) {
  333. requiredKey(options, ['value', 'source', 'valueKey', 'targetKey']);
  334. return [];
  335. }
  336. let val = value;
  337. if (typeof value === 'string') {
  338. val = [value];
  339. }
  340. // 树数据处理
  341. const treeDeal = (data) => {
  342. if (data[treeChildKey]) {
  343. // 是个树
  344. let r = this.getValueByKeyForObject({
  345. ...options,
  346. source: data[treeChildKey]
  347. });
  348. if (treeDataAll && r.length) {
  349. // 需要返回父级数据
  350. treePrefix = data[targetKey];
  351. r = r.map((item) => {
  352. return treePrefix + '/' + item;
  353. });
  354. }
  355. result.push(...r);
  356. }
  357. };
  358. if (Object.prototype.toString.call(source) === '[object Object]') {
  359. // object类型,假设是树
  360. if (val.includes(source[valueKey])) {
  361. result.push(source[targetKey]);
  362. }
  363. treeDeal(source);
  364. } else {
  365. // 数组,也可能是数组树
  366. source.forEach((item) => {
  367. if (Object.prototype.toString.call(item) === '[object Object]') {
  368. // object类型,假设是树
  369. if (val.includes(item[valueKey])) {
  370. result.push(item[targetKey]);
  371. }
  372. treeDeal(item); // 树处理
  373. }
  374. if (Array.isArray(item)) {
  375. // 多维数组
  376. result.push(
  377. ...this.getValueByKeyForObject({
  378. ...options,
  379. source: item
  380. })
  381. );
  382. }
  383. });
  384. }
  385. return result;
  386. },
  387. requiredKey(obj, keys) {
  388. let r = [];
  389. keys.forEach((item) => {
  390. if (ZoIsEmpty(obj[item])) {
  391. console.error(`${item} is required!`);
  392. r.push(item);
  393. }
  394. });
  395. return r;
  396. }
  397. }
  398. };
  399. </script>
  400. <style scoped lang="scss">
  401. .mosowe-tree-list-component {
  402. font-size: 40rpx;
  403. color: #333;
  404. line-height: 70rpx;
  405. font-weight: 700;
  406. .mosowe-tree-title-wrap {
  407. margin-top: 10rpx;
  408. border-radius: 10rpx;
  409. border:1rpx solid #007aff;
  410. display: flex;
  411. align-items: center;
  412. .mosowe-tree-title-icon {
  413. flex: none;
  414. width: 20px;
  415. height: 20px;
  416. .icon {
  417. width: 100%;
  418. height: 100%;
  419. display: flex;
  420. position: relative;
  421. cursor: pointer;
  422. transition: all 0.3s;
  423. &::before {
  424. content: '';
  425. display: inline-block;
  426. width: 0;
  427. height: 0;
  428. border: 6px solid;
  429. border-color: transparent transparent transparent #999;
  430. margin: auto;
  431. margin-right: -2rpx;
  432. }
  433. &.open {
  434. transform: rotate(90deg);
  435. transform-origin: center;
  436. }
  437. }
  438. }
  439. .mosowe-tree-title-selection {
  440. flex: none;
  441. width: 30rpx;
  442. height: 30rpx;
  443. border: 1rpx solid #ccc;
  444. border-radius: 2rpx;
  445. margin-right: 18rpx;
  446. cursor: pointer;
  447. position: relative;
  448. &.part-select {
  449. background-color: $uni-color-primary;
  450. border-color: $uni-color-primary;
  451. &::after {
  452. content: '';
  453. display: block;
  454. width: 8px;
  455. height: 2px;
  456. background-color: #fff;
  457. border-radius: 2px;
  458. position: absolute;
  459. left: 50%;
  460. top: 50%;
  461. transform: translateX(-50%) translateY(-50%);
  462. }
  463. }
  464. &.all-select {
  465. background-color: $uni-color-primary;
  466. border-color: $uni-color-primary;
  467. &::after {
  468. content: '';
  469. display: block;
  470. width: 6px;
  471. height: 3px;
  472. border: 2px solid;
  473. border-color: transparent transparent #fff #fff;
  474. border-radius: 2px;
  475. position: absolute;
  476. left: 50%;
  477. top: 50%;
  478. transform: translateX(-50%) translateY(-80%) rotate(-45deg);
  479. }
  480. }
  481. &.disabled {
  482. background-color: rgba(0, 0, 0, 0.2);
  483. cursor: no-drop;
  484. }
  485. }
  486. .mosowe-tree-title-name {
  487. // flex: 1;
  488. cursor: pointer;
  489. }
  490. .mosowe-tree-title-name-disable {
  491. // flex: 1;
  492. color: #999;
  493. cursor: pointer;
  494. }
  495. }
  496. .mosowe-tree-children-wrap {
  497. padding-left: 10px;
  498. }
  499. }
  500. </style>