feat: ui update
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
// 日志模块名
|
||||
const LOG = 'v2-overlay'
|
||||
|
||||
/**
|
||||
* v2 统一玻璃弹层。
|
||||
*
|
||||
* 三类形态:
|
||||
* modal 居中玻璃卡(确认/提示),buttons 决定按钮,事件 confirm/cancel
|
||||
* actionsheet 底部动作表(选择),items 决定条目,事件 select/cancel
|
||||
* toast 深色玻璃胶囊,duration 后自动关闭并抛出 close
|
||||
*
|
||||
* 页面侧只维护 visible 与文案数据,回调分支与原生 wx.showModal 一一对应。
|
||||
*/
|
||||
Component({
|
||||
properties: {
|
||||
visible: { type: Boolean, value: false },
|
||||
type: { type: String, value: 'modal' },
|
||||
title: { type: String, value: '' },
|
||||
content: { type: String, value: '' },
|
||||
// modal 按钮:[{ text, value, type: 'main' | 'normal' }]
|
||||
buttons: { type: Array, value: [] },
|
||||
// actionsheet 条目:['拍照', '从相册选择'] 或 [{ text, value }]
|
||||
items: { type: Array, value: [] },
|
||||
cancelText: { type: String, value: '取消' },
|
||||
// toast 自动关闭时长(ms)
|
||||
duration: { type: Number, value: 1800 },
|
||||
},
|
||||
|
||||
observers: {
|
||||
visible(v: boolean) {
|
||||
if (!v) {
|
||||
this.clearTimer()
|
||||
return
|
||||
}
|
||||
if (this.data.type === 'toast') this.startTimer()
|
||||
},
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
detached() {
|
||||
this.clearTimer()
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
startTimer() {
|
||||
this.clearTimer()
|
||||
const t = setTimeout(() => {
|
||||
logger.debug(LOG, 'toast 自动关闭')
|
||||
this.triggerEvent('close')
|
||||
}, this.data.duration)
|
||||
;(this as any).timer = t
|
||||
},
|
||||
|
||||
clearTimer() {
|
||||
const t = (this as any).timer
|
||||
if (t) {
|
||||
clearTimeout(t)
|
||||
;(this as any).timer = null
|
||||
}
|
||||
},
|
||||
|
||||
onBtn(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = Number((e.currentTarget.dataset as any).index) || 0
|
||||
const btn = (this.data.buttons[index] || {}) as any
|
||||
logger.debug(LOG, 'modal 按钮点击, index=', index, 'value=', btn.value)
|
||||
this.triggerEvent('confirm', { index, value: btn.value || '' })
|
||||
},
|
||||
|
||||
onItem(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = Number((e.currentTarget.dataset as any).index) || 0
|
||||
const raw = this.data.items[index] as any
|
||||
const value = raw && typeof raw === 'object' ? raw.value || '' : raw
|
||||
logger.debug(LOG, 'actionsheet 条目点击, index=', index, 'value=', value)
|
||||
this.triggerEvent('select', { index, value })
|
||||
},
|
||||
|
||||
onCancel() {
|
||||
logger.debug(LOG, '弹层取消')
|
||||
this.triggerEvent('cancel')
|
||||
},
|
||||
|
||||
// 阻断蒙层下的页面滚动
|
||||
noop() {},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- v2 玻璃弹层:居中玻璃卡 Modal / 底部动作表 ActionSheet / 深色玻璃胶囊 Toast -->
|
||||
<view wx:if="{{visible}}" class="ov-root" catchtouchmove="noop">
|
||||
<!-- 居中玻璃卡 -->
|
||||
<block wx:if="{{type === 'modal'}}">
|
||||
<view class="scrim"></view>
|
||||
<view class="modal-wrap">
|
||||
<view class="modal">
|
||||
<view class="modal-body">
|
||||
<view wx:if="{{title}}" class="modal-title">{{title}}</view>
|
||||
<view wx:if="{{content}}" class="modal-text">{{content}}</view>
|
||||
</view>
|
||||
<view class="modal-acts">
|
||||
<view
|
||||
wx:for="{{buttons}}"
|
||||
wx:key="index"
|
||||
class="modal-act {{item.type === 'main' ? 'modal-act--main' : ''}}"
|
||||
data-index="{{index}}"
|
||||
hover-class="ov-press"
|
||||
hover-stay-time="80"
|
||||
bindtap="onBtn"
|
||||
>{{item.text}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 底部动作表 -->
|
||||
<block wx:elif="{{type === 'actionsheet'}}">
|
||||
<view class="scrim" bindtap="onCancel"></view>
|
||||
<view class="as-wrap">
|
||||
<view class="as-card">
|
||||
<view wx:if="{{title}}" class="as-hd">{{title}}</view>
|
||||
<view
|
||||
wx:for="{{items}}"
|
||||
wx:key="index"
|
||||
class="as-item"
|
||||
data-index="{{index}}"
|
||||
hover-class="ov-press"
|
||||
hover-stay-time="80"
|
||||
bindtap="onItem"
|
||||
>{{item.text || item}}</view>
|
||||
</view>
|
||||
<view class="as-cancel" bindtap="onCancel">{{cancelText}}</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 深色玻璃胶囊 -->
|
||||
<block wx:else>
|
||||
<view class="toast-wrap">
|
||||
<view class="toast">{{content}}</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
@@ -0,0 +1,159 @@
|
||||
/** v2 弹层:毛玻璃 + 发丝线 + 苹果级缓动 **/
|
||||
.ov-root {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
}
|
||||
|
||||
.scrim {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, .28);
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
backdrop-filter: blur(2px);
|
||||
animation: ovFade .28s cubic-bezier(.32, .72, 0, 1) both;
|
||||
}
|
||||
@keyframes ovFade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
.ov-press { opacity: .6; }
|
||||
|
||||
/* Modal */
|
||||
.modal-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 68rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
.modal {
|
||||
width: 100%;
|
||||
max-width: 576rpx;
|
||||
border-radius: var(--r-sheet, 40rpx);
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
background: rgba(255, 255, 255, .86);
|
||||
-webkit-backdrop-filter: saturate(180%) blur(30px);
|
||||
backdrop-filter: saturate(180%) blur(30px);
|
||||
border: 1rpx solid rgba(255, 255, 255, .7);
|
||||
box-shadow: 0 48rpx 120rpx -36rpx rgba(0, 0, 0, .32);
|
||||
animation: ovModalIn .42s cubic-bezier(.32, .72, 0, 1) both;
|
||||
}
|
||||
@keyframes ovModalIn {
|
||||
from { opacity: 0; transform: scale(.94); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
.modal-body { padding: 44rpx 40rpx 36rpx; text-align: center; }
|
||||
.modal-title {
|
||||
font-family: var(--serif, serif);
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
letter-spacing: -.005em;
|
||||
color: var(--ink, #0A0A0A);
|
||||
}
|
||||
.modal-text {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.6;
|
||||
color: var(--ink-2, #6E6E73);
|
||||
}
|
||||
.modal-acts { display: flex; border-top: 1rpx solid var(--hairline, rgba(0, 0, 0, .08)); }
|
||||
.modal-act {
|
||||
flex: 1;
|
||||
height: 96rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: var(--ink-2, #6E6E73);
|
||||
transition: background .16s cubic-bezier(.32, .72, 0, 1);
|
||||
}
|
||||
.modal-act + .modal-act { border-left: 1rpx solid var(--hairline, rgba(0, 0, 0, .08)); }
|
||||
.modal-act--main { color: var(--ink, #0A0A0A); font-weight: 600; }
|
||||
|
||||
/* ActionSheet */
|
||||
.as-wrap {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 16rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.as-card {
|
||||
border-radius: 28rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, .86);
|
||||
-webkit-backdrop-filter: saturate(180%) blur(30px);
|
||||
backdrop-filter: saturate(180%) blur(30px);
|
||||
border: 1rpx solid rgba(255, 255, 255, .7);
|
||||
box-shadow: 0 -20rpx 80rpx -36rpx rgba(0, 0, 0, .3);
|
||||
animation: ovAsIn .4s cubic-bezier(.32, .72, 0, 1) both;
|
||||
}
|
||||
@keyframes ovAsIn {
|
||||
from { opacity: 0; transform: translateY(24rpx); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
.as-hd {
|
||||
padding: 26rpx 32rpx;
|
||||
text-align: center;
|
||||
font-size: 25rpx;
|
||||
color: var(--ink-3, #AEAEB2);
|
||||
border-bottom: 1rpx solid var(--hairline, rgba(0, 0, 0, .08));
|
||||
}
|
||||
.as-item {
|
||||
height: 104rpx;
|
||||
line-height: 104rpx;
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: var(--ink, #0A0A0A);
|
||||
border-bottom: 1rpx solid var(--hairline, rgba(0, 0, 0, .08));
|
||||
transition: background .16s cubic-bezier(.32, .72, 0, 1);
|
||||
}
|
||||
.as-item:last-child { border-bottom: 0; }
|
||||
.as-cancel {
|
||||
margin-top: 16rpx;
|
||||
height: 104rpx;
|
||||
line-height: 104rpx;
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: var(--ink, #0A0A0A);
|
||||
border-radius: 28rpx;
|
||||
background: rgba(255, 255, 255, .86);
|
||||
-webkit-backdrop-filter: saturate(180%) blur(30px);
|
||||
backdrop-filter: saturate(180%) blur(30px);
|
||||
border: 1rpx solid rgba(255, 255, 255, .7);
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast {
|
||||
max-width: 460rpx;
|
||||
padding: 26rpx 40rpx;
|
||||
border-radius: 28rpx;
|
||||
background: rgba(28, 28, 30, .82);
|
||||
color: #fff;
|
||||
font-size: 27rpx;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
backdrop-filter: blur(24px);
|
||||
box-shadow: 0 36rpx 88rpx -32rpx rgba(0, 0, 0, .5);
|
||||
animation: ovToastIn .34s cubic-bezier(.32, .72, 0, 1) both;
|
||||
}
|
||||
@keyframes ovToastIn {
|
||||
from { opacity: 0; transform: scale(.92) translateY(12rpx); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
Reference in New Issue
Block a user