yunshangxie/tuniao-ui/components/tn-select/tn-select.vue

428 lines
11 KiB
Vue
Raw Normal View History

2023-12-25 17:56:30 +08:00
<template>
2024-05-21 18:16:48 +08:00
<view v-if="value" class="tn-select-class tn-select">
<tn-popup v-model="value" mode="bottom" :popup="false" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom"
:maskCloseable="maskCloseable" :zIndex="elZIndex" @close="close">
<view class="tn-select__content" :style="{paddingBottom:safeAreaInsetBottom?'120rpx':'0'}">
<!-- 头部 -->
<view class="tn-select__content__header" @touchmove.stop.prevent>
<view class="tn-select__content__header__btn tn-select__content__header--cancel"
:style="{ color: cancelColor }" hover-class="tn-hover-class" hover-stay-time="150"
@tap="getResult('cancel')">{{ cancelText }}</view>
<view class="tn-select__content__header__title">{{ title }}</view>
<view class="tn-select__content__header__btn tn-select__content__header--confirm"
:style="{ color: confirmColor }" hover-class="tn-hover-class" hover-stay-time="150"
@tap="getResult('confirm')">{{ confirmText }}</view>
</view>
<!-- 列表内容 -->
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
<view class="tn-select__content__body">
<view v-if="searchShow && mode==='single'">
<view class="tn-flex tn-select__content__body__search">
<view class="tn-icon-search tn-padding-sm"></view>
<input class="tn-select__content__body__search__input" :placeholder="searchPlaceholder"
maxlength="255" confirm-type="search" @input="searchInput" @confirm="search">
</view>
</view>
<picker-view class="tn-select__content__body__view" :value="defaultSelector" @pickstart="pickStart"
@pickend="pickEnd" @change="columnChange">
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
<picker-view-column v-for="(item, index) in columnData" :key="index">
<view class="tn-select__content__body__item" v-for="(sub_item, sub_index) in item"
:key="sub_index">
<view class="tn-text-ellipsis">
{{ sub_item[labelName] }}
</view>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</tn-popup>
</view>
2023-12-25 17:56:30 +08:00
</template>
<script>
2024-05-21 18:16:48 +08:00
export default {
name: 'tn-select',
props: {
value: {
type: Boolean,
default: false
},
// 列表模式
// single 单列 multi 多列 multi-auto 多列联动
mode: {
type: String,
default: 'single'
},
// 列数据
list: {
type: Array,
default () {
return []
}
},
// value属性名
valueName: {
type: String,
default: 'value'
},
// label属性名
labelName: {
type: String,
default: 'label'
},
// 当mode=multi-auto时children的属性名
childName: {
type: String,
default: 'children'
},
// 默认值
defaultValue: {
type: Array,
default () {
return [0]
}
},
// 顶部标题
title: {
type: String,
default: ''
},
// 取消按钮文字
cancelText: {
type: String,
default: '取消'
},
// 取消按钮文字颜色
cancelColor: {
type: String,
default: ''
},
// 确认按钮文字
confirmText: {
type: String,
default: '确认'
},
// 确认按钮文字颜色
confirmColor: {
type: String,
default: ''
},
// 点击遮罩关闭
maskCloseable: {
type: Boolean,
default: true
},
// 预留安全区域
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// zIndex
zIndex: {
type: Number,
default: 0
},
// 是否开启模糊搜索(只在单列模式生效)
searchShow: {
type: Boolean,
default: true
},
//搜索框placeholder
searchPlaceholder: {
type: String,
default: '搜索'
}
},
computed: {
elZIndex() {
return this.zIndex ? this.zIndex : this.$tn.zIndex.popup
}
},
data() {
return {
// 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
moving: false,
// 用户保存当前列的索引,用于判断下一次变化时改变的列
defaultSelector: [0],
// picker-view数据
columnData: [],
// 保存用户选择的结果
selectValue: [],
// 上一次改变时的index
lastSelectIndex: [],
// 列数
columnNum: 0,
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
}
},
watch: {
// 在select弹起的时候重新初始化所有数据
value: {
handler(val) {
if (val) setTimeout(() => this.init(), 10)
},
immediate: true
}
},
methods: {
//搜索输入监听
searchInput(e) {
console.log(e.detail.value);
this.searchResult(e.detail.value)
},
//搜索完成监听
search(e) {
console.log(e.detail.value)
this.searchResult(e.detail.value)
},
//执行搜索方法
searchResult(value) {
let result = [];
// console.log(this.list)
let data = this.list.filter(item => item[this.labelName].indexOf(value) > -1);
if (data.length > 0) {
result.push(data);
}
// console.log(result)
this.columnData = result;
this.selectValue = []
if (this.columnData.length > 0) {
this.setSelectValue()
}
},
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
// 标识滑动开始,只有微信小程序才有这样的事件
pickStart() {
// #ifdef MP-WEIXIN
this.moving = true;
// #endif
},
// 标识滑动结束
pickEnd() {
// #ifdef MP-WEIXIN
this.moving = false;
// #endif
},
init() {
this.setColumnNum()
this.setDefaultSelector()
this.setColumnData()
this.setSelectValue()
},
// 获取默认选中列下标
setDefaultSelector() {
// 如果没有传入默认值生成用0填充长度为columnNum的数组
this.defaultSelector = this.defaultValue.length === this.columnNum ? this.defaultValue : Array(this
.columnNum).fill(0)
this.lastSelectIndex = this.$tn.deepClone(this.defaultSelector)
},
// 计算列数
setColumnNum() {
// 单列的数量为1
if (this.mode === 'single') this.columnNum = 1
// 多列时取list的长度
else if (this.mode === 'multi') this.columnNum = this.list.length
// 多列联动时通过遍历list的第一个元素得出有多少列
else if (this.mode === 'multi-auto') {
let num = 1
let column = this.list
// 如果存在children属性再次遍历
while (column[0][this.childName]) {
column = column[0] ? column[0][this.childName] : {},
num++
}
this.columnNum = num
}
},
// 获取需要展示在picker中的列数据
setColumnData() {
let data = []
this.selectValue = []
if (this.mode === 'multi-auto') {
// 获取所有数据中的第一个元素
let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0]
// 通过循环所有列数,再根据设定列的数组,得出当前需要渲染的整个列数组
for (let i = 0; i < this.columnNum; i++) {
// 第一列默认为整个list数组
if (i === 0) {
data[i] = this.list
column = column[this.childName]
} else {
// 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
data[i] = column
column = column[this.defaultSelector[i]][this.childName]
}
}
} else if (this.mode === 'single') {
data[0] = this.list
} else {
data = this.list
}
this.columnData = data
},
// 获取默认选中的值,如果没有设置,则默认选中第一项
setSelectValue() {
let tmp = null
for (let i = 0; i < this.columnNum; i++) {
tmp = this.columnData[i][this.defaultSelector[i]]
let data = {
value: tmp ? tmp[this.valueName] : null,
label: tmp ? tmp[this.labelName] : null
}
// 判断是否存在额外的参数
if (tmp && tmp.extra) data.extra = tmp.extra
this.selectValue.push(data)
}
// console.log("默认",this.selectValue)
},
// 列选项
columnChange(event) {
let index = null
let columnIndex = event.detail.value
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
this.selectValue = []
if (this.mode === 'multi-auto') {
// 对比前后两个数组,判断变更的是那一列
this.lastSelectIndex.map((v, idx) => {
if (v != columnIndex[idx]) index = idx
})
this.defaultSelector = columnIndex
// 当前变化列的下一列的数据需要获取上一列的数据同时需要指定是上一列的第几个的children再往后的
// 默认是队列的第一个为默认选项
for (let i = index + 1; i < this.columnNum; i++) {
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this
.childName
]
this.defaultSelector[i] = 0
}
// 在历遍的过程中可能由于上一步修改this.columnData导致产生连锁反应程序触发columnChange会有多次调用
// 只有在最后一次数据稳定后的结果是正确的此前的历遍中可能会产生undefined故需要判断
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
})
this.lastSelectIndex = columnIndex
} else if (this.mode === 'single') {
let data = this.columnData[0][columnIndex[0]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
} else if (this.mode === 'multi') {
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]]
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null
}
if (data && data.extra !== undefined) tmp.extra = data.extra
this.selectValue.push(tmp)
})
}
},
close() {
this.$emit('input', false)
},
getResult(event = null) {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
if (event) this.$emit(event, this.selectValue)
this.close()
}
}
}
2023-12-25 17:56:30 +08:00
</script>
<style lang="scss" scoped>
2024-05-21 18:16:48 +08:00
.tn-select {
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__content {
position: relative;
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__header {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
height: 90rpx;
padding: 0 40rpx;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
font-size: 30rpx;
background-color: #FFFFFF;
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__btn {
padding: 16rpx;
box-sizing: border-box;
text-align: center;
text-decoration: none;
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__title {
color: $tn-font-color;
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&--cancel {
color: $tn-font-sub-color;
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&--confirm {
// color: $tn-main-color;
background-color: #07C160;
color: #FFFFFF;
padding: 10rpx 25rpx;
border-radius: 10rpx;
}
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__body {
width: 100%;
height: 500rpx;
overflow: hidden;
background-color: #FFFFFF;
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__search {
z-index: 5;
align-items: center;
border-radius: 19px;
background: #f8f8f8;
width: calc(100% - 60rpx);
margin: 0 auto;
position: relative;
top: 15px;
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__input {
width: 600rpx;
}
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__view {
height: 100%;
box-sizing: border-box;
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
&__item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: $tn-font-color;
padding: 0 8rpx;
}
}
}
2023-12-25 17:56:30 +08:00
2024-05-21 18:16:48 +08:00
}
</style>