568 lines
13 KiB
Vue
568 lines
13 KiB
Vue
<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': idx === currentTab && bold }"
|
||
:style="{ color: idx === currentTab ? activeColor : color, fontSize: size + 'rpx' }"
|
||
:id="`id_${idx}`" @tap.stop="swichNav" :data-current="idx" v-for="(item, idx) in selectedArr"
|
||
:key="idx">
|
||
{{ item.text }}
|
||
<view class="tui-active-line" :style="{ backgroundColor: lineColor }"
|
||
v-if="idx === 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',
|
||
emits: ['change', 'complete'],
|
||
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: []
|
||
},
|
||
defaultKey: {
|
||
type: String,
|
||
default: 'text'
|
||
},
|
||
//是否显示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: '#1A73E8'
|
||
},
|
||
//选中后文字加粗
|
||
bold: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
//选中后是否显示底部线条
|
||
showLine: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
//线条颜色
|
||
lineColor: {
|
||
type: String,
|
||
default: '#1A73E8'
|
||
},
|
||
//icon 大小
|
||
checkMarkSize: {
|
||
type: Number,
|
||
default: 15
|
||
},
|
||
//icon 颜色
|
||
checkMarkColor: {
|
||
type: String,
|
||
default: '#1A73E8'
|
||
},
|
||
//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) {
|
||
if ((typeof defaultItemList[0] === 'string' || typeof defaultItemList[0] === 'number') && !this
|
||
.request) {
|
||
let subi = -1
|
||
let selectedArr = []
|
||
for (let j = 0, len = defaultItemList.length; j < len; j++) {
|
||
let item = defaultItemList[j]
|
||
let list = []
|
||
let obj = {}
|
||
if (j === 0) {
|
||
list = this.getItemList(-1)
|
||
} else {
|
||
list = this.getItemList(j - 1, subi,selectedArr)
|
||
}
|
||
subi = this.getDefaultIndex(list, item)
|
||
if (subi !== -1) {
|
||
obj = list[subi]
|
||
selectedArr.push({
|
||
text: obj.text || this.text,
|
||
value: obj.value || '',
|
||
src: obj.src || '',
|
||
subText: obj.subText || '',
|
||
index: subi,
|
||
scrollViewId: `id_${subi}`,
|
||
list: list
|
||
})
|
||
}
|
||
|
||
if (subi === -1) break;
|
||
}
|
||
this.selectedArr = selectedArr;
|
||
this.currentTab = selectedArr.length - 1;
|
||
this.$nextTick(() => {
|
||
this.checkCor();
|
||
});
|
||
} else {
|
||
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);
|
||
}
|
||
},
|
||
getDefaultIndex(arr, val) {
|
||
if (!arr || arr.length === 0 || val === undefined) return -1;
|
||
let index = -1;
|
||
let key = this.defaultKey || 'text'
|
||
for (let i = 0, len = arr.length; i < len; i++) {
|
||
if (arr[i][key] == val) {
|
||
index = i;
|
||
break;
|
||
}
|
||
}
|
||
return index;
|
||
},
|
||
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, selectedArr) {
|
||
let list = [];
|
||
let arr = JSON.parse(JSON.stringify(this.itemList));
|
||
selectedArr = selectedArr || this.selectedArr
|
||
if (layer == -1) {
|
||
list = this.removeChildren(arr);
|
||
} else {
|
||
let value = 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 : 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 arr = this.selectedArr;
|
||
if (layer < arr.length - 1) {
|
||
let newArr = arr.slice(0, layer + 1);
|
||
this.selectedArr = newArr;
|
||
}
|
||
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: 350rpx;
|
||
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>
|