Browse Source

feat: 优化价格波动显示和价格变化箭头

蛋吧eggbar
Trae AI 1 month ago
parent
commit
60c2113a0e
  1. 78
      pages/goods-detail/goods-detail.js
  2. 4
      pages/goods-detail/goods-detail.wxml
  3. 32
      pages/goods-detail/goods-detail.wxss
  4. 49
      pages/index/index.js
  5. 1
      pages/index/index.wxml
  6. 23
      pages/index/index.wxss
  7. 4
      pages/order/detail/index.wxml
  8. 2
      project.private.config.json
  9. 31
      server-example/server-mysql.js

78
pages/goods-detail/goods-detail.js

@ -339,7 +339,7 @@ function formatDateTime(dateString) {
} }
// 处理净重、件数、规格和价格数据,将逗号分隔的字符串转换为一一对应的数组 // 处理净重、件数、规格和价格数据,将逗号分隔的字符串转换为一一对应的数组
function processWeightAndQuantityData(weightSpecString, quantityString, specString, priceString, specStatusString) { function processWeightAndQuantityData(weightSpecString, quantityString, specString, priceString, specStatusString, specPriceChanges = {}) {
console.log('===== 处理净重、件数、规格和价格数据 ====='); console.log('===== 处理净重、件数、规格和价格数据 =====');
console.log('输入参数:'); console.log('输入参数:');
console.log('- weightSpecString:', weightSpecString, '(类型:', typeof weightSpecString, ')'); console.log('- weightSpecString:', weightSpecString, '(类型:', typeof weightSpecString, ')');
@ -450,13 +450,23 @@ function processWeightAndQuantityData(weightSpecString, quantityString, specStri
console.log(`${i}组数据处理结果: weightSpecDisplay=${weightSpecDisplay}, quantity=${quantity}, price=${price}, specStatus=${specStatus}, display=${display}`); console.log(`${i}组数据处理结果: weightSpecDisplay=${weightSpecDisplay}, quantity=${quantity}, price=${price}, specStatus=${specStatus}, display=${display}`);
// 检查该规格是否有价格变化
let priceChangeDirection = null;
for (const spec in specPriceChanges) {
if (weightSpecDisplay.includes(spec)) {
priceChangeDirection = specPriceChanges[spec];
break;
}
}
result.push({ result.push({
weightSpec: weightSpecDisplay, weightSpec: weightSpecDisplay,
quantity: quantity, quantity: quantity,
price: price, price: price,
specStatus: specStatus, specStatus: specStatus,
display: display, display: display,
isOffShelf: specStatus === '1' isOffShelf: specStatus === '1',
priceChangeDirection: priceChangeDirection
}); });
} }
@ -1286,6 +1296,64 @@ Page({
// 处理grossWeight为null或无效的情况,返回空字符串以支持文字输入 // 处理grossWeight为null或无效的情况,返回空字符串以支持文字输入
const grossWeightValue = product.grossWeight !== null && product.grossWeight !== undefined ? product.grossWeight : ''; const grossWeightValue = product.grossWeight !== null && product.grossWeight !== undefined ? product.grossWeight : '';
// 处理价格波动,计算价格变化的方向
const calculatePriceChangeDirection = (productLog) => {
if (!productLog || !Array.isArray(productLog) || productLog.length === 0) {
return {};
}
// 按规格分组日志
const specLogs = {};
productLog.forEach(log => {
if (typeof log === 'string') {
// 提取规格信息
const specMatch = log.match(/将(.+?)规格的销售价格/);
if (specMatch && specMatch[1]) {
const spec = specMatch[1].trim();
if (!specLogs[spec]) {
specLogs[spec] = [];
}
specLogs[spec].push(log);
}
}
});
// 检查每个规格的价格变化
const specPriceChanges = {};
for (const spec in specLogs) {
const logs = specLogs[spec];
if (logs.length >= 2) {
// 提取最近两次的价格
const latestLog = logs[logs.length - 1];
const previousLog = logs[logs.length - 2];
// 提取价格
const latestPriceMatch = latestLog.match(/修改为了(.*?)元/);
const previousPriceMatch = previousLog.match(/修改为了(.*?)元/);
if (latestPriceMatch && previousPriceMatch) {
const latestPrice = parseFloat(latestPriceMatch[1]);
const previousPrice = parseFloat(previousPriceMatch[1]);
if (!isNaN(latestPrice) && !isNaN(previousPrice)) {
if (latestPrice > previousPrice) {
specPriceChanges[spec] = 'up'; // 价格上升
} else if (latestPrice < previousPrice) {
specPriceChanges[spec] = 'down'; // 价格下降
}
}
}
}
}
return specPriceChanges; // 返回每个规格的价格变化方向
};
// 检查所有可能的产品日志字段名
const productLog = product.product_log || product.productLog || [];
// 计算每个规格的价格变化方向
const specPriceChanges = calculatePriceChangeDirection(productLog);
// 处理净重、件数据和规格数据,获取一一对应的显示数组 // 处理净重、件数据和规格数据,获取一一对应的显示数组
// 注意:数据库中的规格字段包含净重信息,我们需要与件数数据正确匹配 // 注意:数据库中的规格字段包含净重信息,我们需要与件数数据正确匹配
// 修复:根据用户反馈,数据库中的规格字段内容为:净重46-47,净重44-43,净重47-48 // 修复:根据用户反馈,数据库中的规格字段内容为:净重46-47,净重44-43,净重47-48
@ -1415,7 +1483,7 @@ Page({
if (product.spec_status) { if (product.spec_status) {
specStatusString = String(product.spec_status); specStatusString = String(product.spec_status);
} }
weightQuantityData = processWeightAndQuantityData(weightSpecString, quantityString, '', priceString, specStatusString); weightQuantityData = processWeightAndQuantityData(weightSpecString, quantityString, '', priceString, specStatusString, specPriceChanges);
console.log('× 非售空分支结果:', weightQuantityData); console.log('× 非售空分支结果:', weightQuantityData);
} }
@ -1539,7 +1607,9 @@ Page({
imageUrls: imageUrls || [], imageUrls: imageUrls || [],
mediaItems: mediaItems, mediaItems: mediaItems,
// 确保region使用提取后的省份信息,放在最后覆盖所有展开操作 // 确保region使用提取后的省份信息,放在最后覆盖所有展开操作
region: region region: region,
// 添加价格波动信息
specPriceChanges: specPriceChanges
}; };
// 调试:打印formattedGoods的imageUrls和mediaItems // 调试:打印formattedGoods的imageUrls和mediaItems

4
pages/goods-detail/goods-detail.wxml

@ -94,6 +94,8 @@
<view style="display: flex; align-items: center;"> <view style="display: flex; align-items: center;">
<text class="price-symbol">价格:</text> <text class="price-symbol">价格:</text>
<text class="price-value">{{goodsDetail.displayPrice}}</text> <text class="price-value">{{goodsDetail.displayPrice}}</text>
<text wx:if="{{goodsDetail.weightQuantityData && goodsDetail.weightQuantityData[0] && goodsDetail.weightQuantityData[0].priceChangeDirection === 'up'}}" class="price-change-arrow up">↑</text>
<text wx:elif="{{goodsDetail.weightQuantityData && goodsDetail.weightQuantityData[0] && goodsDetail.weightQuantityData[0].priceChangeDirection === 'down'}}" class="price-change-arrow down">↓</text>
<text style="margin-left: 10rpx; color: #333; font-size: 32rpx;">(可议价)</text> <text style="margin-left: 10rpx; color: #333; font-size: 32rpx;">(可议价)</text>
</view> </view>
<view <view
@ -124,6 +126,8 @@
<view class="wq-item"> <view class="wq-item">
<!-- 使用display字段显示完整信息,包括已下架标记 --> <!-- 使用display字段显示完整信息,包括已下架标记 -->
<text class="wq-text">{{item.display}}</text> <text class="wq-text">{{item.display}}</text>
<text wx:if="{{item.priceChangeDirection === 'up'}}" class="price-change-arrow up">↑</text>
<text wx:elif="{{item.priceChangeDirection === 'down'}}" class="price-change-arrow down">↓</text>
<text wx:if="{{item.isOffShelf}}" style="color: red; margin-left: 20rpx;">已下架</text> <text wx:if="{{item.isOffShelf}}" style="color: red; margin-left: 20rpx;">已下架</text>
</view> </view>
</block> </block>

32
pages/goods-detail/goods-detail.wxss

@ -784,6 +784,38 @@ video.slider-media .wx-video-volume-icon {
margin-left: 10rpx; margin-left: 10rpx;
} }
/* 价格变化箭头样式 */
.price-change-arrow {
margin-left: 8rpx;
font-size: 32rpx;
font-weight: bold;
}
.price-change-arrow.up {
color: #ff4d4f;
animation: pulse 2s infinite;
}
.price-change-arrow.down {
color: #52c41a;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.8;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* 对比价格弹窗样式 */ /* 对比价格弹窗样式 */
.compare-modal-overlay { .compare-modal-overlay {
position: fixed; position: fixed;

49
pages/index/index.js

@ -1073,6 +1073,50 @@ Page({
// 格式化规格显示 // 格式化规格显示
const formattedSpec = this.formatSpecification(product.specification || product.spec || '', product.yolk || ''); const formattedSpec = this.formatSpecification(product.specification || product.spec || '', product.yolk || '');
// 处理价格波动
const processPriceChange = (productLog) => {
if (!productLog || !Array.isArray(productLog) || productLog.length === 0) {
return {
hasPriceChange: false,
priceChangeDirection: null
};
}
// 检查是否有同规格的第二次日志
const specLogs = {};
productLog.forEach(log => {
if (typeof log === 'string') {
// 提取规格信息
const specMatch = log.match(/将(.+?)规格的销售价格/);
if (specMatch && specMatch[1]) {
const spec = specMatch[1].trim();
if (!specLogs[spec]) {
specLogs[spec] = [];
}
specLogs[spec].push(log);
}
}
});
// 检查是否有规格有多次价格调整
let hasPriceChange = false;
for (const spec in specLogs) {
if (specLogs[spec].length >= 2) {
hasPriceChange = true;
break;
}
}
return {
hasPriceChange,
priceChangeDirection: null // 默认为null,在商品详情页中计算具体方向
};
};
// 检查所有可能的产品日志字段名
const productLog = product.product_log || product.productLog || [];
const priceChangeInfo = processPriceChange(productLog);
return { return {
...product, ...product,
id: productId, // 统一使用id字段 id: productId, // 统一使用id字段
@ -1102,7 +1146,10 @@ Page({
originalTotalStock: totalStock, // 保留原始计算值用于调试 originalTotalStock: totalStock, // 保留原始计算值用于调试
displaySpecification: formattedSpec.displaySpec, // 格式化后的规格 displaySpecification: formattedSpec.displaySpec, // 格式化后的规格
displayYolk: formattedSpec.displayYolk, // 格式化后的蛋黄 displayYolk: formattedSpec.displayYolk, // 格式化后的蛋黄
price: product.price !== undefined ? product.price : '' // 确保price字段存在 price: product.price !== undefined ? product.price : '', // 确保price字段存在
// 添加价格波动信息
hasPriceChange: priceChangeInfo.hasPriceChange,
priceChangeDirection: priceChangeInfo.priceChangeDirection
} }
}) })

1
pages/index/index.wxml

@ -323,6 +323,7 @@
<view class="product-stock-row"> <view class="product-stock-row">
<view wx:if="{{item.status !== 'sold_out'}}" class="status-tag item-count">库存:{{item.totalStock && item.totalStock !== '充足' ? item.totalStock + '件' : (item.totalStock || '充足')}}</view> <view wx:if="{{item.status !== 'sold_out'}}" class="status-tag item-count">库存:{{item.totalStock && item.totalStock !== '充足' ? item.totalStock + '件' : (item.totalStock || '充足')}}</view>
<view wx:if="{{item.status === 'sold_out'}}" class="status-tag item-count">已售:{{item.originalTotalStock || 0}}件</view> <view wx:if="{{item.status === 'sold_out'}}" class="status-tag item-count">已售:{{item.originalTotalStock || 0}}件</view>
<view wx:if="{{item.hasPriceChange}}" class="status-tag price-change">价格有波动</view>
</view> </view>
<view class="product-meta"> <view class="product-meta">
<text class="sales-count">已有{{item.frequency || 0}}人浏览</text> <text class="sales-count">已有{{item.frequency || 0}}人浏览</text>

23
pages/index/index.wxss

@ -1279,6 +1279,29 @@ wx-button:not([size=mini]) {
border: 1rpx solid rgba(82, 196, 26, 0.5); border: 1rpx solid rgba(82, 196, 26, 0.5);
} }
.status-tag.price-change {
background: transparent;
color: #ff4d4f;
border: none;
font-weight: bold;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.05);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* product-status-row 容器样式 */ /* product-status-row 容器样式 */
.product-status-row { .product-status-row {
display: flex; display: flex;

4
pages/order/detail/index.wxml

@ -100,11 +100,11 @@
<!-- 二维码 --> <!-- 二维码 -->
<view class="section"> <view class="section">
<view class="section-title">二维码</view> <view class="section-title">产品合格证</view>
<view class="qrcode-container"> <view class="qrcode-container">
<image wx:if="{{orderDetail.QR_code}}" src="{{orderDetail.QR_code}}" class="qrcode-image" mode="aspectFit"></image> <image wx:if="{{orderDetail.QR_code}}" src="{{orderDetail.QR_code}}" class="qrcode-image" mode="aspectFit"></image>
<view wx:else class="qrcode-placeholder"> <view wx:else class="qrcode-placeholder">
<text>二维码暂无</text> <text>暂无产品合格证</text>
<text style="font-size: 20rpx; color: #999;">QR_code: {{orderDetail.QR_code}}</text> <text style="font-size: 20rpx; color: #999;">QR_code: {{orderDetail.QR_code}}</text>
</view> </view>
</view> </view>

2
project.private.config.json

@ -1,6 +1,6 @@
{ {
"libVersion": "3.10.3", "libVersion": "3.10.3",
"projectname": "wechatapp", "projectname": "Mini-Program",
"setting": { "setting": {
"urlCheck": false, "urlCheck": false,
"coverView": true, "coverView": true,

31
server-example/server-mysql.js

@ -3420,6 +3420,7 @@ app.post('/api/product/list', async (req, res) => {
'producting', 'producting',
'description', 'description',
'frequency', 'frequency',
'product_log',
'spec_status' 'spec_status'
], ],
order: [['created_at', 'DESC']], order: [['created_at', 'DESC']],
@ -3548,6 +3549,35 @@ app.post('/api/product/list', async (req, res) => {
productJSON.created_at = getBeijingTimeISOString(); productJSON.created_at = getBeijingTimeISOString();
} }
// 处理产品日志字段,确保返回数组格式
if (productJSON.product_log) {
console.log('【产品日志】原始product_log:', productJSON.product_log, '类型:', typeof productJSON.product_log);
if (typeof productJSON.product_log === 'string') {
try {
productJSON.product_log = JSON.parse(productJSON.product_log);
console.log('【产品日志】反序列化后的product_log:', productJSON.product_log, '类型:', typeof productJSON.product_log);
// 确保是数组格式
if (!Array.isArray(productJSON.product_log)) {
productJSON.product_log = [productJSON.product_log];
console.log('【产品日志】转换为数组:', productJSON.product_log);
}
} catch (parseError) {
console.error('【产品日志】反序列化失败:', parseError);
// 如果解析失败,将字符串作为单个日志条目
productJSON.product_log = [productJSON.product_log];
console.log('【产品日志】转换为单条日志:', productJSON.product_log);
}
} else if (!Array.isArray(productJSON.product_log)) {
// 如果不是字符串也不是数组,转换为数组
productJSON.product_log = [productJSON.product_log];
console.log('【产品日志】转换为数组:', productJSON.product_log);
}
} else {
// 如果没有日志,返回空数组
productJSON.product_log = [];
console.log('【产品日志】没有日志,返回空数组');
}
// 记录第一个商品的转换信息用于调试 // 记录第一个商品的转换信息用于调试
if (products.indexOf(product) === 0) { if (products.indexOf(product) === 0) {
console.log('商品列表 - 第一个商品毛重字段处理:'); console.log('商品列表 - 第一个商品毛重字段处理:');
@ -3555,6 +3585,7 @@ app.post('/api/product/list', async (req, res) => {
console.log('- 转换后的值:', productJSON.grossWeight, '类型:', typeof productJSON.grossWeight); console.log('- 转换后的值:', productJSON.grossWeight, '类型:', typeof productJSON.grossWeight);
console.log('- reservedCount值:', productJSON.reservedCount, '类型:', typeof productJSON.reservedCount); console.log('- reservedCount值:', productJSON.reservedCount, '类型:', typeof productJSON.reservedCount);
console.log('- seller信息:', JSON.stringify(productJSON.seller)); console.log('- seller信息:', JSON.stringify(productJSON.seller));
console.log('- product_log信息:', JSON.stringify(productJSON.product_log));
} }
return productJSON; return productJSON;

Loading…
Cancel
Save