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个 - - - - - - - - - - - - + + + 💰 估价完成 - 基于您选择的商品信息计算得出 - - - - 预估总价 - - ¥ - {{evaluateResult.totalPrice}} - - 元({{evaluateData.quantity}}个) - + 基于您的选择,系统为您提供以下估价 - - - - - 🤖 - 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