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

663 lines
17 KiB

<template>
<view>
<view class="maskbox" v-show="active" @tap="maskClick"></view>
<view :class="description == 'row'?'uni-combox-row':'uni-combox-column'">
<view v-if="label" class="uni-combox__label" :style="labelStyle">
<text>{{label}}</text>
</view>
<view class="u-dropdown">
<view class="u-dropdown__menu">
<view class="u-dropdown__menu__item">
<view class="u-flex " style="width: 100%;">
<view class="u-close-wrap" v-if="isSearch">
<u-icon class="u-clear-icon" :size="30" :name="searchIcon"
:color="searchIconColor ? searchIconColor : color"></u-icon>
</view>
<view class="u-input">
<u-input confirm-type="search" class="uni-combox__input"
:type="isAutoHeight ? 'textarea':'text'" v-model="inputVal" @confirm="confirm()"
:placeholder="placeholder" :auto-height="isAutoHeight"
:height="isAutoHeight ?height-20:height" />
</view>
<icon class="uni-combox__icon" type="clear" size="16" @tap="clearInputValue"
v-if="inputVal!=''" style="height: 100%;align-items: center;justify-content: center;" />
<!-- <image size="16" src="/static/icons/close-circle2.svg"
style="height: 35%;width: 10%;align-items: center;justify-content: center;background-color: aqua;display: flex;"> -->
<!-- </image> -->
<view :style="{width:height + 'rpx'}" class="u-dropdown__menu__item__arrow" :class="{
'u-dropdown__menu__item__arrow--rotate': active
}" @tap.stop="menuClick()">
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon"
:size="$u.addUnit(menuIconSize)" :color="active ? activeColor : '#c0c4cc'"></u-icon>
</view>
</view>
</view>
</view>
<view class="u-dropdown__content" :style="[contentStyle, {
transition: `opacity ${duration / 1000}s linear`,
top: $u.addUnit(height+28),
height: contentHeight + 'px'}]" @tap="maskClick" @touchmove.stop.prevent>
<view class="u-dropdown__content__popup" :style="[popupStyle]" @touchmove.stop.prevent="() => {}"
@tap.stop.prevent="() => {}">
<view class="uni-combox__selector-empty" v-if="optionsLength === 0" @click="empty()">
<text>{{emptyTips}}</text>
</view>
<view v-for="(item, index) in filterOptions" style="width: 100%;"
@click="onSelectorClick(index)">
<view v-if="itemTextCount == 1" class="uni-combox-item">
<text class="uni-combox-item-text" :title-style="{
color: inputVal === item ? activeColor : inactiveColor
}">{{item}}</text>
<u-icon v-if="inputVal === item" name="checkbox-mark" :color="activeColor" size="32">
</u-icon>
</view>
<view v-if="itemTextCount == 2" class="uni-combox-item2">
<text class="uni-combox-item-text" :title-style="{
color: inputVal === item ? activeColor : inactiveColor
}">{{item.value}}</text>
<text class="uni-combox-item-text" :title-style="{
color: inputVal === item ? activeColor : inactiveColor
}">{{item.desc}}</text>
</view>
<u-line class="line_color"></u-line>
</view>
</view>
<!-- <view class="u-dropdown__content__mask"></view> -->
</view>
</view>
</view>
</view>
</template>
<script>
/**
* dropdown 下拉菜单
* @description 菜单标题与编辑框排列方向,即水平排列row/垂直排列column,默认水平排列row
* @tutorial http://uviewui.com/components/dropdown.html
* @property {String} active-color 标题和选项卡选中的颜色(默认#2979ff)
* @property {String} inactive-color 标题和选项卡未选中的颜色(默认#606266)
* @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true)
* @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单(默认true)
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
* @property {String | Number} height 标题菜单的高度,单位任意(默认80)
* @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认0)
* @property {Boolean} border-bottom 标题菜单是否显示下边框(默认false)
* @property {String | Number} title-size 标题的字体大小,单位任意,数值默认为rpx单位(默认28)
* @event {Function} open 下拉菜单被打开时触发
* @event {Function} close 下拉菜单被关闭时触发
* @example <pulldown></pulldown>
*/
export default {
name: 'pulldown',
emits: ["open", "close", "update:modelValue", "input", "change", "confirm"],
props: {
// 菜单标题与编辑框排列方向,水平排列row/垂直排列column
description: {
type: String,
default: 'row'
},
// 菜单标题
label: {
type: String,
default: ''
},
// 菜单标题长度
labelWidth: {
type: String,
default: 'auto'
},
// 菜单标题和选项的激活态颜色
activeColor: {
type: String,
default: '#2979ff'
},
// 菜单标题和选项的未激活态颜色
inactiveColor: {
type: String,
default: '#606266'
},
// 点击遮罩是否关闭菜单
closeOnClickMask: {
type: Boolean,
default: true
},
// 点击当前激活项标题是否关闭菜单
closeOnClickSelf: {
type: Boolean,
default: true
},
// 过渡时间
duration: {
type: [Number, String],
default: 300
},
// 标题菜单的高度,单位任意,数值默认为rpx单位
height: {
type: [Number, String],
default: 55
},
// 是否显示下边框
borderBottom: {
type: Boolean,
default: false
},
// 标题的字体大小
titleSize: {
type: [Number, String],
default: 28
},
// 下拉出来的内容部分的圆角值
borderRadius: {
type: [Number, String],
default: 0
},
// 菜单右侧的icon图标
menuIcon: {
type: String,
default: 'arrow-down'
},
// 菜单右侧图标的大小
menuIconSize: {
type: [Number, String],
default: 26
},
emptyTips: {
type: String,
default: '无匹配项'
},
// 是否开启搜索模式
isSearch: {
type: Boolean,
default: false
},
// 占位提示文字
hint: {
type: String,
default: ''
},
// 搜索图标的颜色,默认同输入框字体颜色
searchIconColor: {
type: String,
default: ''
},
// 输入框字体颜色
color: {
type: String,
default: '#606266'
},
// 左边输入框的图标,可以为uView图标名称或图片路径
searchIcon: {
type: String,
default: 'search'
},
// 选项数据,如果传入了默认slot,此参数无效
options: {
type: Array,
default () {
return [];
}
},
// 搜索图标的颜色,默认同输入框字体颜色
itemTextCount: {
type: Number,
default: 1
},
//输入框的高度是否随着行数增加,而自动增加输入框的高度
isAutoHeight: {
type: Boolean,
default: false
},
},
data() {
return {
inputVal: '',
showDropdown: true, // 是否打开下来菜单,
menuList: [], // 显示的菜单
active: false, // 下拉菜单的状态
// 当前是第几个菜单处于激活状态,小程序中此处不能写成false或者"",否则后续将current赋值为0,
// 无能的TX没有使用===而是使用==判断,导致程序认为前后二者没有变化,从而不会触发视图更新
current: 99999,
// 外层内容的样式,初始时处于底层,且透明
contentStyle: {
// zIndex: 11,
zIndex: -1,
opacity: 0
},
// 让某个菜单保持高亮的状态
highlightIndex: 99999,
contentHeight: 0
}
},
computed: {
// 下拉出来部分的样式
popupStyle() {
let style = {};
// 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏
style.transform = `translateY(${this.active ? 0 : '-100%'})`
style['transition-duration'] = this.duration / 1000 + 's';
style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
return style;
},
labelStyle() {
if (this.labelWidth === 'auto') {
return {}
}
return {
width: this.labelWidth
}
},
optionsLength() {
return this.options.length
},
filterOptions() {
if (this.isSearch) {
return this.options.filter((item) => {
return item.indexOf(this.inputVal) > -1
})
} else {
return this.options;
}
},
placeholder() {
if (this.hint) {
return this.hint;
} else {
if (this.isSearch) {
return '请输入关键字';
} else {
return '请选择';
}
}
},
},
created() {
// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
this.children = [];
},
mounted() {
this.getContentHeight();
},
methods: {
//清空数据
clearInputValue() {
this.inputVal = "";
},
//回车监听
confirm() {
this.$emit("confirm", this.inputVal);
},
init() {
// 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍
// 以保证数据的正确性
this.menuList = [];
this.children.map(child => {
child.init();
})
},
// 点击菜单
menuClick() {
// 判断是否被禁用
// if (this.menuList[index].disabled) return;
// 如果点击时的索引和当前激活项索引相同,意味着点击了激活项,需要收起下拉菜单
if (this.active) {
this.close();
// 等动画结束后,再移除下拉菜单中的内容,否则直接移除,也就没有下拉菜单收起的效果了
// setTimeout(() => {
// this.active = false;
// }, this.duration)
return;
}
this.open();
},
onSelectorClick(index) {
// console.log(index);
if (this.itemTextCount == 1) {
this.inputVal = this.options[index];
this.close();
// 修改通过v-model绑定的值
this.$emit("input", this.inputVal);
this.$emit("update:modelValue", this.inputVal);
// 发出事件,抛出当前勾选项的value
this.$emit("change", index, this.inputVal);
} else if (this.itemTextCount == 2) {
this.inputVal = this.options[index].value;
this.close();
// 修改通过v-model绑定的值
this.$emit("input", this.inputVal);
this.$emit("update:modelValue", this.inputVal);
// 发出事件,抛出当前勾选项的value
this.$emit("change", index, this.inputVal);
}
},
// 打开下拉菜单
open() {
console.log(this.options[0]);
console.log(this.options.length);
// console.log(this.filterOptions());
// 重置高亮索引,否则会造成多个菜单同时高亮
// this.highlightIndex = 9999;
// 展开时,设置下拉内容的样式
this.contentStyle = {
zIndex: 11,
}
this.$forceUpdate();
// 标记展开状态以及当前展开项的索引
this.active = true;
// this.current = index;
// 历遍所有的子元素,将索引匹配的项标记为激活状态,因为子元素是通过v-if控制切换的
// 之所以不是因display: none,是因为nvue没有display这个属性
this.$emit('open', this.current);
},
// 设置下拉菜单处于收起状态
close() {
this.$emit('close', this.current);
// 设置为收起状态,同时current归位,设置为空字符串
this.active = false;
this.current = 99999;
// 下拉内容的样式进行调整,不透明度设置为0
this.contentStyle = {
zIndex: -1,
opacity: 0
}
},
//点击无匹配选项关闭下拉菜单
empty() {
// 设置为收起状态,同时current归位,设置为空字符串
this.active = false;
this.current = 99999;
// 下拉内容的样式进行调整,不透明度设置为0
this.contentStyle = {
zIndex: -1,
opacity: 0
}
},
// 点击遮罩
maskClick() {
// 如果不允许点击遮罩,直接返回
if (!this.closeOnClickMask) return;
this.close();
},
// 外部手动设置某个菜单高亮
highlight(index = undefined) {
this.highlightIndex = index !== undefined ? index : 99999;
},
// 获取下拉菜单内容的高度
getContentHeight() {
// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
// 才能让遮罩占满菜单一下,直到屏幕底部的高度
// this.$u.sys()为uView封装的获取设备信息的方法
let windowHeight = this.$u.sys().windowHeight;
this.$uGetRect('.u-dropdown__menu').then(res => {
// 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本)
// H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离
// 二者是互相矛盾的,本质原因是H5端导航栏非原生,uni的开发者大意造成
// 这里取菜单栏的botton值合理的,不能用res.top,否则页面会造成滚动
this.contentHeight = windowHeight - res.bottom;
// console.log(this.contentHeight);
// console.log(res.bottom);
})
}
}
}
</script>
<style scoped lang="scss">
@import "../../uni_modules/vk-uview-ui/libs/css/style.components.scss";
.u-input {
width: 100%;
padding-top: 5px;
padding-bottom: 5px;
}
.u-clear-icon {
@include vue-flex;
align-items: center;
}
.u-close-wrap {
width: 30px;
height: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.maskbox {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 4;
}
.uni-combox-row {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
/* #endif */
min-height: 40px;
flex-direction: row;
align-items: center;
position: relative;
background-color: #FFFFFF;
// padding-right: 5px;
// padding-left: 5px;
// z-index: 3;
}
.uni-combox-column {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
/* #endif */
min-height: 40px;
flex-direction: column;
align-items: flex-start;
position: relative;
background-color: #FFFFFF;
// padding-right: 5px;
// padding-left: 5px;
// z-index: 3;
}
.uni-combox__label {
font-size: 16px;
line-height: 22px;
padding-right: 10px;
}
.uni-combox__selector-empty,
.uni-combox__selector-item {
/* #ifdef APP-NVUE */
display: flex;
/* #endif */
line-height: 36px;
font-size: 14px;
text-align: center;
border-bottom: solid 1px #DDDDDD;
/* margin: 0px 10px; */
}
.uni-combox__icon {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
background-color: #FFFFFF;
z-index: 4;
}
.uni-combox__input {
// flex: 1;
font-size: 16px;
height: 100%;
line-height: 100%;
background-color: #FFFFFF;
width: 100%;
z-index: 5;
}
.uni-combox-item {
display: flex;
flex-direction: row;
font-size: 16px;
height: 100%;
line-height: 100%;
background-color: #FFFFFF;
width: 100%;
}
.uni-combox-item2 {
display: flex;
flex-direction: column;
font-size: 16px;
height: 100%;
line-height: 100%;
background-color: #FFFFFF;
width: 100%;
}
.uni-combox-item-text {
font-size: 16px;
height: 100%;
line-height: 100%;
background-color: #FFFFFF;
width: 100%;
padding: 20rpx;
display: flex;
}
.uni-combox__input-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: column;
}
.uni-combox__selector {
box-sizing: border-box;
position: absolute;
top: 42px;
left: 0;
width: 100%;
background-color: #FFFFFF;
border-radius: 6px;
box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px;
z-index: 2;
}
.u-dropdown {
flex: 1;
width: 100%;
position: relative;
background-color: #FFFFFF;
margin-right: 2rpx;
margin-left: 2rpx;
&__menu {
@include vue-flex;
// position: relative;
z-index: 11;
width: 100%;
&__item {
// flex: 1;
@include vue-flex;
justify-content: center;
// align-items: center;
border: 2rpx solid $uni-border-color;
width: 100%;
margin-top: 2rpx;
// margin-right: 2rpx;
// margin-left: 2rpx;
&__text {
font-size: 28rpx;
color: $u-content-color;
}
&__arrow {
transition: transform .3s;
align-items: center;
justify-content: center;
@include vue-flex;
&--rotate {
transform: rotate(180deg);
}
}
}
}
&__content {
position: absolute;
z-index: 8;
width: 100%;
left: 0px;
bottom: 0;
overflow: hidden;
// z-index: 10;
// box-sizing: border-box;
// position: absolute;
// top: 42px;
// left: 0;
// width: 100%;
// // background-color: #FFFFFF;
// border-radius: 6px;
// box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px;
// z-index: 10;
&__mask {
position: relative;
z-index: 9;
background: rgba(0, 0, 0, .3);
width: 100%;
height: 100%;
left: 0;
top: 0;
bottom: 0;
}
&__popup {
position: relative;
z-index: 11;
transition: all 0.3s;
transform: translate3D(0, -100%, 0);
overflow: hidden;
width: 100%;
background-color: aliceblue;
border: 2rpx solid $uni-border-color;
// z-index: 10;
}
}
}
</style>