fix: 修复 VoiceController Map.of 兼容性 + ExploreController 参数不匹配
- VoiceController: Map.of() -> Collections.singletonMap() 兼容 Java 8 - ExploreController: 补齐 takeoutService.roll() 缺失的 taste/priceRange/allergies 参数 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
105
miniapp/components/box-animation/box-animation.js
Normal file
105
miniapp/components/box-animation/box-animation.js
Normal file
@@ -0,0 +1,105 @@
|
||||
Component({
|
||||
properties: {
|
||||
show: { type: Boolean, value: false },
|
||||
boxType: { type: String, value: 'takeout' },
|
||||
dataReady: { type: Boolean, value: false }
|
||||
},
|
||||
|
||||
data: {
|
||||
act: 0,
|
||||
boxEmoji: '🎁',
|
||||
sparkEmoji: '✨',
|
||||
confettiColors: [],
|
||||
_pendingAct3: false
|
||||
},
|
||||
|
||||
observers: {
|
||||
'show': function(val) {
|
||||
if (val) {
|
||||
this.setData({ act: 0, _pendingAct3: false });
|
||||
this.startAct1();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
|
||||
'dataReady': function(val) {
|
||||
if (val && this.data._pendingAct3) {
|
||||
this.playAct3();
|
||||
}
|
||||
},
|
||||
|
||||
'boxType': function(val) {
|
||||
const config = this.getTypeConfig(val);
|
||||
this.setData({
|
||||
boxEmoji: config.boxEmoji,
|
||||
sparkEmoji: config.sparkEmoji,
|
||||
confettiColors: config.confettiColors
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getTypeConfig(type) {
|
||||
const map = {
|
||||
takeout: {
|
||||
boxEmoji: '🛵',
|
||||
sparkEmoji: '🛵',
|
||||
confettiColors: ['#E8693B', '#FF8A5C', '#FFD54F', '#FFAB40',
|
||||
'#FFCC80', '#E8693B', '#FF8A5C', '#FFD54F', '#FFAB40']
|
||||
},
|
||||
fridge: {
|
||||
boxEmoji: '🥬',
|
||||
sparkEmoji: '🥬',
|
||||
confettiColors: ['#4CAF50', '#66BB6A', '#A5D6A7', '#81C784',
|
||||
'#C8E6C9', '#4CAF50', '#66BB6A', '#A5D6A7', '#81C784']
|
||||
},
|
||||
explore: {
|
||||
boxEmoji: '🍜',
|
||||
sparkEmoji: '📍',
|
||||
confettiColors: ['#7C3AED', '#9C27B0', '#CE93D8', '#BA68C8',
|
||||
'#E1BEE7', '#7C3AED', '#9C27B0', '#CE93D8', '#BA68C8']
|
||||
}
|
||||
};
|
||||
return map[type] || map.takeout;
|
||||
},
|
||||
|
||||
/* ── Act 1: 光晕扩散 + 盒子震动 (0–400ms) ── */
|
||||
startAct1() {
|
||||
wx.vibrateShort({ type: 'light' });
|
||||
setTimeout(() => {
|
||||
this.setData({ act: 1 });
|
||||
}, 50);
|
||||
|
||||
// Act 2: 盒盖飞起 + 金光 + 白屏 (400ms)
|
||||
setTimeout(() => {
|
||||
wx.vibrateShort({ type: 'medium' });
|
||||
this.setData({ act: 2 });
|
||||
// 标记等待数据
|
||||
this.setData({ _pendingAct3: true });
|
||||
// 如果数据已就绪,立即进入 Act 3
|
||||
if (this.data.dataReady) {
|
||||
this.playAct3();
|
||||
}
|
||||
}, 400);
|
||||
},
|
||||
|
||||
/* ── Act 3: 内容弹出 + 纸屑(数据就绪后触发) ── */
|
||||
playAct3() {
|
||||
this.setData({ _pendingAct3: false });
|
||||
wx.vibrateLong();
|
||||
setTimeout(() => {
|
||||
this.setData({ act: 3 });
|
||||
}, 150);
|
||||
|
||||
// 通知父页面动画完成
|
||||
setTimeout(() => {
|
||||
this.triggerEvent('done');
|
||||
}, 2200);
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.setData({ act: 0, _pendingAct3: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
3
miniapp/components/box-animation/box-animation.json
Normal file
3
miniapp/components/box-animation/box-animation.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"component": true
|
||||
}
|
||||
52
miniapp/components/box-animation/box-animation.wxml
Normal file
52
miniapp/components/box-animation/box-animation.wxml
Normal file
@@ -0,0 +1,52 @@
|
||||
<view class="animation-overlay box-{{boxType}} {{show ? 'visible' : ''}}" wx:if="{{show}}">
|
||||
|
||||
<!-- ═══ Act 1: 光晕扩散 + 背景变暗 ═══ -->
|
||||
<view class="bg-dimmer {{act >= 1 ? 'active' : ''}}"></view>
|
||||
|
||||
<view class="act1-glow-ring {{act >= 1 ? 'active' : ''}}">
|
||||
<view class="glow-inner"></view>
|
||||
</view>
|
||||
|
||||
<!-- ═══ 盲盒主体 + 盒盖 ═══ -->
|
||||
<view class="box-stage {{act >= 1 ? 'active' : ''}}">
|
||||
|
||||
<!-- 盒盖(Act2 飞起) -->
|
||||
<view class="box-lid {{act >= 2 ? 'active' : ''}}">
|
||||
<view class="lid-top"></view>
|
||||
</view>
|
||||
|
||||
<!-- 金色光芒(Act2 从盒缝透出) -->
|
||||
<view class="golden-light {{act >= 2 ? 'active' : ''}}"></view>
|
||||
|
||||
<!-- 盒体 -->
|
||||
<view class="box-body {{act >= 2 ? 'compressed' : ''}}">
|
||||
<text class="box-emoji">{{boxEmoji}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 粒子(Act2 溅射) -->
|
||||
<view class="spark-particles {{act >= 2 ? 'active' : ''}}">
|
||||
<view class="spark s1">{{sparkEmoji}}</view>
|
||||
<view class="spark s2">{{sparkEmoji}}</view>
|
||||
<view class="spark s3">{{sparkEmoji}}</view>
|
||||
<view class="spark s4">{{sparkEmoji}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ═══ Act 2: 白色闪屏过渡 ═══ -->
|
||||
<view class="act2-mask {{act >= 2 ? 'active' : ''}}"></view>
|
||||
|
||||
<!-- ═══ Act 3: 内容承载层(弹性弹出) ═══ -->
|
||||
<view class="act3-content {{act >= 3 ? 'active' : ''}}">
|
||||
<view class="content-inner">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ═══ 彩带纸屑(Act3 收尾) ═══ -->
|
||||
<view class="confetti-stage {{act >= 3 ? 'active' : ''}}">
|
||||
<view class="confetti c1" wx:for="{{confettiColors}}" wx:key="*this"
|
||||
style="--c:{{item}}; --x:{{10 + index * 22}}%; --d:{{0.8 + index * 0.15}}s;">
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
304
miniapp/components/box-animation/box-animation.wxss
Normal file
304
miniapp/components/box-animation/box-animation.wxss
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 开盒动效 · 三幕分镜
|
||||
* 参考 doc/box.md
|
||||
*
|
||||
* Act 1 (0–400ms): 光晕扩散 + 盒子震动 + 背景变暗
|
||||
* Act 2 (400–1000ms): 盒盖飞起 + 金色光芒 + 粒子溅射 + 白色闪屏
|
||||
* Act 3 (1000–1800ms): 内容弹性弹出 + 彩带纸屑
|
||||
*
|
||||
* 总时长约 1.8–2.0s
|
||||
*/
|
||||
|
||||
/* ═══ 覆盖层 ═══ */
|
||||
.animation-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.animation-overlay.visible {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* ═══ 背景变暗 ═══ */
|
||||
.bg-dimmer {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease-out;
|
||||
}
|
||||
|
||||
.bg-dimmer.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ════════════════════════
|
||||
Act 1 · 光晕扩散 (0–400ms)
|
||||
════════════════════════ */
|
||||
|
||||
.act1-glow-ring {
|
||||
position: absolute;
|
||||
width: 320rpx;
|
||||
height: 320rpx;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transform: scale(0.15);
|
||||
}
|
||||
|
||||
.act1-glow-ring.active {
|
||||
animation: glowExpand 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
.glow-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 模式颜色 */
|
||||
.box-takeout .glow-inner {
|
||||
background: radial-gradient(circle, #E8693B 0%, rgba(232,105,59,0.4) 35%, transparent 70%);
|
||||
}
|
||||
|
||||
.box-fridge .glow-inner {
|
||||
background: radial-gradient(circle, #4CAF50 0%, rgba(76,175,80,0.4) 35%, transparent 70%);
|
||||
}
|
||||
|
||||
.box-explore .glow-inner {
|
||||
background: radial-gradient(circle, #7C3AED 0%, rgba(124,58,237,0.4) 35%, transparent 70%);
|
||||
}
|
||||
|
||||
@keyframes glowExpand {
|
||||
0% { opacity: 0.9; transform: scale(0.15); }
|
||||
50% { opacity: 0.6; transform: scale(1.5); }
|
||||
100% { opacity: 0; transform: scale(3.2); }
|
||||
}
|
||||
|
||||
/* ════════════════════════
|
||||
Act 1 · 盒子震动
|
||||
════════════════════════ */
|
||||
|
||||
.box-stage {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transform: scale(0.85);
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.box-stage.active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* ── 盒盖 ── */
|
||||
.box-lid {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
margin-bottom: -12rpx;
|
||||
transition: transform 0.35s ease-in, opacity 0.35s ease-in;
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
|
||||
.lid-top {
|
||||
width: 120rpx;
|
||||
height: 40rpx;
|
||||
background: linear-gradient(180deg, #F5A623 0%, #E8961A 100%);
|
||||
border-radius: 20rpx 20rpx 4rpx 4rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(200, 120, 20, 0.4);
|
||||
}
|
||||
|
||||
.box-lid.active {
|
||||
transform: translateY(-80rpx) rotate(-8deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── 金色光芒 ── */
|
||||
.golden-light {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transform: scale(0.3);
|
||||
}
|
||||
|
||||
.box-takeout .golden-light {
|
||||
background: radial-gradient(circle, #FFD54F 0%, rgba(255, 180, 50, 0.6) 30%, transparent 65%);
|
||||
}
|
||||
|
||||
.box-fridge .golden-light {
|
||||
background: radial-gradient(circle, #A5D6A7 0%, rgba(76, 175, 80, 0.5) 30%, transparent 65%);
|
||||
}
|
||||
|
||||
.box-explore .golden-light {
|
||||
background: radial-gradient(circle, #CE93D8 0%, rgba(124, 58, 237, 0.5) 30%, transparent 65%);
|
||||
}
|
||||
|
||||
.golden-light.active {
|
||||
animation: goldenBurst 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes goldenBurst {
|
||||
0% { opacity: 0; transform: scale(0.3); }
|
||||
30% { opacity: 1; transform: scale(1.8); }
|
||||
100% { opacity: 0; transform: scale(3.5); }
|
||||
}
|
||||
|
||||
/* ── 盒体(Act2 压缩反弹) ── */
|
||||
.box-body {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
transition: transform 0.25s ease-out;
|
||||
}
|
||||
|
||||
.box-body.compressed {
|
||||
animation: bodyCompress 0.3s ease-out;
|
||||
}
|
||||
|
||||
.box-emoji {
|
||||
font-size: 160rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes bodyCompress {
|
||||
0% { transform: scaleY(1); }
|
||||
40% { transform: scaleY(0.85); }
|
||||
100% { transform: scaleY(1); }
|
||||
}
|
||||
|
||||
/* ── 粒子溅射(Act2) ── */
|
||||
.spark-particles {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 50%; left: 50%;
|
||||
width: 0; height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.spark-particles.active .spark {
|
||||
animation: sparkBurst 0.7s ease-out forwards;
|
||||
}
|
||||
|
||||
.spark {
|
||||
position: absolute;
|
||||
font-size: 32rpx;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.spark.s1 { animation-delay: 0s !important; }
|
||||
.spark.s2 { animation-delay: 0.06s !important; }
|
||||
.spark.s3 { animation-delay: 0.12s !important; }
|
||||
.spark.s4 { animation-delay: 0.18s !important; }
|
||||
|
||||
@keyframes sparkBurst {
|
||||
0% { opacity: 1; transform: translate(0, 0) scale(0.5); }
|
||||
100% { opacity: 0; transform: translate(var(--sx, 60rpx), var(--sy, -80rpx)) scale(1.2); }
|
||||
}
|
||||
|
||||
.box-takeout .spark { --sx: 70rpx; --sy: -90rpx; }
|
||||
.box-takeout .spark.s2 { --sx: -60rpx; --sy: -70rpx; }
|
||||
.box-takeout .spark.s3 { --sx: 50rpx; --sy: -100rpx; }
|
||||
.box-takeout .spark.s4 { --sx: -80rpx; --sy: -80rpx; }
|
||||
|
||||
.box-fridge .spark { --sx: 70rpx; --sy: -90rpx; }
|
||||
.box-fridge .spark.s2 { --sx: -60rpx; --sy: -70rpx; }
|
||||
.box-fridge .spark.s3 { --sx: 50rpx; --sy: -100rpx; }
|
||||
.box-fridge .spark.s4 { --sx: -80rpx; --sy: -80rpx; }
|
||||
|
||||
.box-explore .spark { --sx: 70rpx; --sy: -90rpx; }
|
||||
.box-explore .spark.s2 { --sx: -60rpx; --sy: -70rpx; }
|
||||
.box-explore .spark.s3 { --sx: 50rpx; --sy: -100rpx; }
|
||||
.box-explore .spark.s4 { --sx: -80rpx; --sy: -80rpx; }
|
||||
|
||||
/* ════════════════════════
|
||||
Act 2 · 白色闪屏 (600–1000ms)
|
||||
════════════════════════ */
|
||||
|
||||
.act2-mask {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: #FFFFFF;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.act2-mask.active {
|
||||
animation: maskFlash 0.5s ease-in-out forwards;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes maskFlash {
|
||||
0% { opacity: 0; }
|
||||
45% { opacity: 0.92; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* ════════════════════════
|
||||
Act 3 · 内容弹性弹出 (1000–1800ms)
|
||||
════════════════════════ */
|
||||
|
||||
.act3-content {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
opacity: 0;
|
||||
transform: scale(0.85) translateY(30rpx);
|
||||
}
|
||||
|
||||
.act3-content.active {
|
||||
animation: contentReveal 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@keyframes contentReveal {
|
||||
0% { opacity: 0; transform: scale(0.85) translateY(30rpx); }
|
||||
100% { opacity: 1; transform: scale(1) translateY(0); }
|
||||
}
|
||||
|
||||
/* ════════════════════════
|
||||
Act 3 · 彩带纸屑 (1600ms+)
|
||||
════════════════════════ */
|
||||
|
||||
.confetti-stage {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.confetti-stage.active .confetti {
|
||||
animation: confettiDrop 1.4s ease-in forwards;
|
||||
}
|
||||
|
||||
.confetti {
|
||||
position: absolute;
|
||||
top: -20rpx;
|
||||
left: var(--x, 20%);
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 3rpx;
|
||||
background: var(--c, #E8693B);
|
||||
opacity: 0;
|
||||
animation-delay: var(--d, 0s);
|
||||
}
|
||||
|
||||
@keyframes confettiDrop {
|
||||
0% { opacity: 1; transform: translateY(0) rotate(0deg) scale(1); }
|
||||
30% { opacity: 0.9; transform: translateY(280rpx) rotate(180deg) scale(0.7); }
|
||||
60% { opacity: 0.5; transform: translateY(600rpx) rotate(400deg) scale(0.4); }
|
||||
100% { opacity: 0; transform: translateY(1000rpx) rotate(720deg) scale(0.1); }
|
||||
}
|
||||
Reference in New Issue
Block a user