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:
王鹏
2026-05-08 20:02:27 +08:00
commit 802b4ba229
98 changed files with 5761 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
const api = require('../../utils/api');
Page({
data: {
showAnimation: false,
dataReady: false,
loading: false,
results: [],
ingredients: []
},
onLoad(options) {
if (options.payload) {
try {
const payload = JSON.parse(decodeURIComponent(options.payload));
const ingredients = payload.ingredients || [];
const staples = payload.staples || [];
this.setData({ ingredients });
this.match(ingredients, staples);
} catch (e) {
wx.showToast({ title: '参数错误', icon: 'none' });
}
}
},
match(ingredients, staples) {
this.setData({
showAnimation: true,
dataReady: false,
results: []
});
api.post('/api/fridge/match', { ingredients: ingredients, staples: staples })
.then((data) => {
this.setData({ results: data, dataReady: true });
if (data.length > 0) {
const history = wx.getStorageSync('box_history') || [];
history.push({
id: Date.now().toString(),
icon: '🥬',
name: data[0].recipe.name + ' (' + data[0].matchRate + '%匹配)',
time: new Date().toLocaleString(),
typeName: '冰箱盲盒'
});
wx.setStorageSync('box_history', history);
}
})
.catch(() => {
this.setData({
showAnimation: false,
results: [],
loading: false
});
wx.showToast({ title: '匹配失败,请重试', icon: 'none' });
});
},
onAnimationDone() {
this.setData({ showAnimation: false });
},
goDetail(e) {
const id = e.currentTarget.dataset.id;
let missing = e.currentTarget.dataset.missing;
// dataset 值可能是字符串,需要解析
if (typeof missing === 'string') {
try { missing = JSON.parse(missing); } catch (e) { missing = []; }
}
if (!Array.isArray(missing)) missing = [];
let url = '/pages/recipe-detail/recipe-detail?id=' + id;
if (missing.length > 0) {
url += '&missing=' + encodeURIComponent(JSON.stringify(missing));
}
wx.navigateTo({ url: url });
},
reshuffle() {
this.match(this.data.ingredients);
}
});

View File

@@ -0,0 +1,6 @@
{
"usingComponents": {
"box-animation": "/components/box-animation/box-animation"
},
"navigationBarTitleText": "菜谱推荐"
}

View File

@@ -0,0 +1,42 @@
<view class="recipe-list-page">
<box-animation
show="{{showAnimation}}"
boxType="fridge"
dataReady="{{dataReady}}"
bind:done="onAnimationDone"
>
<view class="animation-result-preview" wx:if="{{results.length > 0}}">
<view class="preview-emoji">🥬</view>
<view class="preview-text">{{results[0].recipe.name}}</view>
<view class="preview-sub">匹配度 {{results[0].matchRate}}%</view>
</view>
</box-animation>
<view class="list-header" wx:if="{{!showAnimation && results.length}}">
为你找到 {{results.length}} 个菜谱
</view>
<view class="recipe-cards" wx:if="{{!showAnimation}}">
<view class="recipe-card card" wx:for="{{results}}" wx:key="recipe.id" bind:tap="goDetail" data-id="{{item.recipe.id}}" data-missing="{{item.missingIngredients}}">
<view class="card-left">
<view class="recipe-name">{{item.recipe.name}}</view>
<view class="recipe-meta">
<text wx:if="{{item.recipe.difficulty}}">⭐{{item.recipe.difficulty}}</text>
<text wx:if="{{item.recipe.cookTime}}"> ⏱️{{item.recipe.cookTime}}分钟</text>
</view>
<view class="match-rate">
<view class="rate-bar">
<view class="rate-fill" style="width:{{item.matchRate}}%"></view>
</view>
<text>{{item.matchRate}}%</text>
</view>
<view class="missing" wx:if="{{item.missingIngredients.length}}">
缺:<text wx:for="{{item.missingIngredients}}" wx:key="*this">{{item}} </text>
</view>
</view>
</view>
</view>
<view class="center" wx:if="{{!showAnimation && results.length === 0}}">没有匹配的菜谱,试试换个食材组合</view>
<view class="btn-retry" wx:if="{{!showAnimation && results.length}}" bind:tap="reshuffle">换一批菜谱</view>
</view>

View File

@@ -0,0 +1,133 @@
/*
* 菜谱推荐列表
*/
.recipe-list-page {
padding: var(--space-md) var(--space-lg);
min-height: 100vh;
}
/* ── 页头 ── */
.list-header {
font-size: var(--text-body);
font-weight: 600;
color: var(--color-text);
margin-bottom: var(--space-md);
}
/* ── 卡片 ── */
.recipe-cards {
display: flex;
flex-direction: column;
gap: var(--space-sm);
margin-bottom: var(--space-md);
}
.recipe-card {
padding: var(--space-md);
background: var(--color-surface);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
transition: transform 0.15s ease;
}
.recipe-card:active {
transform: scale(0.985);
}
/* ── 内容 ── */
.recipe-name {
font-size: var(--text-subtitle);
font-weight: 700;
color: var(--color-text);
margin-bottom: 4rpx;
}
.recipe-meta {
font-size: var(--text-body-sm);
color: var(--color-text-muted);
margin-bottom: var(--space-sm);
}
/* ── 匹配度条 ── */
.match-rate {
display: flex;
align-items: center;
margin-bottom: 8rpx;
font-size: var(--text-body-sm);
font-weight: 600;
color: var(--color-text);
}
.rate-bar {
flex: 1;
height: 6rpx;
background: #F0EDE8;
border-radius: 3rpx;
overflow: hidden;
margin-right: 12rpx;
}
.rate-fill {
height: 100%;
background: var(--color-green);
border-radius: 3rpx;
transition: width 0.5s ease;
}
/* ── 缺少食材 ── */
.missing {
font-size: var(--text-caption);
color: #E8A040;
margin-top: 8rpx;
}
/* ── 空态 ── */
.center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
font-size: var(--text-body);
color: var(--color-text-muted);
}
/* ── 换一批 ── */
.btn-retry {
text-align: center;
font-size: var(--text-body);
font-weight: 500;
color: var(--color-primary);
padding: var(--space-md);
}
.btn-retry:active {
opacity: 0.6;
}
/* ── 动效内预览 ── */
.animation-result-preview {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx;
}
.preview-emoji {
font-size: 64rpx;
margin-bottom: 12rpx;
}
.preview-text {
font-size: var(--text-subtitle);
font-weight: 700;
color: #FFFFFF;
text-align: center;
}
.preview-sub {
font-size: var(--text-body-sm);
color: rgba(255, 255, 255, 0.8);
margin-top: 8rpx;
}