Browse Source

实现价格评估功能:添加数据库模型、API端点和前端集成

- 在server-mysql.js中添加Resources模型定义,映射resources表结构
- 创建/api/evaluate/price端点,支持基于历史数据的鸡蛋价格评估
- 实现价格趋势分析算法,预测价格波动在0.2范围内
- 更新evaluate/index.js中的地区和品种选项以匹配数据库数据
- 在utils/api.js中添加evaluatePrice方法用于调用新的API端点
- 添加详细的错误处理和日志记录用于调试
pull/3/head
徐飞洋 2 months ago
parent
commit
cfc1a13042
  1. 30
      custom-tab-bar/index.js
  2. 10
      custom-tab-bar/index.wxml
  3. 719
      pages/evaluate/index.js
  4. 432
      pages/evaluate/index.wxml
  5. 355
      pages/evaluate/index.wxss
  6. 290
      server-example/server-mysql.js
  7. 29
      utils/api.js

30
custom-tab-bar/index.js

@ -15,7 +15,9 @@ Component({
tabBarItems: [ tabBarItems: [
{ key: 'index', route: 'pages/index/index' }, { key: 'index', route: 'pages/index/index' },
{ key: 'chat', route: 'pages/chat/index', badgeKey: 'chat' }, { key: 'chat', route: 'pages/chat/index', badgeKey: 'chat' },
{ key: 'evaluate', route: 'pages/evaluate/index' },
{ key: 'settlement', route: 'pages/settlement/index' }, { key: 'settlement', route: 'pages/settlement/index' },
{ key: 'favorites', route: 'pages/favorites/index' },
{ key: 'profile', route: 'pages/profile/index' } { key: 'profile', route: 'pages/profile/index' }
] ]
}, },
@ -91,9 +93,10 @@ Component({
// 定义tabBar页面列表 // 定义tabBar页面列表
const tabBarPages = [ const tabBarPages = [
'pages/index/index', 'pages/index/index',
'pages/chat/index', 'pages/chat/index',
'pages/seller/index', 'pages/evaluate/index',
'pages/settlement/index', 'pages/settlement/index',
'pages/favorites/index',
'pages/profile/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() { syncFromGlobalData() {
try { try {

10
custom-tab-bar/index.wxml

@ -42,6 +42,16 @@
<view class="tab-bar-text">入驻</view> <view class="tab-bar-text">入驻</view>
</view> </view>
<view class="tab-bar-item {{selected === 'favorites' ? 'active' : ''}}"
data-path="pages/favorites/index"
data-key="favorites"
bindtap="switchTab">
<view class="tab-bar-icon">
<view class="tab-bar-badge" wx:if="{{badges['favorites']}}">{{badges['favorites']}}</view>
</view>
<view class="tab-bar-text">收藏</view>
</view>
<view class="tab-bar-item {{selected === 'profile' ? 'active' : ''}}" <view class="tab-bar-item {{selected === 'profile' ? 'active' : ''}}"
data-path="pages/profile/index" data-path="pages/profile/index"
data-key="profile" data-key="profile"

719
pages/evaluate/index.js

@ -1,530 +1,313 @@
// pages/evaluate/index.js // pages/evaluate/index.js
//估价页面 //估价页面 - 简化版三步骤流程
const api = require('../../utils/api.js');
Page({ const api = require('../../utils/api');
// 分享给朋友/群聊
onShareAppMessage() {
return {
title: '鸡蛋贸易平台 - 智能估价,专业分析',
path: '/pages/evaluate/index',
imageUrl: '/images/你有好蛋.png'
}
},
// 分享到朋友圈
onShareTimeline() {
return {
title: '鸡蛋贸易平台 - 智能估价,专业分析',
query: '',
imageUrl: '/images/你有好蛋.png'
}
},
Page({
data: { data: {
evaluateStep: 1, currentStep: 1,
fromPreviousStep: false, // 用于标记是否从下一步返回 // 月日选择
evaluateData: { selectedMonth: '',
region: '', selectedMonthIndex: 0,
breed: '', selectedDay: '',
spec: '', selectedDayIndex: 0,
freshness: '', months: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
size: '', days: [],
packaging: '', // 地区和品种选择
quantity: '' selectedRegion: '',
}, selectedRegionIndex: 0,
// 客户地区列表 - 省市二级联动数据 selectedBreed: '',
provinceCities: { selectedBreedIndex: 0,
'北京': ['北京'],
'河北': ['石家庄', '唐山', '秦皇岛', '邯郸', '邢台', '保定', '张家口', '承德', '沧州', '廊坊', '衡水'],
'四川': ['成都', '自贡', '攀枝花', '泸州', '德阳', '绵阳', '广元', '遂宁', '内江', '乐山', '南充', '眉山', '宜宾', '广安', '达州', '雅安', '巴中', '资阳'],
'云南': ['昆明', '曲靖', '玉溪', '保山', '昭通', '丽江', '普洱', '临沧', '楚雄', '红河', '文山', '西双版纳', '大理', '德宏', '怒江', '迪庆'],
'贵州': ['贵阳', '六盘水', '遵义', '安顺', '毕节', '铜仁', '黔西南', '黔东南', '黔南']
},
// 省份列表
provinces: [],
// 城市列表
cities: [],
// 当前选中的省份
selectedProvince: '',
// 当前是否显示城市选择
showCities: false,
evaluateResult: { evaluateResult: {
finalPrice: '0', estimatedPrice: '0.00',
totalPrice: '0', priceRange: '0.00 - 0.00',
aidate: '' confidence: '85%'
}, },
// 鸡蛋品种数据(包含成交单量) // 地区选项
eggBreeds: [ regionOptions: [
{ name: '罗曼粉', dailyOrders: 2341 }, '成都市场报价'
{ name: '伊莎粉', dailyOrders: 1892 },
{ name: '罗曼灰', dailyOrders: 1567 },
{ name: '海蓝灰', dailyOrders: 1432 },
{ name: '海蓝褐', dailyOrders: 1298 },
{ name: '绿壳', dailyOrders: 1054 },
{ name: '粉一', dailyOrders: 987 },
{ name: '粉二', dailyOrders: 876 },
{ name: '粉八', dailyOrders: 765 },
{ name: '京粉1号', dailyOrders: 654 },
{ name: '京红', dailyOrders: 543 },
{ name: '京粉6号', dailyOrders: 456 },
{ name: '京粉3号', dailyOrders: 389 },
{ name: '农大系列', dailyOrders: 321 },
{ name: '黑鸡土蛋', dailyOrders: 298 },
{ name: '双黄蛋', dailyOrders: 245 },
{ name: '大午金凤', dailyOrders: 198 },
{ name: '黑凤', dailyOrders: 156 }
], ],
// 品种选项
// 鸡蛋规格数据 breedOptions: [
eggSpecs: [ '黄蛋(红蛋)'
{ name: '格子装', dailyOrders: 3214 },
{ name: '散托', dailyOrders: 2890 },
{ name: '不限规格', dailyOrders: 2567 },
{ name: '净重47+', dailyOrders: 2345 },
{ name: '净重46-47', dailyOrders: 2109 },
{ name: '净重45-46', dailyOrders: 1876 },
{ name: '净重44-45', dailyOrders: 1654 },
{ name: '净重43-44', dailyOrders: 1432 },
{ name: '净重42-43', dailyOrders: 1234 },
{ name: '净重41-42', dailyOrders: 1056 },
{ name: '净重40-41', dailyOrders: 987 },
{ name: '净重39-40', dailyOrders: 876 },
{ name: '净重38-39', dailyOrders: 765 },
{ name: '净重37-39', dailyOrders: 654 },
{ name: '净重37-38', dailyOrders: 543 },
{ name: '净重36-38', dailyOrders: 456 },
{ name: '净重36-37', dailyOrders: 389 },
{ name: '净重35-36', dailyOrders: 321 },
{ name: '净重34-35', dailyOrders: 289 },
{ name: '净重33-34', dailyOrders: 256 },
{ name: '净重32-33', dailyOrders: 223 },
{ name: '净重32-34', dailyOrders: 198 },
{ name: '净重31-32', dailyOrders: 165 },
{ name: '净重30-35', dailyOrders: 143 },
{ name: '净重30-34', dailyOrders: 121 },
{ name: '净重30-32', dailyOrders: 109 },
{ name: '净重30-31', dailyOrders: 98 },
{ name: '净重29-31', dailyOrders: 87 },
{ name: '净重29-30', dailyOrders: 76 },
{ name: '净重28-29', dailyOrders: 65 },
{ name: '净重28以下', dailyOrders: 54 },
{ name: '毛重52以上', dailyOrders: 132 },
{ name: '毛重50-51', dailyOrders: 121 },
{ name: '毛重48-49', dailyOrders: 109 },
{ name: '毛重47-48', dailyOrders: 98 },
{ name: '毛重46-47', dailyOrders: 87 },
{ name: '毛重45-47', dailyOrders: 76 },
{ name: '毛重45-46', dailyOrders: 65 },
{ name: '毛重44-45', dailyOrders: 54 },
{ name: '毛重43-44', dailyOrders: 43 },
{ name: '毛重42-43', dailyOrders: 32 },
{ name: '毛重41-42', dailyOrders: 21 },
{ name: '毛重40-41', dailyOrders: 19 },
{ name: '毛重38-39', dailyOrders: 17 },
{ name: '毛重36-37', dailyOrders: 15 },
{ name: '毛重34-35', dailyOrders: 13 },
{ name: '毛重32-33', dailyOrders: 11 },
{ name: '毛重30-31', dailyOrders: 9 },
{ name: '毛重30以下', dailyOrders: 7 }
] ]
}, },
onLoad() { onLoad() {
console.log('估价页面初始化') console.log('估价页面初始化 - 简化版')
// 页面加载时,对鸡蛋品种按成交单量降序排序并添加排名
const sortedBreeds = [...this.data.eggBreeds].sort((a, b) => 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
}));
// 初始化省份列表 // 初始化为当前北京时间
const provinces = Object.keys(this.data.provinceCities); this.initCurrentDate()
this.setData({
eggBreeds: breedsWithRank,
eggSpecs: specsWithRank,
fromPreviousStep: false, // 初始化标志
provinces: provinces
});
}, },
// 选择省份 // 初始化当前日期
selectProvince(e) { initCurrentDate() {
const province = e.currentTarget.dataset.province; const now = new Date()
const cities = this.data.provinceCities[province]; const currentMonth = (now.getMonth() + 1).toString()
this.setData({ const currentDate = now.getDate().toString()
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;
}
const fullRegion = `${this.data.selectedProvince}-${city}`; // 设置默认月份
const monthIndex = this.data.months.indexOf(currentMonth)
this.setData({ this.setData({
'evaluateData.region': fullRegion, selectedMonth: currentMonth,
showCities: false selectedMonthIndex: monthIndex >= 0 ? monthIndex : 0
}); })
// 显示选择成功提示 // 根据当前月份更新日期数组
wx.showToast({ this.updateDaysForMonth(currentMonth)
title: `已选择: ${fullRegion}`,
icon: 'success',
duration: 1500
});
// 只有当当前步骤是1且已经从下一步返回时,才自动进入下一步
if (this.data.evaluateStep === 1 && !this.data.fromPreviousStep) {
this.setData({
evaluateStep: 2
});
}
// 重置标志 // 设置默认日期
const dayIndex = this.data.days.indexOf(currentDate)
this.setData({ this.setData({
fromPreviousStep: false selectedDay: dayIndex >= 0 ? currentDate : '',
}); selectedDayIndex: dayIndex >= 0 ? dayIndex : 0
})
}, },
// 返回选择省份 // 第一步:点击我知道了
backToProvince() { onIKnow() {
this.setData({ this.setData({
showCities: false currentStep: 2
}); })
}, },
// 选择鸡蛋品种 // 月份选择器变化
selectEggBreed(e) { onMonthChange(e) {
const breed = e.currentTarget.dataset.breed; const monthIndex = e.detail.value
this.setData({ const selectedMonth = this.data.months[monthIndex]
'evaluateData.breed': breed
});
// 显示选择成功提示
wx.showToast({
title: '已选择: ' + breed,
icon: 'none',
duration: 1500
});
// 只有当当前步骤是2且不是从下一步返回时,才自动进入下一步 // 更新月份
if (this.data.evaluateStep === 2 && !this.data.fromPreviousStep) { this.setData({
this.setData({ evaluateStep: 3 }); selectedMonth: selectedMonth,
} selectedMonthIndex: monthIndex,
selectedDay: '', // 清空日期选择
selectedDayIndex: 0
})
// 重置标志 // 根据月份设置对应的日期数组
this.setData({ fromPreviousStep: false }); this.updateDaysForMonth(selectedMonth)
}, },
// 选择鸡蛋规格 // 日期选择器变化
selectEggSpec(e) { onDayChange(e) {
const spec = e.currentTarget.dataset.spec; const dayIndex = e.detail.value
const selectedDay = this.data.days[dayIndex]
this.setData({ this.setData({
'evaluateData.spec': spec selectedDay: selectedDay,
}); selectedDayIndex: dayIndex
})
// 显示选择成功提示 },
wx.showToast({
title: '已选择: ' + spec, // 根据月份更新日期数组
icon: 'none', updateDaysForMonth(month) {
duration: 1500 let daysCount
});
// 只有当当前步骤是3且不是从下一步返回时,才自动进入下一步 // 根据月份设置日期数量
if (this.data.evaluateStep === 3 && !this.data.fromPreviousStep) { switch(month) {
this.setData({ evaluateStep: 4 }); case '2': // 2月
daysCount = 28 // 简化处理,不考虑闰年
break
case '4':
case '6':
case '9':
case '11': // 小月
daysCount = 30
break
default: // 大月
daysCount = 31
} }
// 重置标志 const days = []
this.setData({ fromPreviousStep: false }); for (let i = 1; i <= daysCount; i++) {
}, days.push(i.toString())
// 格式化订单数量显示
formatOrderCount(count) {
if (count >= 10000) {
return (count / 10000).toFixed(1) + '万';
} else if (count >= 1000) {
return (count / 1000).toFixed(1) + 'k';
} }
return count.toString();
this.setData({
days: days,
selectedDay: '',
selectedDayIndex: 0
})
}, },
// 选择条件 // 地区选择器变化
selectCondition(e) { onRegionChange(e) {
const { type, value } = e.currentTarget.dataset; const index = e.detail.value
const selectedRegion = this.data.regionOptions[index]
this.setData({ this.setData({
[`evaluateData.${type}`]: value selectedRegion: selectedRegion,
}); selectedRegionIndex: index
})
// 只有当当前步骤不是从返回过来的,才自动进入下一步
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 });
}, },
// 选择数量 // 品种选择器变化
selectQuantity(e) { onBreedChange(e) {
const quantity = e.currentTarget.dataset.quantity; const index = e.detail.value
const selectedBreed = this.data.breedOptions[index]
this.setData({ this.setData({
'evaluateData.quantity': quantity selectedBreed: selectedBreed,
}); selectedBreedIndex: index
})
}, },
// 获取报 // 开始估价
getQuote() { startEvaluation() {
if (this.data.evaluateData.quantity) { const { selectedMonth, selectedDay, selectedRegion, selectedBreed } = this.data
this.calculatePrice();
} else { if (!selectedMonth || !selectedDay || !selectedRegion || !selectedBreed) {
wx.showToast({ wx.showToast({
title: '请选择数量', title: '请完成所有选择',
icon: 'none', icon: 'none',
duration: 2000 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() { getTimeMultiplier(timeValue) {
if (this.data.evaluateStep > 1 && this.data.evaluateStep < 9) { const multipliers = {
this.setData({ 'today': 1.0,
evaluateStep: this.data.evaluateStep - 1, 'tomorrow': 0.98,
fromPreviousStep: true // 标记是从下一步返回的 'week': 0.95,
}); 'month': 0.90
} else if (this.data.evaluateStep === 9) {
// 如果在结果页面,返回第七步(数量选择)
this.setData({
evaluateStep: 7,
fromPreviousStep: true
});
} else {
// 如果在第一步,返回上一页
wx.navigateBack();
} }
return multipliers[timeValue] || 1.0
}, },
// 计算价格 // 获取地区系数
calculatePrice() { getRegionMultiplier(region) {
console.log('开始执行calculatePrice函数'); const tier1Cities = ['北京', '上海', '广州', '深圳']
const { region, breed, spec, freshness, size, packaging, quantity } = this.data.evaluateData; const tier2Cities = ['天津', '江苏', '浙江', '山东']
// 校验必填参数 if (tier1Cities.includes(region)) return 1.15
console.log('校验必填参数:', { region, breed, spec, freshness, size, packaging, quantity }); if (tier2Cities.includes(region)) return 1.05
if (!region || !breed || !spec || !freshness || !size || !packaging || !quantity) { return 1.0
console.error('参数校验失败,缺少必要信息'); },
wx.showToast({
title: '请完成所有选项', // 获取品种系数
icon: 'none', getBreedMultiplier(breed) {
duration: 2000 const premiumBreeds = ['绿壳', '双黄蛋', '黑鸡土蛋']
}); const popularBreeds = ['罗曼粉', '伊莎粉', '海蓝褐']
return;
}
// 显示加载中 if (premiumBreeds.includes(breed)) return 1.20
wx.showLoading({ if (popularBreeds.includes(breed)) return 1.10
title: '计算中...', return 1.0
mask: true },
});
// 计算价格区间
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({ const now = new Date()
region, const currentMonth = (now.getMonth() + 1).toString()
breed, const currentDate = now.getDate().toString()
spec,
freshness,
size,
packaging,
quantity
});
console.log('用户输入信息:', userData);
// 向后端发送请求 if (month === currentMonth && day === currentDate) {
console.log('准备发送API请求到 /api/openai/submit'); confidence += 10
api.request('/api/openai/submit', 'POST', { }
userdate: userData
}).then(res => { // 根据地区增加置信度
console.log('API请求成功,响应数据:', res); if (['北京', '上海', '广州', '深圳'].includes(region)) confidence += 5
if (['罗曼粉', '伊莎粉', '海蓝褐'].includes(breed)) confidence += 5
// 检查响应数据有效性
if (!res) { return Math.min(confidence, 95) + '%'
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
});
});
}, },
// 重新估价 // 重新估价
restartEvaluate() { restartEvaluation() {
this.setData({ this.setData({
evaluateStep: 1, currentStep: 1,
evaluateData: { selectedMonth: '',
region: '', selectedMonthIndex: 0,
breed: '', selectedDay: '',
spec: '', selectedDayIndex: 0,
freshness: '', selectedRegion: '',
size: '', selectedRegionIndex: 0,
packaging: '', selectedBreed: '',
quantity: '' selectedBreedIndex: 0,
},
evaluateResult: { evaluateResult: {
finalPrice: '0', estimatedPrice: '0.00',
totalPrice: '0', priceRange: '0.00 - 0.00',
aidate: '' confidence: '85%'
}, }
fromPreviousStep: false // 重置标志 })
});
}, },
// 返回首页 // 重置估价(与restartEvaluation相同)
backToHome() { resetEvaluation() {
wx.navigateBack(); this.restartEvaluation()
}, },
// 跳转到购物页面 // 查看市场
goToBuy() { goToMarket() {
console.log('goToBuy 函数被调用'); // 实际应用中,这里会跳转到市场页面
// 使用与custom-tab-bar相同的跳转逻辑 wx.showToast({
const url = 'pages/buyer/index'; title: '功能开发中...',
icon: 'none',
// 先尝试使用navigateTo duration: 2000
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);
}
});
}
});
}, },
// 跳转到货源页面 // 返回上一步
goToSell() { goBack() {
console.log('goToSell 函数被调用'); if (this.data.currentStep > 1) {
// 使用与custom-tab-bar相同的跳转逻辑 this.setData({
const url = 'pages/seller/index'; currentStep: this.data.currentStep - 1
})
// 先尝试使用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);
}
});
}
});
} }
}) })

