tui-cascade-selection.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <view class="gui-cascade-selection">
  3. <scroll-view scroll-x scroll-with-animation :scroll-into-view="scrollViewId"
  4. :style="{ backgroundColor: headerBgColor }" class="gui-bottom-line"
  5. :class="{ 'gui-btm-none': !headerLine }">
  6. <view class="gui-selection-header" :style="{ height: tabsHeight, backgroundColor: backgroundColor }">
  7. <block v-for="(item, iindex) in selectedArr" :key="iindex">
  8. <view class="gui-header-item" :class="{ 'gui-font-bold': iindex === currentTab && bold }"
  9. :style="{ color: iindex === currentTab ? activeColor : color, fontSize: size + 'rpx' }"
  10. :id="`id_${iindex}`" @tap.stop="swichNav" :data-current="iindex">
  11. {{ item.text }}
  12. <view class="gui-active-line" :style="{ backgroundColor: lineColor }"
  13. v-if="iindex === currentTab && showLine"></view>
  14. </view>
  15. </block>
  16. </view>
  17. </scroll-view>
  18. <swiper class="gui-selection-list" :current="currentTab" duration="300" @change="switchTab"
  19. :style="{ height: height, backgroundColor: backgroundColor }">
  20. <block v-for="(item, iindex) in selectedArr" :key="iindex">
  21. <swiper-item>
  22. <scroll-view scroll-y :scroll-into-view="item.scrollViewId" class="gui-selection-item"
  23. :style="{ height: height }">
  24. <view class="gui-first-item" :style="{ height: firstItemTop }"></view>
  25. <view class="gui-selection-cell" :style="{ padding: padding, backgroundColor: backgroundColor }"
  26. :id="`id_${subIndex}`" v-for="(subItem, subIndex) in item.list" :key="subIndex"
  27. @tap="change(iindex, subIndex, subItem)">
  28. <icon type="success_no_circle" v-if="item.index === subIndex" :color="checkMarkColor"
  29. :size="checkMarkSize" class="gui-icon-success"></icon>
  30. <image :src="subItem.src" v-if="subItem.src" class="gui-cell-img"
  31. :style="{ width: imgWidth, height: imgHeight, borderRadius: radius }"></image>
  32. <view class="gui-cell-title"
  33. :class="{ 'gui-font-bold': item.index === subIndex && textBold, 'gui-flex-shrink': nowrap }"
  34. :style="{ color: item.index === subIndex ? textActiveColor : textColor, fontSize: textSize + 'rpx' }">
  35. {{ subItem.text }}
  36. </view>
  37. <view class="gui-cell-sub_title"
  38. :style="{ color: subTextColor, fontSize: subTextSize + 'rpx' }" v-if="subItem.subText">
  39. {{ subItem.subText }}</view>
  40. </view>
  41. </scroll-view>
  42. </swiper-item>
  43. </block>
  44. </swiper>
  45. </view>
  46. </template>
  47. <script>
  48. export default {
  49. name: 'tuiCascadeSelection',
  50. props: {
  51. /**
  52. * 如果下一级是请求返回,则为第一级数据,否则所有数据
  53. * 数据格式
  54. [{
  55. src: "",
  56. text: "",
  57. subText: "",
  58. value: 0,
  59. children:[{
  60. text: "",
  61. subText: "",
  62. value: 0,
  63. children:[]
  64. }]
  65. }]
  66. * */
  67. itemList: {
  68. type: Array,
  69. default: () => {
  70. return [];
  71. }
  72. },
  73. /*
  74. 初始化默认选中数据
  75. [{
  76. text: "",//选中text
  77. subText: '',//选中subText
  78. value: '',//选中value
  79. src: '', //选中src,没有则传空或不传
  80. index: 0, //选中数据在当前layer索引
  81. list: [{src: "", text: "", subText: "", value: 101}] //所有layer数据集合
  82. }];
  83. */
  84. defaultItemList: {
  85. type: Array,
  86. value: []
  87. },
  88. //是否显示header底部细线
  89. headerLine: {
  90. type: Boolean,
  91. default: true
  92. },
  93. //header背景颜色
  94. headerBgColor: {
  95. type: String,
  96. default: '#FFFFFF'
  97. },
  98. //顶部标签栏高度
  99. tabsHeight: {
  100. type: String,
  101. default: '88rpx'
  102. },
  103. //默认显示文字
  104. text: {
  105. type: String,
  106. default: '请选择'
  107. },
  108. //tabs 文字大小
  109. size: {
  110. type: Number,
  111. default: 28
  112. },
  113. //tabs 文字颜色
  114. color: {
  115. type: String,
  116. default: '#555'
  117. },
  118. //选中颜色
  119. activeColor: {
  120. type: String,
  121. default: '#5677fc'
  122. },
  123. //选中后文字加粗
  124. bold: {
  125. type: Boolean,
  126. default: true
  127. },
  128. //选中后是否显示底部线条
  129. showLine: {
  130. type: Boolean,
  131. default: true
  132. },
  133. //线条颜色
  134. lineColor: {
  135. type: String,
  136. default: '#5677fc'
  137. },
  138. //icon 大小
  139. checkMarkSize: {
  140. type: Number,
  141. default: 15
  142. },
  143. //icon 颜色
  144. checkMarkColor: {
  145. type: String,
  146. default: '#5677fc'
  147. },
  148. //item 图片宽度
  149. imgWidth: {
  150. type: String,
  151. default: '40rpx'
  152. },
  153. //item 图片高度
  154. imgHeight: {
  155. type: String,
  156. default: '40rpx'
  157. },
  158. //图片圆角
  159. radius: {
  160. type: String,
  161. default: '50%'
  162. },
  163. //item text颜色
  164. textColor: {
  165. type: String,
  166. default: '#333'
  167. },
  168. textActiveColor: {
  169. type: String,
  170. default: '#333'
  171. },
  172. //选中后字体是否加粗
  173. textBold: {
  174. type: Boolean,
  175. default: true
  176. },
  177. //item text字体大小
  178. textSize: {
  179. type: Number,
  180. default: 28
  181. },
  182. //text 是否不换行
  183. nowrap: {
  184. type: Boolean,
  185. default: false
  186. },
  187. //item subText颜色
  188. subTextColor: {
  189. type: String,
  190. default: '#999'
  191. },
  192. //item subText字体大小
  193. subTextSize: {
  194. type: Number,
  195. default: 24
  196. },
  197. // item padding
  198. padding: {
  199. type: String,
  200. default: '20rpx 30rpx'
  201. },
  202. //占位高度,第一条数据距离顶部距离
  203. firstItemTop: {
  204. type: String,
  205. default: '20rpx'
  206. },
  207. //swiper 高度
  208. height: {
  209. type: String,
  210. default: '300px'
  211. },
  212. //item swiper 内容部分背景颜色
  213. backgroundColor: {
  214. type: String,
  215. default: '#FFFFFF'
  216. },
  217. //子集数据是否请求返回(默认false,一次性返回所有数据)
  218. request: {
  219. type: Boolean,
  220. default: false
  221. },
  222. //子级数据(当有改变时,默认当前选中项新增子级数据,request=true时生效)
  223. receiveData: {
  224. type: Array,
  225. default: () => {
  226. return [];
  227. }
  228. },
  229. //改变值则重置数据
  230. reset: {
  231. type: [Number, String],
  232. default: 0
  233. },
  234. joint: {
  235. type: "",
  236. default: " "
  237. }
  238. },
  239. watch: {
  240. itemList(val) {
  241. this.initData(val, -1);
  242. },
  243. receiveData(val) {
  244. this.subLevelData(val, this.currentTab);
  245. },
  246. reset() {
  247. this.initData(this.itemList, -1);
  248. }
  249. },
  250. created() {
  251. let defaultItemList = this.defaultItemList || [];
  252. if (defaultItemList.length > 0) {
  253. defaultItemList.map(item => {
  254. item.scrollViewId = `id_${item.index}`;
  255. });
  256. this.selectedArr = defaultItemList;
  257. this.currentTab = defaultItemList.length - 1;
  258. this.$nextTick(() => {
  259. this.checkCor();
  260. });
  261. } else {
  262. this.initData(this.itemList, -1);
  263. }
  264. },
  265. data() {
  266. return {
  267. currentTab: 0,
  268. //tab栏scrollview滚动的位置
  269. scrollViewId: 'id__1',
  270. selectedArr: []
  271. };
  272. },
  273. methods: {
  274. initData(data, layer) {
  275. if (!data || data.length === 0) return;
  276. if (this.request) {
  277. //第一级数据
  278. this.subLevelData(data, layer);
  279. } else {
  280. let selectedValue = this.selectedValue || {};
  281. if (selectedValue.type) {
  282. this.setDefaultData(selectedValue);
  283. } else {
  284. this.subLevelData(this.getItemList(layer, -1), layer);
  285. }
  286. }
  287. },
  288. removeChildren(data) {
  289. let list = data.map(item => {
  290. delete item['children'];
  291. return item;
  292. });
  293. return list;
  294. },
  295. getItemList(layer, index) {
  296. let list = [];
  297. let arr = JSON.parse(JSON.stringify(this.itemList));
  298. if (layer == -1) {
  299. list = this.removeChildren(arr);
  300. } else {
  301. let value = this.selectedArr[0].index;
  302. value = value == -1 ? index : value;
  303. list = arr[value].children || [];
  304. if (layer > 0) {
  305. for (let i = 1; i < layer + 1; i++) {
  306. let val = layer === i ? index : this.selectedArr[i].index;
  307. list = list[val].children || [];
  308. if (list.length === 0) break;
  309. }
  310. }
  311. list = this.removeChildren(list);
  312. }
  313. return list;
  314. },
  315. //滚动切换
  316. switchTab: function(e) {
  317. this.currentTab = e.detail.current;
  318. // console.log(this.selectedArr)
  319. this.checkCor();
  320. },
  321. //点击标题切换当
  322. swichNav: function(e) {
  323. // console.log("=====<", e)
  324. let cur = e.currentTarget.dataset.current;
  325. if (this.currentTab != cur) {
  326. this.currentTab = cur;
  327. }
  328. },
  329. checkCor: function() {
  330. let item = this.selectedArr[this.currentTab];
  331. item.scrollViewId = 'id__1';
  332. this.$nextTick(() => {
  333. setTimeout(() => {
  334. let val = item.index < 2 ? 0 : Number(item.index - 2);
  335. item.scrollViewId = `id_${val}`;
  336. }, 2);
  337. });
  338. if (this.currentTab > 1) {
  339. this.scrollViewId = `id_${this.currentTab - 1}`;
  340. } else {
  341. this.scrollViewId = `id_0`;
  342. }
  343. },
  344. change(index, subIndex, subItem) {
  345. let item = this.selectedArr[index];
  346. if (item.index == subIndex) {
  347. console.log('没有变化')
  348. console.log('cascadeChange:', index, subIndex, subItem)
  349. // return;
  350. }
  351. item.index = subIndex;
  352. item.text = subItem.text;
  353. item.value = subItem.value;
  354. item.subText = subItem.subText || '';
  355. item.src = subItem.src || '';
  356. if (!this.request) {
  357. let data = this.getItemList(index, subIndex);
  358. this.subLevelData(data, index);
  359. }
  360. let text = '';
  361. let value = '';
  362. let resultArr = JSON.parse(JSON.stringify(this.selectedArr));
  363. const result = resultArr.filter((item, i) => {
  364. return i <= index
  365. }).map(item => {
  366. text ? text += this.joint + item.text : text += item.text;
  367. value ? value += "," + item.value : value += item.value;
  368. delete item['list'];
  369. //delete item['index'];
  370. delete item['scrollViewId'];
  371. return item;
  372. });
  373. this.$emit('change', {
  374. result: result,
  375. layer: index,
  376. subIndex: subIndex,
  377. fullvalue: value,
  378. fulltext: text,
  379. ...subItem
  380. });
  381. },
  382. //新增子级数据时处理
  383. subLevelData(data, layer) {
  384. // console.log(data, layer)
  385. if (!data || data.length === 0) {
  386. if (layer == -1) return;
  387. //完成选择
  388. // console.log("====>", data, layer)
  389. this.selectedArr = this.selectedArr.slice(0, layer + 1);
  390. } else {
  391. //重置数据( >layer层级)
  392. let item = [{
  393. text: this.text,
  394. subText: '',
  395. value: '',
  396. src: '',
  397. index: -1,
  398. scrollViewId: 'id__1',
  399. list: data
  400. }];
  401. if (layer == -1) {
  402. this.selectedArr = item;
  403. } else {
  404. let retainArr = this.selectedArr.slice(0, layer + 1);
  405. this.selectedArr = retainArr.concat(item);
  406. }
  407. this.$nextTick(() => {
  408. this.currentTab = this.selectedArr.length - 1;
  409. });
  410. }
  411. let text = '';
  412. let value = "";
  413. const resultArr = JSON.parse(JSON.stringify(this.selectedArr));
  414. const result = resultArr.filter(item => {
  415. return item.index >= 0
  416. }).map(item => {
  417. text ? text += this.joint + item.text : text += item.text;
  418. value ? value += "," + item.value : value += item.value;
  419. delete item['list'];
  420. //delete item['index'];
  421. delete item['scrollViewId'];
  422. return item;
  423. });
  424. let lastItem = result[result.length - 1] || {};
  425. // console.log(lastItem)
  426. // this.$emit('change', {
  427. // layer: layer,
  428. // result: result,
  429. // value: lastItem.value,
  430. // text: text,
  431. // subText: lastItem.subText,
  432. // src: lastItem.src
  433. // });
  434. if (!data || data.length === 0) {
  435. this.$emit('complete', {
  436. result: result,
  437. value: lastItem.value,
  438. fullvalue: value,
  439. fulltext: text,
  440. text: lastItem.text,
  441. subText: lastItem.subText,
  442. src: lastItem.src
  443. });
  444. }
  445. }
  446. }
  447. };
  448. </script>
  449. <style scoped>
  450. .gui-cascade-selection {
  451. width: 100%;
  452. box-sizing: border-box;
  453. }
  454. .gui-selection-header {
  455. width: 100%;
  456. display: flex;
  457. align-items: center;
  458. position: relative;
  459. box-sizing: border-box;
  460. }
  461. .gui-bottom-line {
  462. position: relative;
  463. }
  464. .gui-bottom-line::after {
  465. width: 100%;
  466. content: '';
  467. position: absolute;
  468. border-bottom: 1rpx solid #eaeef1;
  469. -webkit-transform: scaleY(0.5) translateZ(0);
  470. transform: scaleY(0.5) translateZ(0);
  471. transform-origin: 0 100%;
  472. bottom: 0;
  473. right: 0;
  474. left: 0;
  475. }
  476. .gui-btm-none::after {
  477. border-bottom: 0 !important;
  478. }
  479. .gui-header-item {
  480. max-width: 240rpx;
  481. padding: 15rpx 30rpx;
  482. box-sizing: border-box;
  483. flex-shrink: 0;
  484. overflow: hidden;
  485. white-space: nowrap;
  486. text-overflow: ellipsis;
  487. position: relative;
  488. }
  489. .gui-font-bold {
  490. font-weight: bold;
  491. }
  492. .gui-active-line {
  493. width: 60rpx;
  494. height: 6rpx;
  495. border-radius: 4rpx;
  496. position: absolute;
  497. bottom: 0;
  498. right: 0;
  499. left: 50%;
  500. transform: translateX(-50%);
  501. }
  502. .gui-selection-cell {
  503. width: 100%;
  504. box-sizing: border-box;
  505. display: flex;
  506. align-items: center;
  507. }
  508. .gui-icon-success {
  509. margin-right: 12rpx;
  510. }
  511. .gui-cell-img {
  512. margin-right: 12rpx;
  513. flex-shrink: 0;
  514. }
  515. .gui-cell-title {
  516. word-break: break-all;
  517. }
  518. .gui-flex-shrink {
  519. flex-shrink: 0;
  520. }
  521. .gui-font-bold {
  522. font-weight: bold;
  523. }
  524. .gui-cell-sub_title {
  525. margin-left: 20rpx;
  526. word-break: break-all;
  527. }
  528. .gui-first-item {
  529. width: 100%;
  530. }
  531. </style>