临时保存2

This commit is contained in:
rjb
2025-09-09 08:00:07 +08:00
parent 0c7420d17c
commit 6f82485ccc
21 changed files with 2012 additions and 1 deletions

View File

@@ -0,0 +1,260 @@
// 智能饭菜规划页面逻辑
Page({
data: {
// 表单数据
regionIndex: 0,
regionOptions: ['全国', '北方', '南方', '川菜', '粤菜', '鲁菜', '苏菜', '浙菜', '闽菜', '湘菜', '徽菜'],
dinerCountIndex: 1,
dinerCountOptions: ['1人', '2人', '3人', '4人', '5人', '6人', '8人', '10人'],
mealTypeIndex: 1,
mealTypeOptions: ['早餐', '午餐', '晚餐', '全天'],
hometown: '',
preferences: '',
dietaryRestrictions: '',
budgetIndex: 1,
budgetOptions: ['50元以下', '50-100元', '100-150元', '150-200元', '200-300元', '300-500元', '500元以上'],
// 状态数据
isGenerating: false,
mealPlanResult: '',
// API配置
apiBaseUrl: 'https://your-domain.com/api'
},
onLoad() {
console.log('饭菜规划页面加载');
},
// 地区类型选择
onRegionChange(e) {
this.setData({
regionIndex: e.detail.value
});
},
// 就餐人数选择
onDinerCountChange(e) {
this.setData({
dinerCountIndex: e.detail.value
});
},
// 用餐类型选择
onMealTypeChange(e) {
this.setData({
mealTypeIndex: e.detail.value
});
},
// 家乡输入
onHometownInput(e) {
this.setData({
hometown: e.detail.value
});
},
// 个人喜好输入
onPreferencesInput(e) {
this.setData({
preferences: e.detail.value
});
},
// 饮食禁忌输入
onDietaryRestrictionsInput(e) {
this.setData({
dietaryRestrictions: e.detail.value
});
},
// 预算选择
onBudgetChange(e) {
this.setData({
budgetIndex: e.detail.value
});
},
// 生成饭菜规划
async generateMealPlan() {
// 验证必填字段
if (!this.data.hometown.trim()) {
wx.showToast({
title: '请输入用餐者家乡',
icon: 'none'
});
return;
}
// 设置生成状态
this.setData({
isGenerating: true
});
try {
// 构建请求数据
const requestData = {
region_type: this.data.regionOptions[this.data.regionIndex],
diner_count: this.data.dinerCountOptions[this.data.dinerCountIndex],
meal_type: this.data.mealTypeOptions[this.data.mealTypeIndex],
hometown: this.data.hometown,
preferences: this.data.preferences,
dietary_restrictions: this.data.dietaryRestrictions,
budget: this.data.budgetOptions[this.data.budgetIndex]
};
// 发送请求
const response = await wx.request({
url: `${this.data.apiBaseUrl}/meal-planning/generate`,
method: 'POST',
data: requestData,
header: {
'Content-Type': 'application/json'
}
});
if (response.data.success) {
// 处理Markdown格式的结果
const formattedResult = this.formatMarkdownToHtml(response.data.data.meal_plan);
this.setData({
mealPlanResult: formattedResult
});
wx.showToast({
title: '生成成功!',
icon: 'success'
});
// 滚动到结果区域
wx.pageScrollTo({
selector: '.result-container',
duration: 500
});
} else {
throw new Error(response.data.message || '生成失败');
}
} catch (error) {
console.error('生成饭菜规划失败:', error);
wx.showToast({
title: error.message || '生成失败,请重试',
icon: 'none'
});
} finally {
this.setData({
isGenerating: false
});
}
},
// 格式化Markdown为HTML
formatMarkdownToHtml(text) {
return text
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
.replace(/\*(.*)\*/gim, '<em>$1</em>')
.replace(/^\* (.*$)/gim, '<li>$1</li>')
.replace(/^\d+\. (.*$)/gim, '<li>$1</li>')
.replace(/\n\n/gim, '</p><p>')
.replace(/\n/gim, '<br>')
.replace(/^(.*)$/gim, '<p>$1</p>');
},
// 复制结果
copyResult() {
if (!this.data.mealPlanResult) {
wx.showToast({
title: '没有可复制的内容',
icon: 'none'
});
return;
}
// 提取纯文本内容
const textContent = this.data.mealPlanResult.replace(/<[^>]*>/g, '');
wx.setClipboardData({
data: textContent,
success: () => {
wx.showToast({
title: '已复制到剪贴板',
icon: 'success'
});
},
fail: () => {
wx.showToast({
title: '复制失败',
icon: 'none'
});
}
});
},
// 保存结果
async saveResult() {
if (!this.data.mealPlanResult) {
wx.showToast({
title: '没有可保存的内容',
icon: 'none'
});
return;
}
try {
// 构建保存数据
const saveData = {
meal_plan_content: this.data.mealPlanResult.replace(/<[^>]*>/g, ''),
region_type: this.data.regionOptions[this.data.regionIndex],
diner_count: this.data.dinerCountOptions[this.data.dinerCountIndex],
meal_type: this.data.mealTypeOptions[this.data.mealTypeIndex],
hometown: this.data.hometown,
preferences: this.data.preferences,
dietary_restrictions: this.data.dietaryRestrictions,
budget: this.data.budgetOptions[this.data.budgetIndex]
};
// 发送保存请求
const response = await wx.request({
url: `${this.data.apiBaseUrl}/meal-planning/save`,
method: 'POST',
data: saveData,
header: {
'Content-Type': 'application/json'
}
});
if (response.data.success) {
wx.showToast({
title: '保存成功!',
icon: 'success'
});
} else {
throw new Error(response.data.message || '保存失败');
}
} catch (error) {
console.error('保存饭菜规划失败:', error);
wx.showToast({
title: error.message || '保存失败,请重试',
icon: 'none'
});
}
},
// 页面分享
onShareAppMessage() {
return {
title: '智能饭菜规划 - AI驱动的个性化饭菜清单规划师',
path: '/pages/meal-planning/meal-planning',
imageUrl: '/images/meal-planning-share.jpg'
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '智能饭菜规划 - AI驱动的个性化饭菜清单规划师',
imageUrl: '/images/meal-planning-share.jpg'
};
}
});

