<template> <view class="tui-cascade-selection"> <scroll-view scroll-x scroll-with-animation :scroll-into-view="scrollViewId" :style="{ backgroundColor: headerBgColor }" class="tui-bottom-line" :class="{ 'tui-btm-none': !headerLine }" > <view class="tui-selection-header" :style="{ height: tabsHeight, backgroundColor: backgroundColor }"> <view class="tui-header-item" :class="{ 'tui-font-bold': index === currentTab && bold }" :style="{ color: index === currentTab ? activeColor : color, fontSize: size + 'rpx' }" :id="`id_${index}`" @tap.stop="swichNav" :data-current="index" v-for="(item, index) in selectedArr" :key="index" > {{ item.text }} <view class="tui-active-line" :style="{ backgroundColor: lineColor }" v-if="index === currentTab && showLine"></view> </view> </view> </scroll-view> <swiper class="tui-selection-list" :current="currentTab" duration="300" @change="switchTab" :style="{ height: height, backgroundColor: backgroundColor }"> <swiper-item v-for="(item, index) in selectedArr" :key="index"> <scroll-view scroll-y :scroll-into-view="item.scrollViewId" class="tui-selection-item" :style="{ height: height }"> <view class="tui-first-item" :style="{ height: firstItemTop }"></view> <view class="tui-selection-cell" :style="{ padding: padding, backgroundColor: backgroundColor }" :id="`id_${subIndex}`" v-for="(subItem, subIndex) in item.list" :key="subIndex" @tap="change(index, subIndex, subItem)" > <icon type="success_no_circle" v-if="item.index === subIndex" :color="checkMarkColor" :size="checkMarkSize" class="tui-icon-success"></icon> <image :src="subItem.src" v-if="subItem.src" class="tui-cell-img" :style="{ width: imgWidth, height: imgHeight, borderRadius: radius }"></image> <view class="tui-cell-title" :class="{ 'tui-font-bold': item.index === subIndex && textBold, 'tui-flex-shrink': nowrap }" :style="{ color: item.index === subIndex ? textActiveColor : textColor, fontSize: textSize + 'rpx' }" > {{ subItem.text }} </view> <view class="tui-cell-sub_title" :style="{ color: subTextColor, fontSize: subTextSize + 'rpx' }" v-if="subItem.subText">{{ subItem.subText }}</view> </view> </scroll-view> </swiper-item> </swiper> </view> </template> <script> export default { name: 'tuiCascadeSelection', props: { /** * 如果下一级是请求返回,则为第一级数据,否则所有数据 * 数据格式 [{ src: "", text: "", subText: "", value: 0, children:[{ text: "", subText: "", value: 0, children:[] }] }] * */ itemList: { type: Array, default: () => { return []; } }, /* 初始化默认选中数据 [{ text: "",//选中text subText: '',//选中subText value: '',//选中value src: '', //选中src,没有则传空或不传 index: 0, //选中数据在当前layer索引 list: [{src: "", text: "", subText: "", value: 101}] //所有layer数据集合 }]; */ defaultItemList: { type: Array, value: [] }, //是否显示header底部细线 headerLine: { type: Boolean, default: true }, //header背景颜色 headerBgColor: { type: String, default: '#FFFFFF' }, //顶部标签栏高度 tabsHeight: { type: String, default: '88rpx' }, //默认显示文字 text: { type: String, default: '请选择' }, //tabs 文字大小 size: { type: Number, default: 28 }, //tabs 文字颜色 color: { type: String, default: '#555' }, //选中颜色 activeColor: { type: String, default: '#5677fc' }, //选中后文字加粗 bold: { type: Boolean, default: true }, //选中后是否显示底部线条 showLine: { type: Boolean, default: true }, //线条颜色 lineColor: { type: String, default: '#5677fc' }, //icon 大小 checkMarkSize: { type: Number, default: 15 }, //icon 颜色 checkMarkColor: { type: String, default: '#5677fc' }, //item 图片宽度 imgWidth: { type: String, default: '40rpx' }, //item 图片高度 imgHeight: { type: String, default: '40rpx' }, //图片圆角 radius: { type: String, default: '50%' }, //item text颜色 textColor: { type: String, default: '#333' }, textActiveColor: { type: String, default: '#333' }, //选中后字体是否加粗 textBold: { type: Boolean, default: true }, //item text字体大小 textSize: { type: Number, default: 28 }, //text 是否不换行 nowrap: { type: Boolean, default: false }, //item subText颜色 subTextColor: { type: String, default: '#999' }, //item subText字体大小 subTextSize: { type: Number, default: 24 }, // item padding padding: { type: String, default: '20rpx 30rpx' }, //占位高度,第一条数据距离顶部距离 firstItemTop: { type: String, default: '20rpx' }, //swiper 高度 height: { type: String, default: '300px' }, //item swiper 内容部分背景颜色 backgroundColor: { type: String, default: '#FFFFFF' }, //子集数据是否请求返回(默认false,一次性返回所有数据) request: { type: Boolean, default: false }, //子级数据(当有改变时,默认当前选中项新增子级数据,request=true时生效) receiveData: { type: Array, default: () => { return []; } }, //改变值则重置数据 reset: { type: [Number, String], default: 0 } }, watch: { itemList(val) { this.initData(val, -1); }, receiveData(val) { this.subLevelData(val, this.currentTab); }, reset() { this.initData(this.itemList, -1); }, defaultItemList(val){ this.setDefaultData(val) } }, created() { this.setDefaultData(this.defaultItemList) }, data() { return { currentTab: 0, //tab栏scrollview滚动的位置 scrollViewId: 'id__1', selectedArr: [] }; }, methods: { setDefaultData(val){ let defaultItemList = val || []; if (defaultItemList.length > 0) { defaultItemList.map(item => { item.scrollViewId = `id_${item.index}`; }); this.selectedArr = defaultItemList; this.currentTab = defaultItemList.length - 1; this.$nextTick(() => { this.checkCor(); }); } else { this.initData(this.itemList, -1); } }, initData(data, layer) { if (!data || data.length === 0) return; if (this.request) { //第一级数据 this.subLevelData(data, layer); } else { let selectedValue = this.selectedValue || {}; if (selectedValue.type) { this.setDefaultData(selectedValue); } else { this.subLevelData(this.getItemList(layer, -1), layer); } } }, removeChildren(data) { let list = data.map(item => { delete item['children']; return item; }); return list; }, getItemList(layer, index) { let list = []; let arr = JSON.parse(JSON.stringify(this.itemList)); if (layer == -1) { list = this.removeChildren(arr); } else { let value = this.selectedArr[0].index; value = value == -1 ? index : value; list = arr[value].children || []; if (layer > 0) { for (let i = 1; i < layer + 1; i++) { let val = layer === i ? index : this.selectedArr[i].index; list = list[val].children || []; if (list.length === 0) break; } } list = this.removeChildren(list); } return list; }, //滚动切换 switchTab: function(e) { this.currentTab = e.detail.current; this.checkCor(); }, //点击标题切换当 swichNav: function(e) { let cur = e.currentTarget.dataset.current; if (this.currentTab != cur) { this.currentTab = cur; } }, checkCor: function() { let item = this.selectedArr[this.currentTab]; item.scrollViewId = 'id__1'; this.$nextTick(() => { setTimeout(() => { let val = item.index < 2 ? 0 : Number(item.index - 2); item.scrollViewId = `id_${val}`; }, 2); }); if (this.currentTab > 1) { this.scrollViewId = `id_${this.currentTab - 1}`; } else { this.scrollViewId = `id_0`; } }, change(index, subIndex, subItem) { let item = this.selectedArr[index]; if (item.index == subIndex) return; item.index = subIndex; item.text = subItem.text; item.value = subItem.value; item.subText = subItem.subText || ''; item.src = subItem.src || ''; this.$emit('change', { layer: index, subIndex: subIndex, //layer=> Array index ...subItem }); if (!this.request) { let data = this.getItemList(index, subIndex); this.subLevelData(data, index); } }, //新增子级数据时处理 subLevelData(data, layer) { if (!data || data.length === 0) { if (layer == -1) return; //完成选择 let result = JSON.parse(JSON.stringify(this.selectedArr)); let lastItem = result[result.length - 1] || {}; let text = ''; result.map(item => { text += item.text; delete item['list']; //delete item['index']; delete item['scrollViewId']; return item; }); this.$emit('complete', { result: result, value: lastItem.value, text: text, subText: lastItem.subText, src: lastItem.src }); } else { //重置数据( >layer层级) let item = [ { text: this.text, subText: '', value: '', src: '', index: -1, scrollViewId: 'id__1', list: data } ]; if (layer == -1) { this.selectedArr = item; } else { let retainArr = this.selectedArr.slice(0, layer + 1); this.selectedArr = retainArr.concat(item); } this.$nextTick(() => { this.currentTab = this.selectedArr.length - 1; }); } } } }; </script> <style scoped> .tui-cascade-selection { width: 100%; box-sizing: border-box; } .tui-selection-header { width: 100%; display: flex; align-items: center; position: relative; box-sizing: border-box; } .tui-bottom-line { position: relative; } .tui-bottom-line::after { width: 100%; content: ''; position: absolute; border-bottom: 1rpx solid #eaeef1; -webkit-transform: scaleY(0.5) translateZ(0); transform: scaleY(0.5) translateZ(0); transform-origin: 0 100%; bottom: 0; right: 0; left: 0; } .tui-btm-none::after { border-bottom: 0 !important; } .tui-header-item { max-width: 240rpx; padding: 15rpx 30rpx; box-sizing: border-box; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; position: relative; } .tui-font-bold { font-weight: bold; } .tui-active-line { width: 60rpx; height: 6rpx; border-radius: 4rpx; position: absolute; bottom: 0; right: 0; left: 50%; transform: translateX(-50%); } .tui-selection-cell { width: 100%; box-sizing: border-box; display: flex; align-items: center; } .tui-icon-success { margin-right: 12rpx; } .tui-cell-img { margin-right: 12rpx; flex-shrink: 0; } .tui-cell-title { word-break: break-all; } .tui-flex-shrink { flex-shrink: 0; } .tui-font-bold { font-weight: bold; } .tui-cell-sub_title { margin-left: 20rpx; word-break: break-all; } .tui-first-item { width: 100%; } </style>