| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- <template>
- <view
- class="mine-drawer-stage"
- :class="{ 'mine-drawer-stage--open': open }"
- >
- <!-- 中间缝隙(强调左右两块分离) -->
- <view
- v-show="open"
- class="mine-drawer-gutter"
- :style="gutterStyle"
- />
- <!-- 右侧设置层 -->
- <view
- class="mine-drawer-panel"
- :class="{ 'mine-drawer-panel--open': open }"
- :style="panelLayerStyle"
- >
- <slot name="drawer" />
- </view>
- <!-- 左侧点击关闭热区 -->
- <view
- v-show="open"
- class="mine-drawer-dismiss"
- :style="dismissStyle"
- @tap="onDismiss"
- >
- <view class="mine-drawer-dismiss-inner">
- <text class="mine-drawer-dismiss-icon">‹</text>
- </view>
- <text class="mine-drawer-dismiss-tip">{{ dismissTip }}</text>
- </view>
- <!-- 主内容卡片 -->
- <view
- class="mine-drawer-main"
- :class="{ 'mine-drawer-main--open': open }"
- :style="mainLayerStyle"
- @touchstart="onMainTouchStart"
- @touchmove="onMainTouchMove"
- @touchend="onMainTouchEnd"
- @touchcancel="onMainTouchEnd"
- >
- <slot />
- </view>
- </view>
- </template>
- <script>
- const MAIN_SCALE = 0.837
- const MAIN_GAP_RPX = 32
- const CARD_INSET_RPX = 28
- /** 左侧预览 : 右侧抽屉 = 2.5 : 7.5(占满屏宽) */
- const PEEK_RATIO = 0.25
- const PANEL_RATIO = 0.75
- /**
- * 主屏平移:先贴到 25% 分界线,再额外左推 EXTRA,最后收回 BACK(往右)
- * 40rpx 收回 ≈ 屏宽 40/750;折算后 EXTRA 约 10%
- */
- const MAIN_EXTRA_SHIFT_RATIO = 0.1
- const MAIN_SHIFT_BACK_RPX = 40
- const CLOSE_DRAG_THRESHOLD = 56
- export default {
- name: 'MineDrawerShell',
- props: {
- open: {
- type: Boolean,
- default: false
- },
- dismissTip: {
- type: String,
- default: '返回'
- }
- },
- data() {
- return {
- winWidth: 375,
- panelWidthPx: 300,
- panelLeftPx: 94,
- gutterLeftPx: 86,
- peekWidthPx: 75,
- gapPx: 16,
- shiftBackPx: 20,
- cardInsetPx: 14,
- safeTop: 0,
- safeBottom: 0,
- dragOffset: 0,
- dragging: false,
- _touchStartX: 0,
- _touchStartY: 0,
- _touchAxis: ''
- }
- },
- computed: {
- panelLayerStyle() {
- const base = {
- paddingTop: `${this.safeTop}px`,
- paddingBottom: `${this.safeBottom}px`
- }
- if (!this.open) {
- return {
- ...base,
- width: `${this.panelWidthPx}px`
- }
- }
- return {
- ...base,
- left: `${this.panelLeftPx}px`,
- right: `${this.cardInsetPx}px`,
- width: 'auto',
- top: `${this.cardInsetPx}px`,
- bottom: `${this.cardInsetPx}px`,
- height: 'auto'
- }
- },
- gutterStyle() {
- return {
- left: `${this.gutterLeftPx}px`,
- width: `${this.gapPx}px`,
- top: `${this.cardInsetPx}px`,
- bottom: `${this.cardInsetPx}px`
- }
- },
- dismissStyle() {
- return { width: `${this.peekWidthPx}px` }
- },
- mainBaseShiftPx() {
- return Math.max(this.winWidth - this.peekWidthPx, 0)
- },
- mainExtraShiftPx() {
- return Math.round(this.winWidth * MAIN_EXTRA_SHIFT_RATIO)
- },
- mainTotalShiftPx() {
- const total = this.mainBaseShiftPx + this.mainExtraShiftPx - this.shiftBackPx
- return Math.max(total, 0)
- },
- mainShiftPx() {
- return this.mainTotalShiftPx
- },
- mainLayerStyle() {
- if (!this.open) return {}
- const shift = this.mainTotalShiftPx - this.dragOffset
- const style = {
- left: '0',
- right: '0',
- width: `${this.winWidth}px`,
- borderRadius: '28rpx',
- overflow: 'hidden',
- /* 必须先平移再缩放,否则 translate 在缩放坐标系里几乎不动 */
- transform: `translate3d(-${shift}px, 0, 0) scale(${MAIN_SCALE})`,
- transformOrigin: 'right center',
- top: `${this.cardInsetPx}px`,
- bottom: `${this.cardInsetPx}px`,
- height: 'auto',
- minHeight: '0'
- }
- if (this.dragging) {
- style.transition = 'none'
- }
- return style
- }
- },
- watch: {
- open(val) {
- if (!val) {
- this.dragOffset = 0
- this.dragging = false
- }
- this.togglePageScroll(val)
- }
- },
- created() {
- this.measureViewport()
- if (typeof uni.onWindowResize === 'function') {
- uni.onWindowResize(this.measureViewport)
- }
- },
- beforeDestroy() {
- if (typeof uni.offWindowResize === 'function') {
- uni.offWindowResize(this.measureViewport)
- }
- this.togglePageScroll(false)
- },
- methods: {
- measureViewport() {
- try {
- const info = uni.getSystemInfoSync()
- this.winWidth = info.windowWidth || 375
- this.safeTop = info.statusBarHeight || 0
- this.safeBottom = (info.safeAreaInsets && info.safeAreaInsets.bottom) || 0
- this.gapPx = uni.upx2px(MAIN_GAP_RPX)
- this.shiftBackPx = uni.upx2px(MAIN_SHIFT_BACK_RPX)
- this.cardInsetPx = uni.upx2px(CARD_INSET_RPX)
- this.peekWidthPx = Math.round(this.winWidth * PEEK_RATIO)
- this.panelLeftPx = this.peekWidthPx
- this.panelWidthPx = Math.round(this.winWidth * PANEL_RATIO)
- this.gutterLeftPx = Math.max(this.peekWidthPx - this.gapPx, 0)
- } catch (_) {}
- },
- onDismiss() {
- if (!this.open) return
- this.$emit('close')
- },
- onMainTouchStart(e) {
- if (!this.open || !e.touches || !e.touches.length) return
- this._touchStartX = e.touches[0].clientX
- this._touchStartY = e.touches[0].clientY
- this._touchAxis = ''
- this.dragging = false
- },
- onMainTouchMove(e) {
- if (!this.open || !e.touches || !e.touches.length) return
- const dx = e.touches[0].clientX - this._touchStartX
- const dy = e.touches[0].clientY - this._touchStartY
- if (!this._touchAxis) {
- if (Math.abs(dx) < 8 && Math.abs(dy) < 8) return
- this._touchAxis = Math.abs(dx) >= Math.abs(dy) ? 'x' : 'y'
- }
- if (this._touchAxis !== 'x') return
- if (dx <= 0) {
- this.dragOffset = 0
- return
- }
- this.dragging = true
- this.dragOffset = Math.min(dx, this.mainShiftPx)
- },
- onMainTouchEnd() {
- if (!this.open) return
- if (this.dragOffset >= CLOSE_DRAG_THRESHOLD) {
- this.$emit('close')
- }
- this.dragOffset = 0
- this.dragging = false
- this._touchAxis = ''
- },
- togglePageScroll(lock) {
- // #ifdef H5
- try {
- document.body.style.overflow = lock ? 'hidden' : ''
- } catch (_) {}
- // #endif
- }
- }
- }
- </script>
- <style scoped>
- .mine-drawer-stage {
- position: relative;
- min-height: 100vh;
- width: 100%;
- overflow-x: hidden;
- background-color: #f4f6fb;
- }
- .mine-drawer-stage--open {
- position: fixed;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 10050;
- overflow: hidden;
- background-color: #d8dee8;
- }
- .mine-drawer-gutter {
- position: fixed;
- z-index: 4;
- pointer-events: none;
- border-radius: 8rpx;
- background-color: #d8dee8;
- }
- .mine-drawer-panel {
- position: fixed;
- top: 0;
- right: 0;
- height: 100vh;
- z-index: 2;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- background: #f6f8fc;
- transform: translateX(100%);
- opacity: 0;
- visibility: hidden;
- pointer-events: none;
- transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- opacity 0.32s ease,
- visibility 0.42s,
- top 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- bottom 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- right 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- border-radius 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- box-shadow 0.42s cubic-bezier(0.32, 0.72, 0, 1);
- will-change: transform, opacity;
- }
- .mine-drawer-panel--open {
- z-index: 7;
- transform: translateX(0);
- opacity: 1;
- visibility: visible;
- pointer-events: all;
- border-radius: 28rpx;
- border: 2rpx solid #ffffff;
- overflow: hidden;
- box-shadow: 0 12rpx 48rpx rgba(15, 23, 42, 0.12);
- }
- .mine-drawer-dismiss {
- position: fixed;
- left: 0;
- top: 0;
- height: 100vh;
- z-index: 6;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .mine-drawer-dismiss-inner {
- width: 72rpx;
- height: 72rpx;
- border-radius: 50%;
- background: #ffffff;
- border: 1rpx solid #e5e7eb;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 6rpx 20rpx rgba(15, 23, 42, 0.08);
- }
- .mine-drawer-dismiss-icon {
- font-size: 44rpx;
- color: #4b5563;
- line-height: 1;
- font-weight: 300;
- margin-right: 4rpx;
- }
- .mine-drawer-dismiss-tip {
- margin-top: 16rpx;
- font-size: 22rpx;
- color: #6b7280;
- letter-spacing: 2rpx;
- }
- .mine-drawer-main {
- position: relative;
- z-index: 3;
- min-height: 100vh;
- width: 100%;
- background-color: #f4f6fb;
- transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- border-radius 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- box-shadow 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- top 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- bottom 0.42s cubic-bezier(0.32, 0.72, 0, 1),
- height 0.42s cubic-bezier(0.32, 0.72, 0, 1);
- transform-origin: 100% 50%;
- will-change: transform;
- }
- .mine-drawer-main--open {
- position: fixed;
- left: 0;
- right: 0;
- z-index: 4;
- border-radius: 28rpx;
- border: 2rpx solid #ffffff;
- overflow: hidden;
- box-shadow: 0 12rpx 48rpx rgba(15, 23, 42, 0.14);
- }
- </style>
|