View File

@@ -0,0 +1,104 @@
<!-- 智能饭菜规划页面 -->
<view class="container">
<!-- 页面标题 -->
<view class="page-header">
<view class="page-title">
<text class="title-icon">🍽️</text>
<text class="title-text">智能饭菜规划</text>
</view>
<view class="page-subtitle">AI驱动的个性化饭菜清单规划师</view>
</view>
<!-- 表单区域 -->
<view class="form-container">
<view class="form-section">
<view class="section-title">规划参数</view>
<!-- 地区类型 -->
<view class="form-item">
<view class="form-label">地区类型</view>
<picker bindchange="onRegionChange" value="{{regionIndex}}" range="{{regionOptions}}">
<view class="picker-display">{{regionOptions[regionIndex]}}</view>
</picker>
</view>
<!-- 就餐人数 -->
<view class="form-item">
<view class="form-label">就餐人数</view>
<picker bindchange="onDinerCountChange" value="{{dinerCountIndex}}" range="{{dinerCountOptions}}">
<view class="picker-display">{{dinerCountOptions[dinerCountIndex]}}</view>
</picker>
</view>
<!-- 用餐类型 -->
<view class="form-item">
<view class="form-label">用餐类型</view>
<picker bindchange="onMealTypeChange" value="{{mealTypeIndex}}" range="{{mealTypeOptions}}">
<view class="picker-display">{{mealTypeOptions[mealTypeIndex]}}</view>
</picker>
</view>
<!-- 用餐者家乡 -->
<view class="form-item">
<view class="form-label">用餐者家乡 <text class="required">*</text></view>
<input class="form-input" placeholder="如:四川成都" value="{{hometown}}" bindinput="onHometownInput" />
</view>
<!-- 个人喜好 -->
<view class="form-item">
<view class="form-label">个人喜好</view>
<textarea class="form-textarea" placeholder="如:喜欢辣味、偏爱素食、喜欢海鲜等" value="{{preferences}}" bindinput="onPreferencesInput"></textarea>
</view>
<!-- 饮食禁忌 -->
<view class="form-item">
<view class="form-label">饮食禁忌</view>
<textarea class="form-textarea" placeholder="如:不吃猪肉、对花生过敏、素食主义等" value="{{dietaryRestrictions}}" bindinput="onDietaryRestrictionsInput"></textarea>
</view>
<!-- 预算范围 -->
<view class="form-item">
<view class="form-label">预算范围(元)</view>
<picker bindchange="onBudgetChange" value="{{budgetIndex}}" range="{{budgetOptions}}">
<view class="picker-display">{{budgetOptions[budgetIndex]}}</view>
</picker>
</view>
<!-- 生成按钮 -->
<button class="generate-btn" bindtap="generateMealPlan" disabled="{{isGenerating}}">
<text wx:if="{{!isGenerating}}">🎯 生成饭菜规划</text>
<text wx:else>⏳ 生成中...</text>
</button>
</view>
</view>
<!-- 结果展示区域 -->
<view class="result-container" wx:if="{{mealPlanResult}}">
<view class="result-header">
<view class="result-title">🍽️ 饭菜规划结果</view>
<view class="result-actions">
<button class="action-btn copy-btn" bindtap="copyResult">📋 复制</button>
<button class="action-btn save-btn" bindtap="saveResult">💾 保存</button>
</view>
</view>
<view class="result-content">
<rich-text nodes="{{mealPlanResult}}"></rich-text>
</view>
</view>
<!-- 历史记录入口 -->
<view class="history-entry">
<navigator url="/pages/meal-history/meal-history" class="history-link">
<text class="history-icon">📚</text>
<text class="history-text">查看历史规划</text>
</navigator>
</view>
</view>
<!-- 加载提示 -->
<view class="loading-overlay" wx:if="{{isGenerating}}">
<view class="loading-content">
<view class="loading-spinner"></view>
<view class="loading-text">AI正在为您制定个性化的饭菜清单请稍候...</view>
</view>
</view>

