yunshangxie/tuniao-ui/components/tn-cascade-selection/tn-cascade-selection.vue

655 lines
18 KiB
Vue

<template>
<view class="tn-cascade-selection tn-cascade-selection-class">
<scroll-view
class="selection__scroll-view"
:class="[{'tn-border-solid-bottom': headerLine}]"
:style="[scrollViewStyle]"
scroll-x
scroll-with-animation
:scroll-into-view="scrollViewId"
>
<view class="selection__header" :class="[backgroundColorClass]" :style="[headerStyle]">
<view
v-for="(item, index) in selectedArr"
:key="index"
:id="`select__${index}`"
class="selection__header__item"
:class="[headerItemClass(index)]"
:style="[headerItemStyle(index)]"
@tap.stop="clickNav(index)"
>
{{ item.text }}
<view
v-if="index===currentTab && showActiveLine"
class="selection__header__line"
:style="{backgroundColor: activeLineColor}"
></view>
</view>
</view>
</scroll-view>
<swiper
class="selection__list"
:class="[backgroundColorClass]"
:style="[listStyle]"
:current="currentTab"
:duration="300"
@change="switchTab"
>
<swiper-item
v-for="(item, index) in selectedArr"
:key="index"
>
<scroll-view
class="selection__list__item"
:style="{height: selectionContainerHeight + 'rpx'}"
scroll-y
:scroll-into-view="item.scrollViewId"
>
<view class="selection__list__item--first"></view>
<view
v-for="(subItem, subIndex) in item.list"
:key="subIndex"
:id="`select__${subIndex}`"
class="selection__list__item__cell"
:style="[itemStyle]"
@tap="change(index, subIndex, subItem)"
>
<view
v-if="item.index === subIndex"
class="selection__list__item__icon tn-icon-success"
:style="[itemIconStyle]"
></view>
<image
v-if="subItem.src"
class="selection__list__item__image"
:style="[itemImageStyle]"
:src="subItem.src"
></image>
<view
class="selection__list__item__title"
:class="[{'tn-text-bold': item.index === subIndex && itemActiveBold}]"
:style="[itemTitleStyle(index, subIndex)]"
>
{{ subItem.text }}
</view>
<view
v-if="subItem.subText"
class="selection__list__item__title--sub"
:style="[itemSubTitleStyle]"
>
{{ subItem.subText }}
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</template>
<script>
import componentsColorMixin from '../../libs/mixin/components_color.js'
export default {
name: 'tn-cascade-selection',
mixins: [ componentsColorMixin ],
props: {
// 如果下一级是请求返回,则为第一级数据,否则为所有数据
/* {
text: '', // 标题
subText: '', // 子标题
src: '', // 图片地址
value: 0, // 选中的值
children: [
{
text: '',
subText: '',
value: 0,
children: []
}
]
} */
list: {
type: Array,
default() {
return []
}
},
// 默认选中值
// ['value1','value2','value3']
defaultValue: {
type: Array,
default() {
return []
}
},
// 子集数据通过请求来获取
request: {
type: Boolean,
default: false
},
// request为true时生效, 获取到的子集数据
receiveData: {
type: Array,
default() {
return []
}
},
// 显示header底部细线
headerLine: {
type: Boolean,
default: true
},
// header背景颜色
headerBgColor: {
type: String,
default: ''
},
// 顶部标签栏高度,单位rpx
tabsHeight: {
type: Number,
default: 88
},
// 默认显示文字
text: {
type: String,
default: '请选择'
},
// 选中的颜色
activeColor: {
type: String,
default: '#01BEFF'
},
// 选中后加粗
activeBold: {
type: Boolean,
default: true
},
// 选中显示底部线条
showActiveLine: {
type: Boolean,
default: true
},
// 线条颜色
activeLineColor: {
type: String,
default: '#01BEFF'
},
// icon大小,单位rpx
activeIconSize: {
type: Number,
default: 0
},
// icon颜色
activeIconColor: {
type: String,
default: '#01BEFF'
},
// item图片宽度, 单位rpx
itemImgWidth: {
type: Number,
default: 0
},
// item图片高度, 单位rpx
itemImgHeight: {
type: Number,
default: 0
},
// item图片圆角
itemImgRadius: {
type: String,
default: '50%'
},
// item text颜色
itemTextColor: {
type: String,
default: ''
},
// item text选中颜色
itemActiveTextColor: {
type: String,
default: ''
},
// item text选中加粗
itemActiveBold: {
type: Boolean,
default: true
},
// item text文字大小, 单位rpx
itemTextSize: {
type: Number,
default: 0
},
// item subText颜色
itemSubTextColor: {
type: String,
default: ''
},
// item subText字体大小, 单位rpx
itemSubTextSize: {
type: Number,
default: 0
},
// item样式
itemStyle: {
type: Object,
default() {
return {}
}
},
// selection选项容器高度, 单位rpx
selectionContainerHeight: {
type: Number,
default: 300
}
},
computed: {
scrollViewStyle() {
let style = {}
if (this.headerBgColor) {
style.backgroundColor = this.headerBgColor
}
return style
},
headerStyle() {
let style = {}
style.height = `${this.tabsHeight}rpx`
if (this.backgroundColorStyle) {
style.backgroundColor = this.backgroundColorStyle
}
return style
},
headerItemClass() {
return (index) => {
let clazz = ''
if (index !== this.currentTab) {
clazz += ` ${this.fontColorClass}`
} else {
if (this.activeBold) {
clazz += ' tn-text-bold'
}
}
return clazz
}
},
headerItemStyle() {
return (index) => {
let style = {}
style.color = index === this.currentTab ? this.activeColor : (this.fontColorStyle ? this.fontColorStyle : '')
if (this.fontSizeStyle) {
style.fontSize = this.fontSizeStyle
}
return style
}
},
listStyle() {
let style = {}
style.height = `${this.selectionContainerHeight}rpx`
if (this.backgroundColorStyle) {
style.color = this.backgroundColorStyle
}
return style
},
itemIconStyle() {
let style = {}
if (this.activeIconColor) {
style.color = this.activeIconColor
}
if (this.activeIconSize) {
style.fontSize = this.activeIconSize + 'rpx'
}
return style
},
itemImageStyle() {
let style = {}
if (this.itemImgWidth) {
style.width = this.itemImgWidth + 'rpx'
}
if (this.itemImgHeight) {
style.height = this.itemImgHeight + 'rpx'
}
if (this.itemImgRadius) {
style.borderRadius = this.itemImgRadius
}
return style
},
itemTitleStyle() {
return (index, subIndex) => {
let style = {}
if (index === subIndex) {
if (this.itemActiveTextColor) {
style.color = this.itemActiveTextColor
}
} else {
if (this.itemTextColor) {
style.color = this.itemTextColor
}
}
if (this.itemTextSize) {
style.fontSize = this.itemTextSize + 'rpx'
}
return style
}
},
itemSubTitleStyle() {
let style = {}
if (this.itemSubTextColor) {
style.color = this.itemSubTextColor
}
if (this.itemSubTextSize) {
style.fontSize = this.itemSubTextSize + 'rpx'
}
return {}
}
},
watch: {
list(val) {
this.initData(val, -1)
},
defaultValue(val) {
this.setDefaultValue(val)
},
receiveData(val) {
this.addSubData(val, this.currentTab)
},
},
data() {
return {
// 当前选中的子集
currentTab: 0,
// tabs栏scrollView滚动的位置
scrollViewId: 'select__0',
// 选项数组
selectedArr: []
}
},
created() {
this.setDefaultValue(this.defaultValue)
},
methods: {
// 初始化数据
initData(data, index) {
if (!data || data.length === 0) return
if (this.request) {
// 第一级数据
this.addSubData(data, index)
} else {
this.addSubData(this.getItemList(index, -1), index)
}
},
// 重置数据
reset() {
this.initData(this.list, -1)
},
// 滚动切换
switchTab(e) {
this.currentTab = e.detail.current
this.checkSelectPosition()
},
// 点击标题切换
clickNav(index) {
if (this.currentTab !== index) {
this.currentTab = index
}
},
// 列表数据发生改变
change(index, subIndex, subItem) {
let item = this.selectedArr[index]
if (item.index === subIndex) return
item.index = subIndex
item.text = subItem.text
item.subText = subItem.subText || ''
item.value = subItem.value
item.src = subItem.src || ''
this.$emit('change', {
index: index,
subIndex: subIndex,
...subItem
})
// 如果不是异步加载,则取出对应的数据
if (!this.request) {
let data = this.getItemList(index, subIndex)
this.addSubData(data, index)
}
},
// 设置默认的数据
setDefaultValue(val) {
let defaultValues = val || []
if (defaultValues.length > 0) {
this.selectedArr = this.getItemListWithValues(JSON.parse(JSON.stringify(this.list)), defaultValues)
if (!this.selectedArr) return
this.currentTab = this.selectedArr.length - 1
this.$nextTick(() => {
this.checkSelectPosition()
})
// defaultItemList.map((item) => {
// item.scrollViewId = `select__${item.index}`
// })
// this.selectedArr = defaultItemList
// this.currentTab = defaultItemList.length - 1
// this.$nextTick(() => {
// this.checkSelectPosition()
// })
} else {
this.initData(this.list, -1)
}
},
// 获取对应选项的item数据
getItemList(index, subIndex) {
let list = []
let arr = JSON.parse(JSON.stringify(this.list))
// 初始化数据
if (index === -1) {
list = this.removeChildren(arr)
} else {
// 判断第一项是否已经选择
let value = this.selectedArr[0].index
value = value === -1 ? subIndex : value
list = arr[value].children || []
if (index > 0) {
for (let i = 1; i < index + 1; i++) {
// 获取当前数据选中的序号
let val = index === i ? subIndex : this.selectedArr[i].index
list = list[val].children || []
if (list.length === 0) break
}
}
list = this.removeChildren(list)
}
return list
},
// 根据数组中的值获取对应的item数据
getItemListWithValues(data, values) {
const defaultValues = JSON.parse(JSON.stringify(values))
if (!defaultValues || defaultValues.length === 0) return
// 取出第一个值所对应的item
const itemIndex = data.findIndex((item) => {
return item.value === defaultValues[0]
})
if (itemIndex === -1) return
const item = data[itemIndex]
item.index = itemIndex
item.scrollViewId = `select__${itemIndex}`
item.list = this.removeChildren(JSON.parse(JSON.stringify(data)))
// 判断是否只有1个值
if (defaultValues.length === 1 || (!item.hasOwnProperty('children') || item.children.length === 0)) {
return this.removeChildren([item])
} else {
let selectItemList = []
const children = item.children
selectItemList.push(item)
// 移除已经获取的值
defaultValues.splice(0, 1)
const childrenValue = this.getItemListWithValues(children, defaultValues)
selectItemList = selectItemList.concat(childrenValue)
return this.removeChildren(selectItemList)
}
},
// 删除子元素
removeChildren(data) {
let list = data.map((item) => {
if (item.hasOwnProperty('children')) {
delete item['children']
}
return item
})
return list
},
// 新增子集数据时处理
addSubData(data, index) {
// 判断是否已经完成选择数据或者为初始化数据
if (!data || data.length === 0) {
if (index == -1) return
// 完成选择
let arr = this.selectedArr
// 如果当前选中项的序号比已选数据的长度小,则表示当前重新选择了数据
if (index < arr.length - 1) {
let newArr = arr.slice(0, index + 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['scrollViewId']
return item
})
this.$emit('complete', {
result: result,
value: lastItem.value,
text: text,
subText: lastItem.subText,
src: lastItem.src
})
} else {
// 重置数据
let item = [{
text: this.text,
subText: '',
value: '',
src: '',
index: -1,
scrollViewId: 'select__0',
list: data
}]
// 初始化数据
if (index === -1) {
this.selectedArr = item
} else {
// 拼接新旧数据并且判断是否为重新选择了数据(如果为重新选择了数据则重置之后的选项数据)
let retainArr = this.selectedArr.slice(0, index + 1)
this.selectedArr = retainArr.concat(item)
}
this.$nextTick(() => {
this.currentTab = this.selectedArr.length - 1
})
}
},
// 检查当前选中项,并将选项设置位置信息
checkSelectPosition() {
let item = this.selectedArr[this.currentTab]
item.scrollViewId = 'select__0'
this.$nextTick(() => {
setTimeout(() => {
// 设置当前数据滚动到的位置
let val = item.index < 2 ? 0 : Number(item.index - 2)
item.scrollViewId = `select__${val}`
}, 10)
})
// 设置选项滚动到所在的位置
if (this.currentTab > 1) {
this.scrollViewId = `select__${this.currentTab - 1}`
} else {
this.scrollViewId = `select__0`
}
}
}
}
</script>
<style lang="scss" scoped>
.tn-cascade-selection {
width: 100%;
}
.selection {
&__scroll-view {
background-color: #FFFFFF;
}
&__header {
width: 100%;
display: flex;
align-items: center;
position: relative;
&__item {
max-width: 240rpx;
padding: 15rpx 30rpx;
flex-shrink: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
}
&__line {
width: 60rpx;
height: 6rpx;
border-radius: 4rpx;
position: absolute;
bottom: 0;
right: 0;
left: 50%;
transform: translateX(-50%);
}
}
&__list {
background-color: #FFFFFF;
&__item {
&--first {
width: 100%;
height: 20rpx;
}
&__cell {
width: 100%;
display: flex;
align-items: center;
padding: 20rpx 30rpx;
}
&__icon {
margin-right: 12rpx;
font-size: 24rpx;
}
&__image {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
&__title {
word-break: break-all;
color: #333333;
font-size: 28rpx;
&--sub {
margin-left: 20rpx;
word-break: break-all;
color: $tn-font-sub-color;
font-size: 24rpx;
}
}
}
}
}
</style>