shop-applet/components/e-select/e-select.vue

449 lines
11 KiB
Vue
Raw Normal View History

2024-05-14 17:25:13 +08:00
<template>
<view
class="e-stat__select"
:style="{ width: width, minWidth: minWidth }">
<!-- 主体区域 -->
<view class="e-select-main">
<view
class="e-select"
:class="{ 'e-select-disabled': disabled }">
<view
class="e-select__input-box"
@click="toggleSelector">
<!-- 微信小程序input组件在部分安卓机型上会出现文字重影placeholder抖动问题2019年时微信小程序就有这个问题一直没修复估计短时间内也别指望修复了 -->
<input
class="e-select__input-text"
:placeholder="placeholder"
v-model="currentData"
@input="filter"
v-if="search && !disabled" />
<view
class="e-select__input-text"
v-else>
{{ currentData || currentData === 0 ? currentData : placeholder }}
</view>
<!-- 用一个更大的盒子包裹图标,便于点击 -->
<view
class="e-select-icon"
@click.stop="clearVal"
v-if="currentData && clear && !disabled">
<uni-icons
type="clear"
color="#e1e1e1"
size="18"></uni-icons>
</view>
<view
class="e-select-icon"
@click.stop="toggleSelector"
v-else>
<uni-icons
size="14"
color="#999"
type="top"
class="arrowAnimation"
:class="showSelector ? 'top' : 'bottom'"></uni-icons>
</view>
</view>
<!-- 全屏遮罩-->
<view
class="e-select--mask"
v-if="showSelector"
@click="toggleSelector" />
<!-- 选项列表 这里用v-show是因为微信小程序会报警告 [Component] slot "" is not foundv-if会导致开发工具不能正确识别到slot -->
<!-- https://developers.weixin.qq.com/community/minihome/doc/000c8295730700d1cd7c81b9656c00 -->
<view
class="e-select__selector"
v-show="showSelector">
<!-- 三角小箭头 -->
<view class="e-popper__arrow"></view>
<scroll-view
scroll-y="true"
:scroll-top="scrollTop"
class="e-select__selector-scroll"
:scroll-into-view="scrollToId"
:scroll-with-animation="scrollWithAnimation"
v-if="showSelector">
<view
class="e-select__selector-empty"
v-if="currentOptions.length === 0">
<text>{{ emptyTips }}</text>
</view>
<!-- 非空,渲染选项列表 -->
<view
v-else
class="e-select__selector-item"
:class="[
{ highlight: currentData == item[props.text] },
{
'e-select__selector-item-disabled': item[props.disabled],
},
]"
v-for="(item, index) in currentOptions"
:key="index"
@click="change(item, index)">
<text>{{ item[props.text] }}</text>
<view
id="scrollToId"
v-if="currentData == item[props.text]"></view>
</view>
</scroll-view>
<slot />
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'e-select',
data() {
return {
// 是否显示下拉选择列表
showSelector: false,
// 当前选项
currentOptions: [],
// 当前值
currentData: '',
// 旧的滚动高度
oldScrollTop: 0,
// 最新的滚动高度
scrollTop: 0,
// 滚动至的id
scrollToId: '',
// 滚动动画
scrollWithAnimation: false,
};
},
props: {
// 选项列表
options: {
type: Array,
default() {
return [];
},
},
// 配置选项
props: {
type: Object,
default: function () {
return {
text: 'text',
value: 'value',
disabled: 'disabled',
};
},
},
// vue2 v-model传值方式
value: {
type: [String, Number],
default: '',
},
// vue3 v-model传值方式
modelValue: {
type: [String, Number],
default: '',
},
// 占位
placeholder: {
type: String,
default: '请选择',
},
// 宽度
width: {
type: String,
default: '100%',
},
// 最小宽度
minWidth: {
type: String,
default: '120rpx',
},
// 空值占位
emptyTips: {
type: String,
default: '暂无选项',
},
// 是否可清除
clear: {
type: Boolean,
default: false,
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
// 开启搜索
search: {
type: Boolean,
default: true,
},
// 搜索开启滚动动画
animation: {
type: Boolean,
default: true,
},
},
watch: {
options: {
handler() {
this.currentOptions = this.options;
this.initData();
},
immediate: true,
deep: true,
},
modelValue: {
handler() {
this.initData();
},
immediate: true,
},
value: {
handler() {
this.initData();
},
immediate: true,
},
},
methods: {
/** 处理数据此函数用于兼容vue2 vue3 */
initData() {
this.currentData = '';
// vue2
if (this.value || this.value === 0) {
for (let item of this.options) {
if (item[this.props.value] === this.value) {
this.currentData = item[this.props.text];
this.$emit('getText', this.currentData);
return;
}
}
}
// vue3
if (this.modelValue || this.modelValue === 0) {
for (let item of this.options) {
if (item[this.props.value] === this.modelValue) {
this.currentData = item[this.props.text];
this.$emit('getText', this.currentData);
return;
}
}
}
},
/** 过滤选项列表,会自动回到顶部 */
filter() {
this.$emit('getText', this.currentData);
if (this.currentData) {
this.currentOptions = this.options.filter((item) => {
return item[this.props.text].indexOf(this.currentData) > -1;
});
} else {
this.currentOptions = this.options;
}
// scrollTop变化才能触发滚动顶部
this.scrollTop = 1;
this.$nextTick(() => {
this.scrollTop = 0;
});
},
/** 改变值 */
change(item, index) {
if (item[this.props.disabled]) return;
const data = {
...item,
index,
};
this.$emit('change', data);
this.emit(data);
this.toggleSelector();
},
/** 传递父组件值 */
emit(item) {
this.$emit('input', item[this.props.value]);
this.$emit('update:modelValue', item[this.props.value]);
},
/** 清空值 */
clearVal() {
this.$emit('change', 'clear');
this.$emit('input', '');
this.$emit('update:modelValue', '');
},
/** 切换下拉显示 */
toggleSelector() {
if (this.disabled) return;
this.showSelector = !this.showSelector;
if (this.showSelector) {
this.currentOptions = this.options;
// scrollToId变化才能触发scroll-to-view的滚动
this.scrollToId = '';
this.$nextTick(() => {
this.scrollToId = 'scrollToId';
// 设计理念只在filter时触发滚动动画因为每次打开就触发用户体验不好
if (this.animation) {
setTimeout(() => {
// 开启滚动动画
this.scrollWithAnimation = true;
}, 100);
}
});
} else {
// 关闭时关闭动画
this.scrollWithAnimation = false;
}
},
},
};
</script>
<style lang="scss" scoped>
.e-stat__select {
display: flex;
align-items: center;
cursor: pointer;
box-sizing: border-box;
width: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.e-select-main {
width: 100%;
}
.e-select-disabled {
background-color: #f5f7fa;
cursor: not-allowed;
}
.e-select {
font-size: 14px;
box-sizing: border-box;
border-radius: 4px;
padding: 0 5px;
position: relative;
display: flex;
user-select: none;
flex-direction: row;
align-items: center;
border: 1px solid #dcdfe6;
border-bottom: solid 1px #dddddd;
.e-select__input-box {
width: 100%;
min-height: 34px;
position: relative;
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
.e-select-icon {
width: 50px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.arrowAnimation {
transition: transform 0.3s;
}
.top {
transform: rotateZ(0deg);
}
.bottom {
transform: rotateZ(180deg);
}
.e-select__input-text {
color: #303030;
padding-left: 7px;
width: 100%;
color: #333;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
overflow: hidden;
}
.e-select__input-placeholder {
padding-left: 7px;
color: #666;
}
}
.e-select--mask {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 999;
}
.e-select__selector {
box-sizing: border-box;
position: absolute;
top: calc(100% + 12px);
left: 0;
width: 100%;
background-color: #ffffff;
border: 1px solid #ebeef5;
border-radius: 6px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 999;
padding: 4px 4px;
transition: all 2s;
.e-popper__arrow,
.e-popper__arrow::after {
position: absolute;
display: block;
width: 0;
height: 0;
left: 50%;
border-color: transparent;
border-style: solid;
border-width: 6px;
}
.e-popper__arrow {
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
top: -6px;
left: 50%;
transform: translateX(-50%);
margin-right: 3px;
border-top-width: 0;
border-bottom-color: #ebeef5;
}
.e-popper__arrow::after {
content: ' ';
top: 1px;
margin-left: -6px;
border-top-width: 0;
border-bottom-color: #fff;
}
.e-select__selector-scroll {
max-height: 200px;
box-sizing: border-box;
.e-select__selector-empty,
.e-select__selector-item {
display: flex;
cursor: pointer;
line-height: 34px;
font-size: 14px;
text-align: center;
padding: 0px 10px;
}
.e-select__selector-item:hover {
background-color: #f9f9f9;
}
.e-select__selector-empty:last-child,
.e-select__selector-item:last-child {
border-bottom: none;
}
.e-select__selector-item-disabled {
color: #b1b1b1;
cursor: not-allowed;
}
.highlight {
color: #409eff;
font-weight: bold;
background-color: #f5f7fa;
border-radius: 3px;
}
}
}
}
</style>