View File

@@ -0,0 +1,341 @@
/* 智能饭菜规划页面样式 */
.container {
padding: 20rpx;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
}
/* 页面标题 */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40rpx 30rpx;
margin: -20rpx -20rpx 30rpx -20rpx;
border-radius: 0 0 40rpx 40rpx;
text-align: center;
}
.page-title {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.title-icon {
font-size: 60rpx;
margin-right: 20rpx;
}
.title-text {
font-size: 48rpx;
font-weight: bold;
}
.page-subtitle {
font-size: 28rpx;
opacity: 0.9;
}
/* 表单容器 */
.form-container {
background: white;
border-radius: 30rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
.form-section {
width: 100%;
}
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 40rpx;
text-align: center;
}
/* 表单项 */
.form-item {
margin-bottom: 40rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
font-weight: 500;
}
.required {
color: #ff4757;
margin-left: 10rpx;
}
/* 选择器样式 */
.picker-display {
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 20rpx;
padding: 24rpx 30rpx;
font-size: 28rpx;
color: #333;
position: relative;
}
.picker-display::after {
content: '▼';
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
color: #667eea;
font-size: 24rpx;
}
/* 输入框样式 */
.form-input {
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 20rpx;
padding: 24rpx 30rpx;
font-size: 28rpx;
color: #333;
width: 100%;
box-sizing: border-box;
}
.form-input:focus {
border-color: #667eea;
background: white;
}
/* 文本域样式 */
.form-textarea {
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 20rpx;
padding: 24rpx 30rpx;
font-size: 28rpx;
color: #333;
width: 100%;
min-height: 120rpx;
box-sizing: border-box;
}
.form-textarea:focus {
border-color: #667eea;
background: white;
}
/* 生成按钮 */
.generate-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 25rpx;
padding: 30rpx 60rpx;
font-size: 32rpx;
font-weight: bold;
width: 100%;
margin-top: 40rpx;
box-shadow: 0 8rpx 30rpx rgba(102, 126, 234, 0.3);
}
.generate-btn:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 15rpx rgba(102, 126, 234, 0.3);
}
.generate-btn[disabled] {
opacity: 0.6;
transform: none;
}
/* 结果容器 */
.result-container {
background: white;
border-radius: 30rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
flex-wrap: wrap;
}
.result-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
flex: 1;
}
.result-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 20rpx;
padding: 16rpx 24rpx;
font-size: 24rpx;
color: #333;
min-width: 120rpx;
}
.copy-btn:active {
background: #e3f2fd;
border-color: #2196f3;
color: #2196f3;
}
.save-btn:active {
background: #e8f5e8;
border-color: #4caf50;
color: #4caf50;
}
/* 结果内容 */
.result-content {
background: #f8f9fa;
border-radius: 20rpx;
padding: 30rpx;
border-left: 8rpx solid #667eea;
font-size: 28rpx;
line-height: 1.6;
color: #333;
}
.result-content h1,
.result-content h2,
.result-content h3 {
color: #333;
margin: 30rpx 0 20rpx 0;
font-weight: bold;
}
.result-content h1:first-child,
.result-content h2:first-child,
.result-content h3:first-child {
margin-top: 0;
}
.result-content strong {
color: #667eea;
font-weight: bold;
}
.result-content ul,
.result-content ol {
padding-left: 40rpx;
}
.result-content li {
margin-bottom: 10rpx;
}
/* 历史记录入口 */
.history-entry {
background: white;
border-radius: 30rpx;
padding: 30rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
.history-link {
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: #667eea;
}
.history-icon {
font-size: 40rpx;
margin-right: 20rpx;
}
.history-text {
font-size: 32rpx;
font-weight: 500;
}
/* 加载遮罩 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background: white;
border-radius: 30rpx;
padding: 60rpx 40rpx;
text-align: center;
max-width: 500rpx;
margin: 0 40rpx;
}
.loading-spinner {
width: 80rpx;
height: 80rpx;
border: 6rpx solid #f3f3f3;
border-top: 6rpx solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 30rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
/* 响应式适配 */
@media (max-width: 750rpx) {
.container {
padding: 15rpx;
}
.form-container,
.result-container,
.history-entry {
padding: 30rpx;
}
.page-header {
padding: 30rpx 20rpx;
}
.title-text {
font-size: 40rpx;
}
.page-subtitle {
font-size: 24rpx;
}
}