You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

595 lines
14 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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="defTab" 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.stop="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,
default(){
return []
}
},
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: '#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,
defTab: 0,
//tab栏scrollview滚动的位置
scrollViewId: 'id__1',
selectedArr: []
};
},
methods: {
setDefaultData(val) {
let defaultItemList = JSON.parse(JSON.stringify(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.defTab = this.currentTab;
this.$nextTick(() => {
setTimeout(() => {
this.currentTab = selectedArr.length - 1;
this.defTab = this.currentTab;
this.checkCor();
}, 20)
});
} else {
defaultItemList.map(item => {
item.scrollViewId = `id_${item.index}`;
});
this.selectedArr = defaultItemList;
this.defTab = this.currentTab;
this.$nextTick(() => {
setTimeout(() => {
this.currentTab = defaultItemList.length - 1;
this.defTab = this.currentTab;
this.checkCor();
}, 20)
});
}
} 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 === undefined || value == -1 ? index : value;
if (arr[value] && arr[value].children) {
list = arr[value].children;
}
if (layer > 0) {
for (let i = 1; i < layer + 1; i++) {
let val = layer === i ? index : selectedArr[i].index;
list = val === -1 ? [] : (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.defTab = this.currentTab;
setTimeout(() => {
this.currentTab = cur;
this.defTab = this.currentTab;
}, 20)
}
},
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}`;
}, 20);
});
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);
}
let current = this.selectedArr.length - 1;
if (current >= this.currentTab) {
this.defTab = this.currentTab
}
this.$nextTick(() => {
setTimeout(() => {
this.defTab = current;
this.currentTab = current;
this.scrollViewId = `id_${this.currentTab > 1?this.currentTab - 1:0}`;
}, 50)
});
}
}
}
};
</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>