432
pages/evaluate/index.wxml

@ -1,354 +1,158 @@
<!--pages/evaluate/index.wxml--> <!--pages/evaluate/index.wxml-->
<view class="evaluate-page"> <view class="evaluate-page">
<!-- 顶部导航 --> <!-- 步骤1:提示说明 -->
<view class="evaluate-header"> <view wx:if="{{currentStep === 1}}" class="evaluate-step">
<view class="header-back" bindtap="prevStep">‹</view>
<view class="header-title">鸡蛋估价</view>
<view class="header-placeholder"></view>
</view>
<!-- 进度指示器 -->
<view class="progress-indicator" wx:if="{{evaluateStep < 8}}">
<view class="progress-dots">
<view class="progress-dot {{evaluateStep >= 1 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 2 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 3 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 4 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 5 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 6 ? 'active' : ''}}"></view>
<view class="progress-dot {{evaluateStep >= 7 ? 'active' : ''}}"></view>
</view>
<view class="progress-text">步骤 {{evaluateStep}}/7</view>
</view>
<!-- 步骤1:选择客户地区 -->
<view wx:if="{{evaluateStep === 1}}" class="evaluate-step">
<view class="step-content">
<!-- 省份选择 -->
<view wx:if="{{!showCities}}">
<view class="step-title">选择客户地区</view>
<view class="step-subtitle">请选择您所在的省份</view>
<view class="category-list">
<view wx:for="{{provinces}}" wx:key="*this"
class="category-item"
bindtap="selectProvince" data-province="{{item}}">
<view class="category-info">
<view class="category-name">{{item}}</view>
<view class="category-desc">点击选择该省份</view>
</view>
<view class="category-arrow">›</view>
</view>
</view>
</view>
<!-- 城市选择 -->
<view wx:if="{{showCities}}">
<view class="step-title">选择城市</view>
<view class="step-subtitle">{{selectedProvince}} - 请选择具体城市</view>
<view class="city-header" bindtap="backToProvince">
<view class="city-back">‹ 返回省份选择</view>
</view>
<view class="category-list">
<view wx:for="{{cities}}" wx:key="*this"
class="category-item {{evaluateData.region === selectedProvince + '-' + item ? 'selected' : ''}}"
bindtap="selectCity" data-city="{{item}}">
<view class="category-info">
<view class="category-name">{{item}}</view>
<view class="category-desc">点击选择该城市</view>
</view>
<view class="category-arrow">›</view>
</view>
</view>
</view>
</view>
</view>
<!-- 步骤2:选择鸡蛋品种 -->
<view wx:if="{{evaluateStep === 2}}" class="evaluate-step">
<view class="step-content"> <view class="step-content">
<view class="step-title">选择鸡蛋品种</view> <view class="step-icon">🥚</view>
<view class="step-subtitle">请选择您要估价的鸡蛋品种(按每日成交单量排序)</view> <view class="step-title">鸡蛋估价系统</view>
<view class="step-description">
<view class="category-list"> <text>欢迎使用鸡蛋估价功能!\n\n本系统将帮助您快速估算鸡蛋的市场价值。</text>
<view wx:for="{{eggBreeds}}" wx:key="name" wx:for-item="eggBreed" \n\n<text>估价流程:\n1. 选择您希望了解价格的时间范围\n2. 选择您所在的地区\n3. 选择鸡蛋品种\n\n系统将基于这些信息为您提供初步的价格估算。</text>
class="category-item {{evaluateData.breed === eggBreed.name ? 'selected' : ''}}" \n<text>请注意:\n• 此估价仅供参考,实际价格受市场波动影响\n• 建议咨询专业人士获取更准确的价格信息\n• 系统不支持真实交易,仅用于价格预估</text>
bindtap="selectEggBreed" data-breed="{{eggBreed.name}}">
<view class="category-info">
<view class="category-name">
<text class="rank-number rank-{{eggBreed.rank <= 3 ? eggBreed.rank : ''}}">{{eggBreed.rank}}</text>
{{eggBreed.name}}
</view> </view>
<view class="category-desc">点击选择该品种</view> <view class="know-btn-wrapper">
</view> <button class="know-btn" bindtap="onIKnow">我知道了</button>
<view class="category-arrow">›</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 步骤3:选择具体规格 --> <!-- 步骤2:选择参数 -->
<view wx:if="{{evaluateStep === 3}}" class="evaluate-step"> <view wx:if="{{currentStep === 2}}" class="evaluate-step">
<view class="step-content"> <view class="step-content">
<view class="step-title">选择具体规格</view> <button class="back-btn" bindtap="goBack">← 返回上一步</button>
<view class="step-subtitle">请选择鸡蛋的规格(按每日成交单量排序)</view> <view class="step-title">选择估价参数</view>
<view class="step-subtitle">请完成以下选择,系统将为您估价</view>
<view class="option-list"> <!-- 时间选择 -->
<view wx:for="{{eggSpecs}}" wx:key="name" wx:for-item="spec" <view class="selection-group">
class="option-item {{evaluateData.spec === spec.name ? 'selected' : ''}}" <view class="group-title">选择时间</view>
bindtap="selectEggSpec" data-spec="{{spec.name}}"> <view class="date-selector">
<view class="option-text"> <!-- 月份选择 -->
<text class="rank-number rank-{{spec.rank <= 3 ? spec.rank : ''}}">{{spec.rank}}</text> <picker mode="selector"
{{spec.name}} range="{{months}}"
</view> value="{{selectedMonthIndex}}"
<view class="option-arrow">›</view> bindchange="onMonthChange">
<view class="picker-wrapper small">
<view class="picker-display {{selectedMonth !== '' ? 'selected' : ''}}">
{{selectedMonth ? selectedMonth + '月' : '选择月份'}}
</view>
<view class="picker-arrow">▼</view>
</view>
</picker>
<!-- 日期选择 -->
<picker mode="selector"
range="{{days}}"
value="{{selectedDayIndex}}"
bindchange="onDayChange"
disabled="{{!selectedMonth}}">
<view class="picker-wrapper small {{!selectedMonth ? 'disabled' : ''}}">
<view class="picker-display {{selectedDay ? 'selected' : ''}}">
{{selectedDay ? selectedDay + '日' : '选择日期'}}
</view>
<view class="picker-arrow">▼</view>
</view>
</picker>
</view> </view>
</view> </view>
</view>
</view>
<!-- 步骤4:新鲜度选择 --> <!-- 地区选择 -->
<view wx:if="{{evaluateStep === 4}}" class="evaluate-step"> <view class="selection-group">
<view class="step-content"> <view class="group-title">选择地区</view>
<view class="step-title">新鲜程度</view> <picker mode="selector"
<view class="step-subtitle">请选择鸡蛋的新鲜程度</view> range="{{regionOptions}}"
value="{{selectedRegionIndex}}"
<view class="condition-list"> bindchange="onRegionChange">
<view class="condition-item {{evaluateData.freshness === '非常新鲜' ? 'selected' : ''}}" <view class="picker-wrapper">
bindtap="selectCondition" data-type="freshness" data-value="非常新鲜"> <view class="picker-display {{selectedRegion ? 'selected' : ''}}">
<view class="condition-info"> {{selectedRegion || '请选择地区'}}
<view class="condition-name">非常新鲜</view> </view>
<view class="condition-desc">7天内产出的新鲜鸡蛋</view> <view class="picker-arrow">▼</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.freshness === '非常新鲜'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.freshness === '较新鲜' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="freshness" data-value="较新鲜">
<view class="condition-info">
<view class="condition-name">较新鲜</view>
<view class="condition-desc">15天内产出的鸡蛋</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.freshness === '较新鲜'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.freshness === '一般' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="freshness" data-value="一般">
<view class="condition-info">
<view class="condition-name">一般</view>
<view class="condition-desc">30天内产出的鸡蛋</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.freshness === '一般'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.freshness === '不新鲜' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="freshness" data-value="不新鲜">
<view class="condition-info">
<view class="condition-name">不新鲜</view>
<view class="condition-desc">30天以上的鸡蛋</view>
</view> </view>
<view class="condition-check" wx:if="{{evaluateData.freshness === '不新鲜'}}">✓</view> </picker>
</view>
</view> </view>
</view>
</view>
<!-- 步骤5:大小选择 --> <!-- 品种选择 -->
<view wx:if="{{evaluateStep === 5}}" class="evaluate-step"> <view class="selection-group">
<view class="step-content"> <view class="group-title">选择品种</view>
<view class="step-title">鸡蛋大小</view> <picker mode="selector"
<view class="step-subtitle">请选择鸡蛋的大小规格</view> range="{{breedOptions}}"
value="{{selectedBreedIndex}}"
<view class="condition-list"> bindchange="onBreedChange">
<view class="condition-item {{evaluateData.size === '特大' ? 'selected' : ''}}" <view class="picker-wrapper">
bindtap="selectCondition" data-type="size" data-value="特大"> <view class="picker-display {{selectedBreed ? 'selected' : ''}}">
<view class="condition-info"> {{selectedBreed || '请选择品种'}}
<view class="condition-name">特大</view> </view>
<view class="condition-desc">单枚≥70g</view> <view class="picker-arrow">▼</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.size === '特大'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.size === '大' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="size" data-value="大">
<view class="condition-info">
<view class="condition-name">大</view>
<view class="condition-desc">单枚60-70g</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.size === '大'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.size === '中' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="size" data-value="中">
<view class="condition-info">
<view class="condition-name">中</view>
<view class="condition-desc">单枚50-60g</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.size === '中'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.size === '小' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="size" data-value="小">
<view class="condition-info">
<view class="condition-name">小</view>
<view class="condition-desc">单枚<50g</view>
</view> </view>
<view class="condition-check" wx:if="{{evaluateData.size === '小'}}">✓</view> </picker>
</view>
</view> </view>
</view>
</view>
<!-- 步骤6:包装情况 --> <!-- 开始估价按钮 -->
<view wx:if="{{evaluateStep === 6}}" class="evaluate-step"> <view class="evaluate-btn-wrapper">
<view class="step-content"> <button class="evaluate-btn {{selectedMonth && selectedDay && selectedRegion && selectedBreed ? 'active' : 'disabled'}}"
<view class="step-title">包装情况</view> bindtap="startEvaluation"
<view class="step-subtitle">请选择鸡蛋的包装完好程度</view> disabled="{{!selectedMonth || !selectedDay || !selectedRegion || !selectedBreed}}">
开始估价
<view class="condition-list"> </button>
<view class="condition-item {{evaluateData.packaging === '原装完整' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="packaging" data-value="原装完整">
<view class="condition-info">
<view class="condition-name">原装完整</view>
<view class="condition-desc">原包装完好无损</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.packaging === '原装完整'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.packaging === '部分包装' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="packaging" data-value="部分包装">
<view class="condition-info">
<view class="condition-name">部分包装</view>
<view class="condition-desc">包装有轻微破损</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.packaging === '部分包装'}}">✓</view>
</view>
<view class="condition-item {{evaluateData.packaging === '散装' ? 'selected' : ''}}"
bindtap="selectCondition" data-type="packaging" data-value="散装">
<view class="condition-info">
<view class="condition-name">散装</view>
<view class="condition-desc">无原包装</view>
</view>
<view class="condition-check" wx:if="{{evaluateData.packaging === '散装'}}">✓</view>
</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 步骤7:数量选择 -->
<view wx:if="{{evaluateStep === 7}}" class="evaluate-step">
<view class="step-content">
<view class="step-title">请选择数量</view>
<view class="step-subtitle">请选择鸡蛋的数量</view>
<view class="option-list">
<view class="option-item {{evaluateData.quantity === '500' ? 'selected' : ''}}" bindtap="selectQuantity" data-quantity="500">
<view class="option-text">500个</view>
<view class="option-arrow">›</view>
</view>
<view class="option-item {{evaluateData.quantity === '1000' ? 'selected' : ''}}" bindtap="selectQuantity" data-quantity="1000">
<view class="option-text">1000个</view>
<view class="option-arrow">›</view>
</view>
<view class="option-item {{evaluateData.quantity === '2000' ? 'selected' : ''}}" bindtap="selectQuantity" data-quantity="2000">
<view class="option-text">2000个</view>
<view class="option-arrow">›</view>
</view>
<view class="option-item {{evaluateData.quantity === '10000' ? 'selected' : ''}}" bindtap="selectQuantity" data-quantity="10000">
<view class="option-text">10000个</view>
<view class="option-arrow">›</view>
</view>
</view>
<!-- 获取报价按钮 --> <!-- 步骤3:估价结果 -->
<view class="get-price-section"> <view wx:if="{{currentStep === 3}}" class="evaluate-step">
<button class="get-price-btn" bindtap="getQuote">获取报价</button>
</view>
</view>
</view>
<!-- 步骤9:估价结果 -->
<view wx:if="{{evaluateStep === 9}}" class="evaluate-step result-step">
<view class="step-content"> <view class="step-content">
<button class="back-btn" bindtap="goBack">← 返回上一步</button>
<view class="result-header"> <view class="result-header">
<view class="result-icon">💰</view> <view class="result-icon">💰</view>
<view class="result-title">估价完成</view> <view class="result-title">估价完成</view>
<view class="result-subtitle">基于您选择的商品信息计算得出</view> <view class="result-subtitle">基于您的选择,系统为您提供以下估价</view>
<!-- 核心价格 -->
<view class="price-result">
<view class="price-label">预估总价</view>
<view class="price-amount">
<text class="price-symbol">¥</text>
<text class="price-number">{{evaluateResult.totalPrice}}</text>
</view>
<view class="price-unit">元({{evaluateData.quantity}}个)</view>
</view>
</view> </view>
<view class="result-content"> <!-- 核心价格 -->
<!-- AI报价信息 --> <view class="price-card">
<view class="ai-quotation" wx:if="{{evaluateResult.aidate}}"> <view class="price-label">预估价格</view>
<view class="ai-quotation-title"> <view class="price-amount">
<text class="ai-icon">🤖</text> <text class="price-symbol">¥</text>
AI智能报价 <text class="price-number">{{evaluateResult.estimatedPrice}}</text>
<text class="price-unit">元/斤</text>
</view>
<view class="price-range">价格区间:¥{{evaluateResult.priceRange}}</view>
</view>
<!-- 估价详情 -->
<view class="evaluation-details">
<view class="detail-title">估价详情</view>
<view class="detail-items">
<view class="detail-item">
<view class="detail-label">时间</view>
<view class="detail-value">{{selectedMonth}}月{{selectedDay}}日</view>
</view> </view>
<view class="ai-quotation-content">{{evaluateResult.aidate}}</view> <view class="detail-item">
</view> <view class="detail-label">地区</view>
<view class="detail-value">{{selectedRegion}}</view>
<!-- 商品信息 -->
<view class="product-info-card">
<view class="product-type">{{evaluateData.breed}}</view>
<view class="product-details">
<view class="product-spec">{{evaluateData.spec}}</view>
</view> </view>
</view> <view class="detail-item">
<view class="detail-label">品种</view>
<!-- 商品状况 --> <view class="detail-value">{{selectedBreed}}</view>
<view class="condition-summary"> </view>
<view class="summary-title">商品状况</view> <view class="detail-item">
<view class="condition-items"> <view class="detail-label">可信度</view>
<view class="condition-item"> <view class="detail-value">{{evaluateResult.confidence}}</view>
<view class="condition-label">新鲜度</view>
<view class="condition-value">{{evaluateData.freshness}}</view>
</view>
<view class="condition-item">
<view class="condition-label">大小</view>
<view class="condition-value">{{evaluateData.size}}</view>
</view>
<view class="condition-item">
<view class="condition-label">包装</view>
<view class="condition-value">{{evaluateData.packaging}}</view>
</view>
<view class="condition-item">
<view class="condition-label">规格</view>
<view class="condition-value">{{evaluateData.spec}}</view>
</view>
<view class="condition-item">
<view class="condition-label">数量</view>
<view class="condition-value">{{evaluateData.quantity}}个</view>
</view>
<view class="condition-item">
<view class="condition-label">单价</view>
<view class="condition-value">{{evaluateResult.finalPrice}}元/斤</view>
</view>
</view> </view>
</view> </view>
</view>
<!-- 价格说明 -->
<view class="price-tips"> <!-- 说明文字 -->
<text class="tip-icon">💡</text> <view class="price-tips">
<text class="tip-text">此价格仅供参考,实际成交价可能因市场波动有所差异</text> <text class="tip-icon">💡</text>
</view> <text class="tip-text">此价格为系统初步估算,仅供参考。实际成交价格可能因市场供需、季节因素、品质差异等有所变动。建议结合多个渠道信息做出决策。</text>
</view> </view>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="result-actions"> <view class="result-actions">
<button class="btn-secondary" bindtap="goToBuy">立即购买</button> <button class="btn-secondary" bindtap="resetEvaluation">重新估价</button>
<button class="btn-primary" bindtap="goToSell">即刻上架</button> <button class="btn-primary" bindtap="goToMarket">查看市场</button>
</view> </view>
</view> </view>
</view> </view>

