From cfc1a130424bf18f1b6f77f9ac8fe742ad76f975 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?=
<15778543+xufeiyang6017@user.noreply.gitee.com>
Date: Fri, 2 Jan 2026 09:12:17 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BB=B7=E6=A0=BC=E8=AF=84?=
=?UTF-8?q?=E4=BC=B0=E5=8A=9F=E8=83=BD=EF=BC=9A=E6=B7=BB=E5=8A=A0=E6=95=B0?=
=?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=A8=A1=E5=9E=8B=E3=80=81API=E7=AB=AF?=
=?UTF-8?q?=E7=82=B9=E5=92=8C=E5=89=8D=E7=AB=AF=E9=9B=86=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在server-mysql.js中添加Resources模型定义,映射resources表结构
- 创建/api/evaluate/price端点,支持基于历史数据的鸡蛋价格评估
- 实现价格趋势分析算法,预测价格波动在0.2范围内
- 更新evaluate/index.js中的地区和品种选项以匹配数据库数据
- 在utils/api.js中添加evaluatePrice方法用于调用新的API端点
- 添加详细的错误处理和日志记录用于调试
---
custom-tab-bar/index.js | 30 +-
custom-tab-bar/index.wxml | 10 +
pages/evaluate/index.js | 719 ++++++++++++---------------------
pages/evaluate/index.wxml | 432 ++++++--------------
pages/evaluate/index.wxss | 355 ++++++++++++++++
server-example/server-mysql.js | 290 +++++++++++++
utils/api.js | 29 ++
7 files changed, 1081 insertions(+), 784 deletions(-)
diff --git a/custom-tab-bar/index.js b/custom-tab-bar/index.js
index 7eb2926..150170a 100644
--- a/custom-tab-bar/index.js
+++ b/custom-tab-bar/index.js
@@ -15,7 +15,9 @@ Component({
tabBarItems: [
{ key: 'index', route: 'pages/index/index' },
{ key: 'chat', route: 'pages/chat/index', badgeKey: 'chat' },
+ { key: 'evaluate', route: 'pages/evaluate/index' },
{ key: 'settlement', route: 'pages/settlement/index' },
+ { key: 'favorites', route: 'pages/favorites/index' },
{ key: 'profile', route: 'pages/profile/index' }
]
},
@@ -91,9 +93,10 @@ Component({
// 定义tabBar页面列表
const tabBarPages = [
'pages/index/index',
- 'pages/chat/index',
- 'pages/seller/index',
+ 'pages/chat/index',
+ 'pages/evaluate/index',
'pages/settlement/index',
+ 'pages/favorites/index',
'pages/profile/index'
];
@@ -173,6 +176,29 @@ Component({
})
},
+ // 跳转到估价页面
+ goToEvaluatePage() {
+ wx.switchTab({
+ url: '/pages/evaluate/index',
+ success: () => {
+ console.log('跳转到估价页面成功')
+ },
+ fail: (err) => {
+ console.error('跳转到估价页面失败:', err)
+ // 如果switchTab失败,尝试使用navigateTo
+ wx.navigateTo({
+ url: '/pages/evaluate/index',
+ success: () => {
+ console.log('使用navigateTo跳转到估价页面成功')
+ },
+ fail: (err2) => {
+ console.error('navigateTo也失败:', err2)
+ }
+ })
+ }
+ })
+ },
+
// 从全局数据同步状态的方法 - 增强版
syncFromGlobalData() {
try {
diff --git a/custom-tab-bar/index.wxml b/custom-tab-bar/index.wxml
index 4428e37..824be79 100644
--- a/custom-tab-bar/index.wxml
+++ b/custom-tab-bar/index.wxml
@@ -42,6 +42,16 @@
入驻
+
+
+ {{badges['favorites']}}
+
+ 收藏
+
+
b.dailyOrders - a.dailyOrders);
- // 添加排名属性
- const breedsWithRank = sortedBreeds.map((breed, index) => ({
- ...breed,
- rank: index + 1
- }));
-
- // 对鸡蛋规格按成交单量降序排序并添加排名
- const sortedSpecs = [...this.data.eggSpecs].sort((a, b) => b.dailyOrders - a.dailyOrders);
- const specsWithRank = sortedSpecs.map((spec, index) => ({
- ...spec,
- rank: index + 1
- }));
+ console.log('估价页面初始化 - 简化版')
- // 初始化省份列表
- const provinces = Object.keys(this.data.provinceCities);
- this.setData({
- eggBreeds: breedsWithRank,
- eggSpecs: specsWithRank,
- fromPreviousStep: false, // 初始化标志
- provinces: provinces
- });
+ // 初始化为当前北京时间
+ this.initCurrentDate()
},
- // 选择省份
- selectProvince(e) {
- const province = e.currentTarget.dataset.province;
- const cities = this.data.provinceCities[province];
- this.setData({
- selectedProvince: province,
- cities: cities,
- showCities: true
- });
- },
-
- // 选择城市
- selectCity(e) {
- const city = e.currentTarget.dataset.city;
- if (!city || !this.data.selectedProvince) {
- wx.showToast({
- title: '请选择有效的城市',
- icon: 'none',
- duration: 2000
- });
- return;
- }
+ // 初始化当前日期
+ initCurrentDate() {
+ const now = new Date()
+ const currentMonth = (now.getMonth() + 1).toString()
+ const currentDate = now.getDate().toString()
- const fullRegion = `${this.data.selectedProvince}-${city}`;
+ // 设置默认月份
+ const monthIndex = this.data.months.indexOf(currentMonth)
this.setData({
- 'evaluateData.region': fullRegion,
- showCities: false
- });
+ selectedMonth: currentMonth,
+ selectedMonthIndex: monthIndex >= 0 ? monthIndex : 0
+ })
- // 显示选择成功提示
- wx.showToast({
- title: `已选择: ${fullRegion}`,
- icon: 'success',
- duration: 1500
- });
-
- // 只有当当前步骤是1且已经从下一步返回时,才自动进入下一步
- if (this.data.evaluateStep === 1 && !this.data.fromPreviousStep) {
- this.setData({
- evaluateStep: 2
- });
- }
+ // 根据当前月份更新日期数组
+ this.updateDaysForMonth(currentMonth)
- // 重置标志
+ // 设置默认日期
+ const dayIndex = this.data.days.indexOf(currentDate)
this.setData({
- fromPreviousStep: false
- });
+ selectedDay: dayIndex >= 0 ? currentDate : '',
+ selectedDayIndex: dayIndex >= 0 ? dayIndex : 0
+ })
},
-
- // 返回选择省份
- backToProvince() {
+
+ // 第一步:点击我知道了
+ onIKnow() {
this.setData({
- showCities: false
- });
+ currentStep: 2
+ })
},
- // 选择鸡蛋品种
- selectEggBreed(e) {
- const breed = e.currentTarget.dataset.breed;
- this.setData({
- 'evaluateData.breed': breed
- });
-
- // 显示选择成功提示
- wx.showToast({
- title: '已选择: ' + breed,
- icon: 'none',
- duration: 1500
- });
+ // 月份选择器变化
+ onMonthChange(e) {
+ const monthIndex = e.detail.value
+ const selectedMonth = this.data.months[monthIndex]
- // 只有当当前步骤是2且不是从下一步返回时,才自动进入下一步
- if (this.data.evaluateStep === 2 && !this.data.fromPreviousStep) {
- this.setData({ evaluateStep: 3 });
- }
+ // 更新月份
+ this.setData({
+ selectedMonth: selectedMonth,
+ selectedMonthIndex: monthIndex,
+ selectedDay: '', // 清空日期选择
+ selectedDayIndex: 0
+ })
- // 重置标志
- this.setData({ fromPreviousStep: false });
+ // 根据月份设置对应的日期数组
+ this.updateDaysForMonth(selectedMonth)
},
- // 选择鸡蛋规格
- selectEggSpec(e) {
- const spec = e.currentTarget.dataset.spec;
+ // 日期选择器变化
+ onDayChange(e) {
+ const dayIndex = e.detail.value
+ const selectedDay = this.data.days[dayIndex]
this.setData({
- 'evaluateData.spec': spec
- });
-
- // 显示选择成功提示
- wx.showToast({
- title: '已选择: ' + spec,
- icon: 'none',
- duration: 1500
- });
+ selectedDay: selectedDay,
+ selectedDayIndex: dayIndex
+ })
+ },
+
+ // 根据月份更新日期数组
+ updateDaysForMonth(month) {
+ let daysCount
- // 只有当当前步骤是3且不是从下一步返回时,才自动进入下一步
- if (this.data.evaluateStep === 3 && !this.data.fromPreviousStep) {
- this.setData({ evaluateStep: 4 });
+ // 根据月份设置日期数量
+ switch(month) {
+ case '2': // 2月
+ daysCount = 28 // 简化处理,不考虑闰年
+ break
+ case '4':
+ case '6':
+ case '9':
+ case '11': // 小月
+ daysCount = 30
+ break
+ default: // 大月
+ daysCount = 31
}
- // 重置标志
- this.setData({ fromPreviousStep: false });
- },
-
- // 格式化订单数量显示
- formatOrderCount(count) {
- if (count >= 10000) {
- return (count / 10000).toFixed(1) + '万';
- } else if (count >= 1000) {
- return (count / 1000).toFixed(1) + 'k';
+ const days = []
+ for (let i = 1; i <= daysCount; i++) {
+ days.push(i.toString())
}
- return count.toString();
+
+ this.setData({
+ days: days,
+ selectedDay: '',
+ selectedDayIndex: 0
+ })
},
- // 选择条件
- selectCondition(e) {
- const { type, value } = e.currentTarget.dataset;
+ // 地区选择器变化
+ onRegionChange(e) {
+ const index = e.detail.value
+ const selectedRegion = this.data.regionOptions[index]
this.setData({
- [`evaluateData.${type}`]: value
- });
-
- // 只有当当前步骤不是从返回过来的,才自动进入下一步
- if (!this.data.fromPreviousStep) {
- // 根据当前步骤自动进入下一步
- const currentStep = this.data.evaluateStep;
- if (currentStep === 4) {
- this.setData({ evaluateStep: 5 });
- } else if (currentStep === 5) {
- this.setData({ evaluateStep: 6 });
- } else if (currentStep === 6) {
- this.setData({ evaluateStep: 7 });
- }
- }
-
- // 重置标志
- this.setData({ fromPreviousStep: false });
+ selectedRegion: selectedRegion,
+ selectedRegionIndex: index
+ })
},
-
- // 选择数量
- selectQuantity(e) {
- const quantity = e.currentTarget.dataset.quantity;
+
+ // 品种选择器变化
+ onBreedChange(e) {
+ const index = e.detail.value
+ const selectedBreed = this.data.breedOptions[index]
this.setData({
- 'evaluateData.quantity': quantity
- });
+ selectedBreed: selectedBreed,
+ selectedBreedIndex: index
+ })
},
-
- // 获取报价
- getQuote() {
- if (this.data.evaluateData.quantity) {
- this.calculatePrice();
- } else {
+
+ // 开始估价
+ startEvaluation() {
+ const { selectedMonth, selectedDay, selectedRegion, selectedBreed } = this.data
+
+ if (!selectedMonth || !selectedDay || !selectedRegion || !selectedBreed) {
wx.showToast({
- title: '请选择数量',
+ title: '请完成所有选择',
icon: 'none',
duration: 2000
- });
+ })
+ return
}
+
+ // 显示加载中
+ wx.showLoading({
+ title: '估价中...',
+ mask: true
+ })
+
+ // 调用真实的价格评估API
+ api.evaluatePrice(selectedMonth, selectedDay, selectedRegion, selectedBreed)
+ .then(result => {
+ console.log('价格评估结果:', result);
+
+ // 更新评估结果
+ this.setData({
+ evaluateResult: {
+ estimatedPrice: result.estimatedPrice || '0.00',
+ priceRange: result.priceRange || '0.00 - 0.00',
+ confidence: result.confidence || '85%'
+ },
+ currentStep: 3
+ });
+
+ wx.hideLoading()
+
+ wx.showToast({
+ title: '估价完成',
+ icon: 'success',
+ duration: 1500
+ })
+ })
+ .catch(error => {
+ console.error('价格评估失败:', error);
+
+ wx.hideLoading()
+
+ wx.showToast({
+ title: '估价失败: ' + (error.message || '未知错误'),
+ icon: 'none',
+ duration: 3000
+ })
+
+ // 可以选择保持当前步骤或者回到上一步
+ // this.goBack();
+ })
},
- // 上一步
- prevStep() {
- if (this.data.evaluateStep > 1 && this.data.evaluateStep < 9) {
- this.setData({
- evaluateStep: this.data.evaluateStep - 1,
- fromPreviousStep: true // 标记是从下一步返回的
- });
- } else if (this.data.evaluateStep === 9) {
- // 如果在结果页面,返回第七步(数量选择)
- this.setData({
- evaluateStep: 7,
- fromPreviousStep: true
- });
- } else {
- // 如果在第一步,返回上一页
- wx.navigateBack();
+ // 获取时间系数
+ getTimeMultiplier(timeValue) {
+ const multipliers = {
+ 'today': 1.0,
+ 'tomorrow': 0.98,
+ 'week': 0.95,
+ 'month': 0.90
}
+ return multipliers[timeValue] || 1.0
},
- // 计算价格
- calculatePrice() {
- console.log('开始执行calculatePrice函数');
- const { region, breed, spec, freshness, size, packaging, quantity } = this.data.evaluateData;
+ // 获取地区系数
+ getRegionMultiplier(region) {
+ const tier1Cities = ['北京', '上海', '广州', '深圳']
+ const tier2Cities = ['天津', '江苏', '浙江', '山东']
- // 校验必填参数
- console.log('校验必填参数:', { region, breed, spec, freshness, size, packaging, quantity });
- if (!region || !breed || !spec || !freshness || !size || !packaging || !quantity) {
- console.error('参数校验失败,缺少必要信息');
- wx.showToast({
- title: '请完成所有选项',
- icon: 'none',
- duration: 2000
- });
- return;
- }
+ if (tier1Cities.includes(region)) return 1.15
+ if (tier2Cities.includes(region)) return 1.05
+ return 1.0
+ },
+
+ // 获取品种系数
+ getBreedMultiplier(breed) {
+ const premiumBreeds = ['绿壳', '双黄蛋', '黑鸡土蛋']
+ const popularBreeds = ['罗曼粉', '伊莎粉', '海蓝褐']
- // 显示加载中
- wx.showLoading({
- title: '计算中...',
- mask: true
- });
+ if (premiumBreeds.includes(breed)) return 1.20
+ if (popularBreeds.includes(breed)) return 1.10
+ return 1.0
+ },
+
+ // 计算价格区间
+ calculatePriceRange(basePrice) {
+ const min = (basePrice * 0.9).toFixed(2)
+ const max = (basePrice * 1.1).toFixed(2)
+ return `${min} - ${max}`
+ },
+
+ // 计算置信度
+ calculateConfidence(month, day, region, breed) {
+ let confidence = 80 // 基础置信度
- // 整合用户输入信息
- console.log('准备整合用户输入信息');
- const userData = JSON.stringify({
- region,
- breed,
- spec,
- freshness,
- size,
- packaging,
- quantity
- });
- console.log('用户输入信息:', userData);
+ // 根据选择增加置信度
+ // 如果选择了今日(当前月份和日期),增加置信度
+ const now = new Date()
+ const currentMonth = (now.getMonth() + 1).toString()
+ const currentDate = now.getDate().toString()
- // 向后端发送请求
- console.log('准备发送API请求到 /api/openai/submit');
- api.request('/api/openai/submit', 'POST', {
- userdate: userData
- }).then(res => {
- console.log('API请求成功,响应数据:', res);
-
- // 检查响应数据有效性
- if (!res) {
- throw new Error('后端返回的响应为空');
- }
-
- // 解析后端返回的aidate数据
- let aiData;
- try {
- console.log('开始解析aidate数据:', res.aidate);
- aiData = typeof res.aidate === 'string' ? JSON.parse(res.aidate) : res.aidate;
- console.log('aidate解析结果:', aiData);
- } catch (e) {
- console.error('解析aidate失败:', e);
- console.warn('使用默认价格数据');
- // 如果解析失败,使用默认值
- aiData = {
- finalPrice: '0',
- totalPrice: '0'
- };
- }
-
- // 验证解析后的数据
- if (!aiData || typeof aiData !== 'object') {
- console.error('解析后的aidata格式错误:', aiData);
- aiData = {
- finalPrice: '0',
- totalPrice: '0'
- };
- }
-
- // 更新结果
- console.log('准备更新页面数据,finalPrice:', aiData.finalPrice || '0', 'totalPrice:', aiData.totalPrice || '0');
- this.setData({
- evaluateResult: {
- finalPrice: aiData.finalPrice || '0',
- totalPrice: aiData.totalPrice || '0',
- aidate: res.aidate || '' // 保存完整的aidate数据
- },
- evaluateStep: 9
- }, () => {
- console.log('页面数据更新完成,加载状态隐藏');
- wx.hideLoading();
- });
- }).catch(err => {
- console.error('API请求异常:', err);
- wx.hideLoading();
-
- // 根据不同类型的错误提供更详细的提示
- let errorMessage = '计算失败,请重试';
- if (err && err.message) {
- console.error('错误详情:', err.message);
- // 可以根据具体错误类型提供更精确的提示
- if (err.message.includes('网络')) {
- errorMessage = '网络连接异常,请检查网络设置';
- } else if (err.message.includes('超时')) {
- errorMessage = '请求超时,请稍后再试';
- }
- }
-
- wx.showToast({
- title: errorMessage,
- icon: 'none',
- duration: 3000
- });
- });
+ if (month === currentMonth && day === currentDate) {
+ confidence += 10
+ }
+
+ // 根据地区增加置信度
+ if (['北京', '上海', '广州', '深圳'].includes(region)) confidence += 5
+ if (['罗曼粉', '伊莎粉', '海蓝褐'].includes(breed)) confidence += 5
+
+ return Math.min(confidence, 95) + '%'
},
// 重新估价
- restartEvaluate() {
+ restartEvaluation() {
this.setData({
- evaluateStep: 1,
- evaluateData: {
- region: '',
- breed: '',
- spec: '',
- freshness: '',
- size: '',
- packaging: '',
- quantity: ''
- },
+ currentStep: 1,
+ selectedMonth: '',
+ selectedMonthIndex: 0,
+ selectedDay: '',
+ selectedDayIndex: 0,
+ selectedRegion: '',
+ selectedRegionIndex: 0,
+ selectedBreed: '',
+ selectedBreedIndex: 0,
evaluateResult: {
- finalPrice: '0',
- totalPrice: '0',
- aidate: ''
- },
- fromPreviousStep: false // 重置标志
- });
+ estimatedPrice: '0.00',
+ priceRange: '0.00 - 0.00',
+ confidence: '85%'
+ }
+ })
},
- // 返回首页
- backToHome() {
- wx.navigateBack();
+ // 重置估价(与restartEvaluation相同)
+ resetEvaluation() {
+ this.restartEvaluation()
},
-
- // 跳转到购物页面
- goToBuy() {
- console.log('goToBuy 函数被调用');
- // 使用与custom-tab-bar相同的跳转逻辑
- const url = 'pages/buyer/index';
-
- // 先尝试使用navigateTo
- wx.navigateTo({
- url: '/' + url,
- success: function(res) {
- console.log('使用navigateTo成功跳转到购物页面:', res);
- },
- fail: function(error) {
- console.log('navigateTo失败,尝试使用reLaunch:', error);
- // 如果navigateTo失败,使用reLaunch
- wx.reLaunch({
- url: '/' + url,
- success: function(res) {
- console.log('使用reLaunch成功跳转到购物页面:', res);
- },
- fail: function(err) {
- console.error('reLaunch也失败:', err);
- }
- });
- }
- });
+
+ // 查看市场
+ goToMarket() {
+ // 实际应用中,这里会跳转到市场页面
+ wx.showToast({
+ title: '功能开发中...',
+ icon: 'none',
+ duration: 2000
+ })
},
-
- // 跳转到货源页面
- goToSell() {
- console.log('goToSell 函数被调用');
- // 使用与custom-tab-bar相同的跳转逻辑
- const url = 'pages/seller/index';
-
- // 先尝试使用navigateTo
- wx.navigateTo({
- url: '/' + url,
- success: function(res) {
- console.log('使用navigateTo成功跳转到货源页面:', res);
- },
- fail: function(error) {
- console.log('navigateTo失败,尝试使用reLaunch:', error);
- // 如果navigateTo失败,使用reLaunch
- wx.reLaunch({
- url: '/' + url,
- success: function(res) {
- console.log('使用reLaunch成功跳转到货源页面:', res);
- },
- fail: function(err) {
- console.error('reLaunch也失败:', err);
- }
- });
- }
- });
+
+ // 返回上一步
+ goBack() {
+ if (this.data.currentStep > 1) {
+ this.setData({
+ currentStep: this.data.currentStep - 1
+ })
+ }
}
})
\ No newline at end of file
diff --git a/pages/evaluate/index.wxml b/pages/evaluate/index.wxml
index 8da677a..9714d1e 100644
--- a/pages/evaluate/index.wxml
+++ b/pages/evaluate/index.wxml
@@ -1,354 +1,158 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 步骤 {{evaluateStep}}/7
-
-
-
-
-
-
-
- 选择客户地区
- 请选择您所在的省份
-
-
-
-
- {{item}}
- 点击选择该省份
-
- ›
-
-
-
-
-
-
- 选择城市
- {{selectedProvince}} - 请选择具体城市
-
-
-
-
-
-
- {{item}}
- 点击选择该城市
-
- ›
-
-
-
-
-
-
-
-
+
+
- 选择鸡蛋品种
- 请选择您要估价的鸡蛋品种(按每日成交单量排序)
-
-
-
-
-
- {{eggBreed.rank}}
- {{eggBreed.name}}
+ 🥚
+ 鸡蛋估价系统
+
+ 欢迎使用鸡蛋估价功能!\n\n本系统将帮助您快速估算鸡蛋的市场价值。
+ \n\n估价流程:\n1. 选择您希望了解价格的时间范围\n2. 选择您所在的地区\n3. 选择鸡蛋品种\n\n系统将基于这些信息为您提供初步的价格估算。
+ \n请注意:\n• 此估价仅供参考,实际价格受市场波动影响\n• 建议咨询专业人士获取更准确的价格信息\n• 系统不支持真实交易,仅用于价格预估
- 点击选择该品种
-
- ›
-
+
+
-
-
+
+
- 选择具体规格
- 请选择鸡蛋的规格(按每日成交单量排序)
+
+ 选择估价参数
+ 请完成以下选择,系统将为您估价
-
-
-
- {{spec.rank}}
- {{spec.name}}
-
- ›
+
+
+ 选择时间
+
+
+
+
+
+ {{selectedMonth ? selectedMonth + '月' : '选择月份'}}
+
+ ▼
+
+
+
+
+
+
+
+ {{selectedDay ? selectedDay + '日' : '选择日期'}}
+
+ ▼
+
+
-
-
-
-
-
- 新鲜程度
- 请选择鸡蛋的新鲜程度
-
-
-
-
- 非常新鲜
- 7天内产出的新鲜鸡蛋
-
- ✓
-
-
-
-
- 较新鲜
- 15天内产出的鸡蛋
-
- ✓
-
-
-
-
- 一般
- 30天内产出的鸡蛋
-
- ✓
-
-
-
-
- 不新鲜
- 30天以上的鸡蛋
+
+
+ 选择地区
+
+
+
+ {{selectedRegion || '请选择地区'}}
+
+ ▼
- ✓
-
+
-
-
-
-
-
- 鸡蛋大小
- 请选择鸡蛋的大小规格
-
-
-
-
- 特大
- 单枚≥70g
-
- ✓
-
-
-
-
- 大
- 单枚60-70g
-
- ✓
-
-
-
-
- 中
- 单枚50-60g
-
- ✓
-
-
-
-
- 小
- 单枚<50g
+
+
+ 选择品种
+
+
+
+ {{selectedBreed || '请选择品种'}}
+
+ ▼
- ✓
-
+
-
-
-
-
-
- 包装情况
- 请选择鸡蛋的包装完好程度
-
-
-
-
- 原装完整
- 原包装完好无损
-
- ✓
-
-
-
-
- 部分包装
- 包装有轻微破损
-
- ✓
-
-
-
-
- 散装
- 无原包装
-
- ✓
-
+
+
+
-
-
-
-
- 请选择数量
- 请选择鸡蛋的数量
-
-
-
- 500个
- ›
-
-
- 1000个
- ›
-
-
- 2000个
- ›
-
-
- 10000个
- ›
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
- 🤖
- AI智能报价
+
+
+ 预估价格
+
+ ¥
+ {{evaluateResult.estimatedPrice}}
+ 元/斤
+
+ 价格区间:¥{{evaluateResult.priceRange}}
+
+
+
+
+ 估价详情
+
+
+ 时间
+ {{selectedMonth}}月{{selectedDay}}日
- {{evaluateResult.aidate}}
-
-
-
-
- {{evaluateData.breed}}
-
- {{evaluateData.spec}}
+
+ 地区
+ {{selectedRegion}}
-
-
-
-
- 商品状况
-
-
- 新鲜度
- {{evaluateData.freshness}}
-
-
- 大小
- {{evaluateData.size}}
-
-
- 包装
- {{evaluateData.packaging}}
-
-
- 规格
- {{evaluateData.spec}}
-
-
- 数量
- {{evaluateData.quantity}}个
-
-
- 单价
- {{evaluateResult.finalPrice}}元/斤
-
+
+ 品种
+ {{selectedBreed}}
+
+
+ 可信度
+ {{evaluateResult.confidence}}
-
-
-
- 💡
- 此价格仅供参考,实际成交价可能因市场波动有所差异
-
+
+
+
+
+ 💡
+ 此价格为系统初步估算,仅供参考。实际成交价格可能因市场供需、季节因素、品质差异等有所变动。建议结合多个渠道信息做出决策。
-
-
+
+
diff --git a/pages/evaluate/index.wxss b/pages/evaluate/index.wxss
index 3d853ad..94a21ab 100644
--- a/pages/evaluate/index.wxss
+++ b/pages/evaluate/index.wxss
@@ -145,6 +145,28 @@
color: #FF6B00;
}
+/* 返回上一步按钮样式 */
+.back-btn {
+ font-size: 28rpx;
+ color: #666;
+ display: inline-flex;
+ align-items: center;
+ padding: 10rpx 0;
+ margin-bottom: 20rpx;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ transition: color 0.2s ease;
+}
+
+.back-btn::after {
+ border: none;
+}
+
+.back-btn:active {
+ color: #FF6B00;
+}
+
/* 类别列表 */
.category-list {
background: white;
@@ -787,4 +809,337 @@
.btn-secondary::after, .btn-primary::after {
content: none;
+}
+
+/* ========== 新增3步流程样式 ========== */
+
+/* 步骤1:提示说明页面样式 */
+.step-icon {
+ font-size: 120rpx;
+ text-align: center;
+ margin-bottom: 40rpx;
+ animation: fadeIn 1s ease-out;
+}
+
+.step-description {
+ background: white;
+ border-radius: 20rpx;
+ padding: 40rpx;
+ margin-bottom: 60rpx;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
+ font-size: 28rpx;
+ color: #333;
+ line-height: 48rpx;
+ white-space: pre-line;
+ animation: slideUp 0.8s ease-out;
+}
+
+.know-btn-wrapper {
+ text-align: center;
+}
+
+.know-btn {
+ background: linear-gradient(135deg, #FF6B00, #FF9500);
+ color: white;
+ border: none;
+ border-radius: 50rpx;
+ padding: 32rpx 80rpx;
+ font-size: 34rpx;
+ font-weight: 700;
+ box-shadow: 0 10rpx 30rpx rgba(255, 107, 0, 0.35);
+ transition: all 0.3s ease;
+ letter-spacing: 2rpx;
+}
+
+.know-btn:active {
+ transform: scale(0.98);
+ box-shadow: 0 6rpx 20rpx rgba(255, 107, 0, 0.3);
+}
+
+.know-btn::after {
+ content: none;
+}
+
+/* 步骤2:选择参数页面样式 */
+.selection-group {
+ background: white;
+ border-radius: 20rpx;
+ padding: 40rpx;
+ margin-bottom: 30rpx;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
+ animation: slideUp 0.8s ease-out;
+}
+
+.selection-group:last-child {
+ margin-bottom: 60rpx;
+}
+
+.group-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #333;
+ margin-bottom: 30rpx;
+ letter-spacing: 1rpx;
+}
+
+.option-check {
+ color: #FF6B00;
+ font-size: 36rpx;
+ font-weight: bold;
+ background: white;
+ width: 50rpx;
+ height: 50rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3);
+}
+
+.evaluate-btn-wrapper {
+ text-align: center;
+}
+
+.evaluate-btn {
+ background: linear-gradient(135deg, #FF6B00, #FF9500);
+ color: white;
+ border: none;
+ border-radius: 50rpx;
+ padding: 32rpx 0;
+ font-size: 34rpx;
+ font-weight: 700;
+ width: 100%;
+ box-shadow: 0 10rpx 30rpx rgba(255, 107, 0, 0.35);
+ transition: all 0.3s ease;
+ letter-spacing: 2rpx;
+}
+
+.evaluate-btn.active {
+ background: linear-gradient(135deg, #FF6B00, #FF9500);
+ transform: scale(1);
+}
+
+.evaluate-btn.active:active {
+ transform: scale(0.98);
+ box-shadow: 0 6rpx 20rpx rgba(255, 107, 0, 0.3);
+}
+
+.evaluate-btn.disabled {
+ background: #ccc;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ transform: none;
+}
+
+.evaluate-btn::after {
+ content: none;
+}
+
+/* 步骤3:估价结果页面样式 */
+.result-header {
+ text-align: center;
+ padding: 60rpx 0 40rpx;
+ background: white;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.03);
+ animation: fadeIn 0.8s ease-out;
+}
+
+.result-icon {
+ font-size: 120rpx;
+ margin-bottom: 20rpx;
+ animation: bounce 1s ease-out;
+}
+
+.result-title {
+ font-size: 42rpx;
+ font-weight: 700;
+ color: #333;
+ margin-bottom: 20rpx;
+ letter-spacing: 2rpx;
+ animation: fadeUp 0.6s ease-out;
+}
+
+.result-subtitle {
+ font-size: 26rpx;
+ color: #666;
+ animation: fadeUp 0.6s ease-out 0.2s both;
+}
+
+.price-card {
+ background: white;
+ border-radius: 20rpx;
+ padding: 40rpx;
+ margin: 40rpx 0;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
+ text-align: center;
+ animation: pulseScale 1s ease-out;
+}
+
+.price-label {
+ font-size: 28rpx;
+ color: #666;
+ margin-bottom: 24rpx;
+ font-weight: 500;
+}
+
+.price-amount {
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ gap: 12rpx;
+ margin-bottom: 16rpx;
+}
+
+.price-symbol {
+ font-size: 44rpx;
+ color: #FF6B00;
+ font-weight: 700;
+}
+
+.price-number {
+ font-size: 72rpx;
+ font-weight: bold;
+ color: #FF6B00;
+ line-height: 1;
+ text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.2);
+}
+
+.price-unit {
+ font-size: 28rpx;
+ color: #666;
+ font-weight: 500;
+}
+
+.price-range {
+ font-size: 26rpx;
+ color: #999;
+ margin-top: 12rpx;
+}
+
+.evaluation-details {
+ background: white;
+ border-radius: 20rpx;
+ padding: 40rpx;
+ margin-bottom: 40rpx;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
+ animation: slideUp 0.8s ease-out 0.4s both;
+}
+
+.detail-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #333;
+ margin-bottom: 30rpx;
+ letter-spacing: 1rpx;
+ text-align: center;
+}
+
+.detail-items {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20rpx;
+}
+
+.detail-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 20rpx;
+ background: #f8f8f8;
+ border-radius: 12rpx;
+ transition: all 0.2s ease;
+}
+
+.detail-item:active {
+ background: #f0f0f0;
+}
+
+.detail-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 8rpx;
+ font-weight: 500;
+}
+
+.detail-value {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #333;
+ text-align: center;
+}
+
+/* ========== 下拉框选择器样式 ========== */
+
+.picker-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: white;
+ border: 2rpx solid #e0e0e0;
+ border-radius: 16rpx;
+ padding: 32rpx;
+ transition: all 0.3s ease;
+ position: relative;
+}
+
+.picker-wrapper:active {
+ border-color: #FF6B00;
+ background: #fff8f0;
+}
+
+.picker-wrapper.small {
+ padding: 24rpx;
+ margin: 0 10rpx;
+ border-radius: 12rpx;
+}
+
+.picker-wrapper.disabled {
+ background: #f5f5f5;
+ border-color: #e0e0e0;
+}
+
+.picker-wrapper.disabled:active {
+ border-color: #e0e0e0;
+ background: #f5f5f5;
+}
+
+.picker-display {
+ flex: 1;
+ font-size: 32rpx;
+ color: #999;
+ transition: color 0.3s ease;
+}
+
+.picker-display.selected {
+ color: #333;
+ font-weight: 500;
+}
+
+.picker-arrow {
+ font-size: 24rpx;
+ color: #999;
+ transition: transform 0.3s ease;
+ margin-left: 20rpx;
+}
+
+.picker-wrapper:active .picker-arrow {
+ transform: scale(1.1);
+}
+
+/* ========== 月日二级框样式 ========== */
+
+.date-selector {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: white;
+ border-radius: 20rpx;
+ padding: 20rpx;
+ box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
+}
+
+.date-selector .picker-wrapper {
+ flex: 1;
+ margin: 0;
+ box-shadow: none;
+ border: 2rpx solid #e0e0e0;
}
\ No newline at end of file
diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js
index 02cd7e4..85e7ab1 100644
--- a/server-example/server-mysql.js
+++ b/server-example/server-mysql.js
@@ -1032,6 +1032,46 @@ UserManagement.init({
timestamps: false
});
+// 价格资源模型 - 用于估价功能(匹配实际数据库表结构)
+class Resources extends Model { }
+Resources.init({
+ id: {
+ type: DataTypes.INTEGER.UNSIGNED,
+ autoIncrement: true,
+ primaryKey: true
+ },
+ category: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ comment: '品种/分类'
+ },
+ region: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ comment: '地区'
+ },
+ price1: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ comment: '商品价格上限'
+ },
+ price2: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ comment: '商品价格下限'
+ },
+ time: {
+ type: DataTypes.DATEONLY,
+ allowNull: false,
+ comment: '价格日期'
+ }
+}, {
+ sequelize,
+ modelName: 'Resources',
+ tableName: 'resources',
+ timestamps: false
+});
+
// 定义模型之间的关联关系
// 用户和商品的一对多关系 (卖家发布商品)
@@ -8658,6 +8698,230 @@ app.post('/api/personnel/get', async (req, res) => {
}
});
+// 估价API端点 - 基于resources表的历史价格数据
+app.post('/api/evaluate/price', async (req, res) => {
+ try {
+ // 获取原始请求体,并检查编码问题
+ console.log('原始请求体:', JSON.stringify(req.body));
+
+ // 提取参数并进行编码检查
+ const { month, day, region, breed } = req.body;
+
+ // 记录详细的参数信息
+ console.log('请求参数详细信息:', {
+ month: {
+ value: month,
+ type: typeof month,
+ isEmpty: !month,
+ isValid: !!month
+ },
+ day: {
+ value: day,
+ type: typeof day,
+ isEmpty: !day,
+ isValid: !!day
+ },
+ region: {
+ value: region,
+ type: typeof region,
+ isEmpty: !region,
+ isValid: !!region,
+ isEncoded: /[?%]/.test(region || '')
+ },
+ breed: {
+ value: breed,
+ type: typeof breed,
+ isEmpty: !breed,
+ isValid: !!breed,
+ isEncoded: /[?%]/.test(breed || '')
+ }
+ });
+
+ // 检查参数有效性
+ if (!month || !day || !region || !breed) {
+ return res.status(400).json({
+ success: false,
+ code: 400,
+ message: '缺少必要参数:month, day, region, breed'
+ });
+ }
+
+ // 构建查询日期
+ const currentYear = new Date().getFullYear();
+ const targetDate = new Date(currentYear, parseInt(month) - 1, parseInt(day));
+ const previousYear = currentYear - 1;
+ const twoYearsAgo = currentYear - 2;
+
+ // 格式化日期为YYYY-MM-DD
+ const formatDate = (date) => {
+ // 确保日期格式正确
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ };
+
+ // 构建查询日期列表
+ const queryDates = [
+ formatDate(new Date(previousYear, parseInt(month) - 1, parseInt(day))),
+ formatDate(new Date(twoYearsAgo, parseInt(month) - 1, parseInt(day))),
+ formatDate(new Date(currentYear, parseInt(month) - 1, parseInt(day) - 1))
+ ];
+
+ console.log('查询日期列表:', queryDates);
+ console.log('查询参数:', {
+ region: region,
+ category: breed,
+ timeIn: queryDates
+ });
+
+ // 查询历史价格数据(使用time字段和category字段)
+ const historicalData = await Resources.findAll({
+ where: {
+ region: region,
+ category: breed, // 使用category字段映射breed参数
+ time: {
+ [Op.in]: queryDates
+ }
+ },
+ order: [['time', 'DESC']]
+ });
+
+ console.log('查询到的历史数据数量:', historicalData.length);
+
+ // 如果找到了数据,输出详细数据用于调试
+ if (historicalData.length > 0) {
+ console.log('历史数据详情:', historicalData.map(item => ({
+ id: item.id,
+ category: item.category,
+ region: item.region,
+ time: item.time,
+ price1: item.price1,
+ price2: item.price2
+ })));
+ }
+
+ // 如果没有足够的历史数据,返回默认值
+ if (historicalData.length === 0) {
+ return res.status(200).json({
+ success: true,
+ code: 200,
+ message: '数据不足,使用默认估价',
+ data: {
+ estimatedPrice: 4.50,
+ priceRange: '4.05 - 4.95',
+ confidence: '75%',
+ trend: 'stable',
+ changePercent: 0,
+ dataSource: 'default',
+ explanation: '由于历史数据不足,使用默认价格估算'
+ }
+ });
+ }
+
+ // 处理历史价格数据(使用price1和price2字段)
+ const priceData = historicalData.map(item => {
+ // 计算平均价格作为实际价格
+ const price1 = parseFloat(item.price1) || 0;
+ const price2 = parseFloat(item.price2) || 0;
+ const actualPrice = (price1 + price2) / 2 || 4.5; // 如果没有价格数据,使用默认价格4.5
+
+ return {
+ date: item.time,
+ price: actualPrice,
+ price1: price1,
+ price2: price2,
+ year: new Date(item.time).getFullYear()
+ };
+ });
+
+ // 计算价格趋势
+ const latestPrice = priceData[0]?.price || 4.5;
+ const previousYearPrice = priceData.find(p => p.year === previousYear)?.price || latestPrice;
+ const twoYearsAgoPrice = priceData.find(p => p.year === twoYearsAgo)?.price || latestPrice;
+ const previousDayPrice = priceData.find(p => p.year === currentYear)?.price || latestPrice;
+
+ // 计算涨幅(与前一天比较)
+ const priceChange = latestPrice - previousDayPrice;
+ const changePercent = previousDayPrice > 0 ? (priceChange / previousDayPrice) * 100 : 0;
+
+ // 判断趋势
+ let trend = 'stable';
+ if (changePercent > 0.2) {
+ trend = 'rising';
+ } else if (changePercent < -0.2) {
+ trend = 'falling';
+ }
+
+ // 计算预估价格(基于历史数据和趋势)
+ let estimatedPrice = latestPrice;
+ if (changePercent > 0.2) {
+ // 涨幅超过0.2%,小幅上涨
+ estimatedPrice = latestPrice * (1 + Math.min(changePercent / 100, 0.02));
+ } else if (changePercent < -0.2) {
+ // 跌幅超过0.2%,小幅下跌
+ estimatedPrice = latestPrice * (1 + Math.max(changePercent / 100, -0.02));
+ }
+
+ // 确保价格合理范围
+ estimatedPrice = Math.max(estimatedPrice, 3.0); // 最低3元/斤
+ estimatedPrice = Math.min(estimatedPrice, 8.0); // 最高8元/斤
+
+ // 计算价格区间
+ const priceRangeMin = (estimatedPrice * 0.9).toFixed(2);
+ const priceRangeMax = (estimatedPrice * 1.1).toFixed(2);
+
+ // 计算置信度
+ let confidence = '80%';
+ if (historicalData.length >= 3) {
+ confidence = '90%';
+ } else if (historicalData.length >= 2) {
+ confidence = '85%';
+ }
+
+ // 构建响应数据
+ const responseData = {
+ estimatedPrice: estimatedPrice.toFixed(2),
+ priceRange: `${priceRangeMin} - ${priceRangeMax}`,
+ confidence: confidence,
+ trend: trend,
+ changePercent: changePercent.toFixed(2),
+ dataSource: 'historical',
+ explanation: `基于${historicalData.length}条历史数据,结合当前趋势进行估算`,
+ historicalPrices: priceData,
+ comparison: {
+ vsPreviousDay: {
+ price: previousDayPrice.toFixed(2),
+ change: priceChange.toFixed(2),
+ changePercent: changePercent.toFixed(2) + '%'
+ },
+ vsLastYear: {
+ price: previousYearPrice.toFixed(2),
+ change: (latestPrice - previousYearPrice).toFixed(2),
+ changePercent: ((latestPrice - previousYearPrice) / previousYearPrice * 100).toFixed(2) + '%'
+ }
+ }
+ };
+
+ console.log('估价结果:', responseData);
+
+ res.status(200).json({
+ success: true,
+ code: 200,
+ message: '估价成功',
+ data: responseData
+ });
+
+ } catch (error) {
+ console.error('估价API错误:', error);
+ res.status(500).json({
+ success: false,
+ code: 500,
+ message: '估价失败: ' + error.message
+ });
+ }
+});
+
// 在服务器启动前执行商品联系人更新
updateProductContacts().then(() => {
console.log('\n📦 商品联系人信息更新完成!');
@@ -8671,6 +8935,31 @@ updateProductContacts().then(() => {
startConnectionMonitoring();
console.log('连接监控服务已启动');
+ // 调试API - 查看resources表结构
+ app.get('/api/debug/resources-table', async (req, res) => {
+ try {
+ // 查询表结构
+ const [tableStructure] = await sequelize.query('DESCRIBE resources');
+ console.log('Resources表结构:', tableStructure);
+
+ // 查询示例数据
+ const sampleData = await sequelize.query('SELECT * FROM resources LIMIT 5');
+ console.log('Resources示例数据:', sampleData[0]);
+
+ res.json({
+ success: true,
+ tableStructure: tableStructure,
+ sampleData: sampleData[0]
+ });
+ } catch (error) {
+ console.error('调试API错误:', error);
+ res.status(500).json({
+ success: false,
+ message: error.message
+ });
+ }
+ });
+
server.listen(PORT, '0.0.0.0', () => {
console.log(`\n🚀 服务器启动成功,监听端口 ${PORT}`);
console.log(`API 服务地址: http://localhost:${PORT}`);
@@ -8686,6 +8975,7 @@ module.exports = {
User,
Product,
CartItem,
+ Resources,
sequelize,
createUserAssociations,
app,
diff --git a/utils/api.js b/utils/api.js
index 9225864..7ae9ff7 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -3936,6 +3936,35 @@ module.exports = {
resolve(['全部', '粉壳', '褐壳', '绿壳', '白壳']);
});
});
+ },
+
+ // 价格评估API方法
+ evaluatePrice: function(month, day, region, breed) {
+ return new Promise((resolve, reject) => {
+ console.log('调用价格评估API:', { month, day, region, breed });
+
+ const requestData = {
+ month: month,
+ day: day,
+ region: region,
+ breed: breed
+ };
+
+ request('/api/evaluate/price', 'POST', requestData)
+ .then(response => {
+ console.log('价格评估API响应:', response);
+
+ if (response.success) {
+ resolve(response.data);
+ } else {
+ reject(new Error(response.message || '价格评估失败'));
+ }
+ })
+ .catch(error => {
+ console.error('价格评估API错误:', error);
+ reject(error);
+ });
+ });
}
};
\ No newline at end of file