popup 弹窗 picker-view 滚动选择器
组件链接: https://ext.dcloud.net.cn/plugin?id=7960
<m-picker mode="bottom" :show.sync="show" :range="list" @confirm="confirm" />
一、自定义popup类型的picker-view滚动选择器
- 组件是基于uniapp开发的,过度动画使用了uniapp的
<uni-transition></uni-transition>
组件
Mark 遮罩层
<uni-transition v-if="mark" ref="aniMark" custom-class="transition" mode-class="fade" :styles="stylesMark" :show="show" @click="clickMark" />
popup内容
<uni-transition ref="ani" custom-class="transition" :mode-class="modeClass" :styles="styles" :show="show">
<view class="m-picker">
<view class="m-picker__case" :style="{
background: bgColor,
borderTopLeftRadius: radius[0] + 'rpx',
borderTopRightRadius: radius[1] + 'rpx',
borderBottomLeftRadius: radius[2] + 'rpx',
borderBottomRightRadius: radius[3] + 'rpx',
paddingBottom: model == 'bottom' ? 'calc(constant(safe-area-inset-bottom) + 30rpx)' : '30rpx',
paddingBottom: model == 'bottom' ? 'calc(env(safe-area-inset-bottom) + 30rpx)' : '30rpx'
}">
<slot v-if="model == 'bottom'" name="handle">
<view class="handle">
<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
</view>
</slot>
<slot>
</slot>
<slot v-if="model != 'bottom'" name="handle">
<view class="handle">
<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
</view>
</slot>
</view>
</view>
</uni-transition>
picker-view 组件
<view class="content" :style="{height: `${height}rpx`}">
<picker-view class="picker-view" :indicator-class="indicatorClass" :indicator-style="indicatorStyle" :value="pickerValue" @change="bindChange" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-for="(rangeItem, rangeIndex) in range" :key="rangeIndex">
<view class="picker-view__item" v-for="(item, index) in rangeItem" :key="index">
{{ item[rangeKey] }}
</view>
</picker-view-column>
</picker-view>
</view>
props.js
const props = {
mark: {
type: Boolean,
default() {
return true
}
},
markClose: {
type: Boolean,
default() {
return true
}
},
btnClose: {
type: Boolean,
default() {
return true
}
},
show: {
type: Boolean,
default() {
return false
}
},
range: {
type: Array,
default: []
},
rangeKey: {
type: String,
default: 'label'
},
indicatorStyle: {
type: String,
default: 'height: 50px;'
},
indicatorClass: {
type: String,
default: ''
},
cancelText: {
type: String,
default: '取消'
},
confirmText: {
type: String,
default: '确定'
},
cancelStyle: {
type: Object,
default: {}
},
confirmStyle: {
type: Object,
default: {}
},
height: {
type: Number,
default: 500
},
borderRadius: {
type: Number,
default: 16
},
bgColor: {
type: String,
default: '#FFFFFF'
},
markBgColor: {
type: String,
default: '#00000080'
},
mode: {
type: String,
default: 'bottom'
}
}
export default props
接收样式参数
import props from './props.js'
export default {
name:"m-picker",
props,
computed: {
model() {
if (this.mode == 'top') return 'top';
else if (this.mode == 'bottom') return 'bottom';
else if (this.mode == 'center') return 'center';
else return 'bottom';
},
stylesMark() {
return {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 99,
backgroundColor: this.markBgColor,
}
},
styles() {
const top = {
position: 'fixed',
left: 0,
right: 0,
top: 0,
zIndex: 100
}
const bottom = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
zIndex: 100
}
const center = {
position: 'fixed',
left: '50%',
top: '50%',
width: '90vw',
transform: 'translate(-50%, -50%)',
zIndex: 100
}
if (this.model == 'top') return top;
else if (this.model == 'bottom') return bottom;
else if (this.model == 'center') return center;
else return bottom;
},
radius() {
const borderRadius = this.borderRadius;
if (this.model == 'top') return [0, 0, borderRadius, borderRadius];
else if (this.model == 'bottom') return [borderRadius, borderRadius, 0, 0];
else if (this.model == 'center') return [borderRadius, borderRadius, borderRadius, borderRadius];
else return [0, 0, 0, 0];
},
modeClass() {
if (this.model == 'top') return ['fade', 'slide-top'];
else if (this.model == 'bottom') return ['fade', 'slide-bottom'];
else if (this.model == 'center') return 'fade';
else return ['fade', 'slide-bottom'];
}
},
}
二、组件说明
Methods
方法称名 | 说明 | 参数 |
---|
confirm | 点击右侧按钮 | [] |
cancel | 点击左侧按钮 | |
字段
字段 | 类型 | 默认值 | 说明 |
---|
mark | Boolean | true | 是否显示遮罩 |
markClose | Boolean | true | 点击遮罩是否关闭 |
btnClose | Boolean | true | 点击按钮是否关闭 |
show | Boolean | fale | 是否显示弹窗 |
range | Array | [] | 数据 |
rangeKey | String | ‘label’ | picker-item显示内容的key |
indicatorStyle | String | ‘height: 50px;’ | 设置选择器中间选中框的样式 |
indicatorClass | String | ’ ’ | 设置选择器中间选中框的类名,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ |
cancelText | String | 取消 | cancel文字 |
confirmText | String | 确定 | confirm文字 |
cancelStyle | Object | {} | cancel样式style |
confirmlStyle | Object | {} | confirm样式style |
height | Number | 500 | 内容高度 rpx |
borderRadius | Number | 16 | 圆角 rpx |
bgColor | String | ‘#FFFFFF’ | 背景颜色 |
mode | String | bottom | 方向 top/bottom/center |
Slots
称名 | 说明 |
---|
default | 会覆盖默认的picker-view |
handle | 按钮部分 |
三、 图片演示
bottom
top
center
四、完整代码
<template>
<view class="pricke">
<uni-transition v-if="mark" ref="aniMark" custom-class="transition" mode-class="fade" :styles="stylesMark" :show="show" @click="clickMark"></uni-transition>
<uni-transition ref="ani" custom-class="transition" :mode-class="modeClass" :styles="styles" :show="show">
<view class="m-picker">
<view class="m-picker__case" :style="{
background: bgColor,
borderTopLeftRadius: radius[0] + 'rpx',
borderTopRightRadius: radius[1] + 'rpx',
borderBottomLeftRadius: radius[2] + 'rpx',
borderBottomRightRadius: radius[3] + 'rpx',
paddingBottom: model == 'bottom' ? 'calc(constant(safe-area-inset-bottom) + 30rpx)' : '30rpx',
paddingBottom: model == 'bottom' ? 'calc(env(safe-area-inset-bottom) + 30rpx)' : '30rpx'
}">
<slot v-if="model == 'bottom'" name="handle">
<view class="handle">
<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
</view>
</slot>
<slot>
<view class="content" :style="{height: `${height}rpx`}">
<picker-view class="picker-view" :indicator-class="indicatorClass" :indicator-style="indicatorStyle" :value="pickerValue" @change="bindChange" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-for="(rangeItem, rangeIndex) in range" :key="rangeIndex">
<view class="picker-view__item" v-for="(item, index) in rangeItem" :key="index">
{{ item[rangeKey] }}
</view>
</picker-view-column>
</picker-view>
</view>
</slot>
<slot v-if="model != 'bottom'" name="handle">
<view class="handle">
<view class="button cancel" :style="cancelStyle" @click="cancel">{{cancelText}}</view>
<view class="button confirm" :style="confirmStyle" @click="confirm">{{confirmText}}</view>
</view>
</slot>
</view>
</view>
</uni-transition>
</view>
</template>
<script>
import props from './props.js'
export default {
name:"m-picker",
props,
data() {
return {
pickerValue: [],
pickMove: false,
};
},
computed: {
model() {
if (this.mode == 'top') return 'top';
else if (this.mode == 'bottom') return 'bottom';
else if (this.mode == 'center') return 'center';
else return 'bottom';
},
stylesMark() {
return {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 99,
backgroundColor: this.markBgColor,
}
},
styles() {
const top = {
position: 'fixed',
left: 0,
right: 0,
top: 0,
zIndex: 100
}
const bottom = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
zIndex: 100
}
const center = {
position: 'fixed',
left: '50%',
top: '50%',
width: '90vw',
transform: 'translate(-50%, -50%)',
zIndex: 100
}
if (this.model == 'top') return top;
else if (this.model == 'bottom') return bottom;
else if (this.model == 'center') return center;
else return bottom;
},
radius() {
const borderRadius = this.borderRadius;
if (this.model == 'top') return [0, 0, borderRadius, borderRadius];
else if (this.model == 'bottom') return [borderRadius, borderRadius, 0, 0];
else if (this.model == 'center') return [borderRadius, borderRadius, borderRadius, borderRadius];
else return [0, 0, 0, 0];
},
modeClass() {
if (this.model == 'top') return ['fade', 'slide-top'];
else if (this.model == 'bottom') return ['fade', 'slide-bottom'];
else if (this.model == 'center') return 'fade';
else return ['fade', 'slide-bottom'];
}
},
watch: {
range(val) {
if (val.length) {
let arr = []
for (let index in this.range) {
arr.push(0);
}
this.pickerValue = arr;
}
}
},
created() {
if (this.range && this.range.length) {
this.pickerValue = [];
for (let index in this.range) {
this.pickerValue.push(0)
}
}
},
methods: {
close() {
if (!this.pickMove) this.$emit('update:show', false)
},
cancel() {
this.$emit('cancel')
if (this.btnClose) this.close()
},
confirm() {
this.$emit('confirm', this.pickerValue);
if (this.btnClose) this.close()
},
clickMark() {
if (this.markClose) this.close()
},
bindChange (e) {
const val = e.detail.value;
this.pickerValue = val;
},
pickstart() {
this.pickMove = true;
},
pickend() {
this.pickMove = false;
}
}
}
</script>
<style lang="scss">
.m-picker {
width: 100%;
height: 100%;
&__case {
padding-left: 56rpx;
padding-right: 56rpx;
padding-top: 30rpx;
padding-bottom: 30rpx;
.handle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.button {
font-size: 48rpx;
font-family: PingFang SC;
font-weight: 500;
color: #000000;
&.cancel {
}
&.confirm {
color: #FF8833;
}
}
}
.content {
width: 100%;
height: 400rpx;
}
}
}
.picker-view {
width: 100%;
height: 100%;
&__item {
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
}
}
</style>