123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- <style lang="scss">
- .hqs-popup {
- .qs-con {
- background: #fff;
- }
- .qs-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- position: relative;
- }
- .qs-title {
- font-size: 16px;
- font-weight: bold;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
- .hidden { visibility: hidden; }
- .qs-side {
- min-width: 60px;
- padding: 15px 20px;
- color: #888;
- flex-shrink: 0;
- &:active { opacity: .8; }
- }
- .qs-h-scroll {
- height: 100vh;
- // #ifdef H5
- margin-top: 44px;
- height: calc(100vh - 44px);
- // #endif
- }
- .ta-r { text-align: right; }
- }
- </style>
- <template>
- <uni-popup ref="popup"
- :mask-click="maskClick" @change="onChange"
- :type="from" class="hqs-popup">
- <view class="qs-con"
- :style="conStyle"
- @mousedown="onTouch" @mousemove="onTouch" @mouseup="onTouch"
- @touchstart="onTouch" @touchmove="onTouch" @touchend="onTouch">
- <block v-if="isVertical">
- <view class="qs-header" v-if="from == 'bottom' && showHeader">
- <view class="qs-side" :class="{'hidden': !showBack && !$slots.back}"
- @click="onBack">
- <slot name="back">
- <text>返回</text>
- </slot>
- </view>
- <slot name="title">
- <text class="qs-title">{{ title }}</text>
- </slot>
- <view class="qs-side ta-r" :class="{'hidden': !showClose}"
- @click="close">
- <slot name="close">
- <u-icon :name="closeIcon" :size="32" v-if="closeIcon"></u-icon>
- <text v-else>关闭</text>
- </slot>
- </view>
- </view>
- <slot name="sub-header"></slot>
- <scroll-view scroll-y :style="{ height }"
- @scroll="onScroll">
- <view>
- <slot></slot>
- </view>
- </scroll-view>
- <slot name="bottom"></slot>
- </block>
- <block v-else-if="!isVertical">
- <scroll-view scroll-y class="qs-h-scroll" :style="{ width }">
- <slot></slot>
- </scroll-view>
- </block>
- </view>
- </uni-popup>
- </template>
- <script>
- import UniPopup from './uni-popup.vue'
- export default {
- components: {
- UniPopup,
- },
- props: {
- // 弹窗显示可通过v-model控制
- value: Boolean,
- // 弹窗打开开始方向
- from: {
- type: String,
- default: 'bottom',
- },
-
- // 内容区边缘圆角大小
- round: {
- type: Number,
- default: 10,
- },
- // 弹窗内容宽度(当from=left或right时起作用)
- width: {
- type: String,
- default: '60vw',
- },
- // 弹窗内容高度(当from=top或bottom时起作用)
- height: {
- type: String,
- default: '50vh',
- },
-
- // 显示默认头部标题栏(仅在底部弹出时有)
- showHeader: {
- type: Boolean,
- default: true,
- },
- // 弹窗标题
- title: String,
- // 显示左侧(返回)按钮,如果v-slot:back存在时,也会显示此按钮
- showBack: Boolean,
- // 显示关闭按钮,如果是字符串,将作为uview(需另外引入)的u-icon组件的name(如showClose="close",会显示close图标)
- showClose: {
- type: [Boolean, String],
- default: true,
- },
- // 是否可点击模态背景关闭弹窗
- maskClick: {
- type: Boolean,
- default: true,
- },
- },
- data() {
- return {
- scrollTop: 0,
- panStyle: '',
- showPopup: false,
- }
- },
- computed: {
- closeIcon() {
- if(typeof this.showClose == 'string') return this.showClose
- return false
- },
- isVertical() {
- return ['bottom', 'top'].includes(this.from)
- },
- conStyle() {
- let style = this.panStyle || ''
- let r = this.round
- if(r > 0) {
- r += 'px'
- if(this.from == 'bottom') r = [r, r, 0, 0]
- else if(this.from == 'left') r = [0, r, r, 0]
- else if(this.from == 'right') r = [r, 0, 0, r]
- else r = [0, 0, r, r]
- style += `border-radius: ${r.join(' ')};`
- }
- return style
- },
- },
- watch: {
- value(val) {
- if(val == this.showPopup) return
- if(val) this.open()
- else this.close()
- },
- showPopup(val) {
- this.$emit('input', val)
- },
- },
- mounted() {
- if(this.value) this.open()
- },
- methods: {
- onScroll(e) {
- this.scrollTop = e.detail.scrollTop
- },
- onTouch(ev) {
- // if(!this.maskClick) return
- const { pageX, pageY } = ev.changedTouches[0] || ev
- if(['touchstart', 'mousedown'].includes(ev.type)) {
- this.startX = pageX
- this.startY = pageY
- this.startTime = ev.timeStamp
- } else {
- if(!this.startTime) return
- const orien = this.isVertical ? 'Y' : 'X'
- let moveDis = pageY - this.startY
- if(this.from == 'left') moveDis = this.startX - pageX
- else if(this.from == 'right') moveDis = pageX - this.startX
- else if(this.from == 'top') moveDis = -moveDis
- if(!this.maskClick) moveDis /= 3
- const duration = (ev.timeStamp - this.startTime)
- const speed = moveDis/duration
- if(['touchend', 'mouseup'].includes(ev.type)) {
- if(this.panStyle) {
- if(this.maskClick && (moveDis > 120 || speed > 0.25)) {
- this.close()
- }
- else {
- this.panStyle = `transform: translate${orien}(0); transition: all ease 200ms;`
- }
- setTimeout(() => {
- this.panStyle = ''
- }, 300)
- }
- // conScrollTop = 0
- this.startTime = 0
- return
- }
- // if(this.scrollTop > 0) return
-
- if(moveDis > 0) {
- if(this.from == 'left' || this.from == 'top') moveDis *= -1
- this.panStyle = `transform: translate${orien}(${moveDis}px); transition: none;`
- }
- }
- },
- onChange({ show }) {
- this.showPopup = show
- },
- open() {
- this.$refs.popup.open()
- this.showPopup = true
- },
- close() {
- this.$refs.popup.close()
- this.showPopup = false
- },
- onBack() {
- this.$emit('back')
- },
- }
- }
- </script>
|