355
pages/evaluate/index.wxss

@ -145,6 +145,28 @@
color: #FF6B00; 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 { .category-list {
background: white; background: white;
@ -787,4 +809,337 @@
.btn-secondary::after, .btn-primary::after { .btn-secondary::after, .btn-primary::after {
content: none; 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;
} }

290
server-example/server-mysql.js

@ -1032,6 +1032,46 @@ UserManagement.init({
timestamps: false 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(() => { updateProductContacts().then(() => {
console.log('\n📦 商品联系人信息更新完成!'); console.log('\n📦 商品联系人信息更新完成!');
@ -8671,6 +8935,31 @@ updateProductContacts().then(() => {
startConnectionMonitoring(); startConnectionMonitoring();
console.log('连接监控服务已启动'); 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', () => { server.listen(PORT, '0.0.0.0', () => {
console.log(`\n🚀 服务器启动成功,监听端口 ${PORT}`); console.log(`\n🚀 服务器启动成功,监听端口 ${PORT}`);
console.log(`API 服务地址: http://localhost:${PORT}`); console.log(`API 服务地址: http://localhost:${PORT}`);
@ -8686,6 +8975,7 @@ module.exports = {
User, User,
Product, Product,
CartItem, CartItem,
Resources,
sequelize, sequelize,
createUserAssociations, createUserAssociations,
app, app,

29
utils/api.js

@ -3936,6 +3936,35 @@ module.exports = {
resolve(['全部', '粉壳', '褐壳', '绿壳', '白壳']); 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);
});
});
} }
}; };
Loading…
Cancel
Save