15 changed files with 1122 additions and 5 deletions
@ -0,0 +1,237 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productName: '', |
||||
|
specifications: [], |
||||
|
loading: false, |
||||
|
error: '' |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
let productName = ''; |
||||
|
// 首先检查URL参数
|
||||
|
if (options.productName) { |
||||
|
productName = options.productName; |
||||
|
} else { |
||||
|
// 然后检查本地存储(用于wx.switchTab导航)
|
||||
|
productName = wx.getStorageSync('selectedProductName') || ''; |
||||
|
// 清除本地存储中的商品名称,避免影响下次进入
|
||||
|
if (productName) { |
||||
|
wx.removeStorageSync('selectedProductName'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (productName) { |
||||
|
this.setData({ productName: productName }); |
||||
|
this.loadSpecifications(productName); |
||||
|
} else { |
||||
|
// 如果没有商品名称参数,跳转到商品列表页面
|
||||
|
wx.redirectTo({ |
||||
|
url: '/pages/evaluate1/product-list' |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
loadSpecifications(productName) { |
||||
|
this.setData({ loading: true, specifications: [] }); |
||||
|
|
||||
|
// 直接从本地存储获取商品数据,避免重复请求
|
||||
|
const localGoods = wx.getStorageSync('goods') || []; |
||||
|
console.log('从本地存储获取的商品数量:', localGoods.length); |
||||
|
|
||||
|
if (localGoods.length > 0) { |
||||
|
this.processSpecifications(productName, localGoods); |
||||
|
} else { |
||||
|
// 如果本地没有数据,再请求服务器
|
||||
|
const api = require('../../utils/api'); |
||||
|
api.getProducts().then(products => { |
||||
|
this.processSpecifications(productName, products); |
||||
|
}).catch(err => { |
||||
|
console.error('获取规格失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取规格失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 解析规格,提取类型(净重/毛重)和数值范围
|
||||
|
parseSpecification(spec) { |
||||
|
const weightMatch = spec.match(/(净重|毛重)(\d+)-(\d+)/); |
||||
|
if (weightMatch) { |
||||
|
const type = weightMatch[1]; // 净重或毛重
|
||||
|
const min = parseFloat(weightMatch[2]); |
||||
|
const max = parseFloat(weightMatch[3]); |
||||
|
const avg = (min + max) / 2; |
||||
|
return { |
||||
|
type: type, |
||||
|
min: min, |
||||
|
max: max, |
||||
|
avg: avg |
||||
|
}; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
|
||||
|
processSpecifications(productName, products) { |
||||
|
console.log('处理的商品数据数量:', products.length); |
||||
|
console.log('当前处理的商品名称:', productName); |
||||
|
|
||||
|
// 检查products是否为空
|
||||
|
if (!products || products.length === 0) { |
||||
|
console.error('商品数据为空'); |
||||
|
this.setData({ |
||||
|
error: '商品数据为空', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 过滤出当前商品名称的所有商品
|
||||
|
const filteredProducts = products.filter(product => { |
||||
|
const match = product.productName === productName; |
||||
|
console.log('商品:', product.productName, '规格:', product.specification, '价格:', product.price, '匹配:', match); |
||||
|
return match; |
||||
|
}); |
||||
|
|
||||
|
console.log('过滤后的商品数量:', filteredProducts.length); |
||||
|
console.log('过滤后的商品详情:', filteredProducts); |
||||
|
|
||||
|
// 检查filteredProducts是否为空
|
||||
|
if (filteredProducts.length === 0) { |
||||
|
console.error('未找到对应商品名称的商品'); |
||||
|
this.setData({ |
||||
|
error: '未找到对应商品名称的商品', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 提取规格和价格,处理可能的空值和空格
|
||||
|
const specPriceMap = {}; |
||||
|
filteredProducts.forEach((product, productIndex) => { |
||||
|
const specStr = (product.specification || product.spec || '').trim(); |
||||
|
const price = product.price || ''; |
||||
|
|
||||
|
console.log(`处理第${productIndex + 1}个商品: 规格字符串='${specStr}', 价格字符串='${price}'`); |
||||
|
|
||||
|
if (specStr.length > 0) { |
||||
|
// 处理逗号分隔的多个规格,确保每个规格都被正确分割
|
||||
|
// 首先按逗号分割
|
||||
|
let specs = specStr.split(',').map(spec => spec.trim()).filter(spec => spec.length > 0); |
||||
|
|
||||
|
// 进一步处理规格,确保每个规格都是独立的
|
||||
|
const processedSpecs = []; |
||||
|
specs.forEach(spec => { |
||||
|
// 检查规格是否包含多个规格(例如:"净重29-30,净重31-32")
|
||||
|
if (spec.includes(',')) { |
||||
|
// 按中文逗号分割
|
||||
|
const subSpecs = spec.split(',').map(s => s.trim()).filter(s => s.length > 0); |
||||
|
processedSpecs.push(...subSpecs); |
||||
|
} else { |
||||
|
processedSpecs.push(spec); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
specs = processedSpecs; |
||||
|
|
||||
|
// 处理逗号分隔的多个价格
|
||||
|
const prices = (price || '').split(',').map(p => p.trim()); |
||||
|
|
||||
|
console.log(`规格数组:`, specs); |
||||
|
console.log(`价格数组:`, prices); |
||||
|
|
||||
|
// 将规格和价格配对
|
||||
|
specs.forEach((spec, index) => { |
||||
|
if (spec.length > 0) { |
||||
|
// 确保价格索引不超出范围
|
||||
|
const priceIndex = index % prices.length; |
||||
|
const matchedPrice = prices[priceIndex] || ''; |
||||
|
console.log(`规格'${spec}' 配对价格: '${matchedPrice}'`); |
||||
|
|
||||
|
// 只有当价格不为空时才添加该规格
|
||||
|
if (matchedPrice) { |
||||
|
// 直接使用商品的原始价格,不做任何处理
|
||||
|
specPriceMap[spec] = matchedPrice; |
||||
|
} else { |
||||
|
console.log(`规格'${spec}' 价格为空,不添加`); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
console.log('规格价格映射:', specPriceMap); |
||||
|
|
||||
|
// 检查specPriceMap是否为空
|
||||
|
if (Object.keys(specPriceMap).length === 0) { |
||||
|
console.error('未提取到有效规格'); |
||||
|
this.setData({ |
||||
|
error: '未提取到有效规格', |
||||
|
loading: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 转换为规格对象数组
|
||||
|
const specifications = Object.keys(specPriceMap).map(spec => { |
||||
|
const price = specPriceMap[spec]; |
||||
|
console.log(`最终规格'${spec}' 对应价格: '${price}'`); |
||||
|
|
||||
|
// 解析规格
|
||||
|
const specInfo = this.parseSpecification(spec); |
||||
|
let finalPrice = price; |
||||
|
let finalPriceText = price; |
||||
|
|
||||
|
// 根据规格类型和价格水平计算最终价格
|
||||
|
if (specInfo && parseFloat(price) < 10) { |
||||
|
if (specInfo.type === '净重') { |
||||
|
// 净重:规格平均值 × 价格
|
||||
|
finalPrice = specInfo.avg * parseFloat(price); |
||||
|
finalPriceText = finalPrice.toFixed(2); |
||||
|
console.log(`规格'${spec}' 是净重,计算最终价格: ${finalPriceText}`); |
||||
|
} else if (specInfo.type === '毛重') { |
||||
|
// 毛重:(规格平均值 - 5) × 价格
|
||||
|
finalPrice = (specInfo.avg - 5) * parseFloat(price); |
||||
|
finalPriceText = finalPrice.toFixed(2); |
||||
|
console.log(`规格'${spec}' 是毛重,计算最终价格: ${finalPriceText}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
name: spec, |
||||
|
price: price, |
||||
|
priceText: price, |
||||
|
finalPrice: finalPrice, |
||||
|
finalPriceText: finalPriceText |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
console.log('提取的规格和价格:', specifications); |
||||
|
|
||||
|
this.setData({ |
||||
|
specifications: specifications, |
||||
|
error: '', // 清除之前的错误
|
||||
|
loading: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到规格详情页面
|
||||
|
goToSpecDetail(e) { |
||||
|
const specItem = e.currentTarget.dataset.spec; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/evaluate1/spec-detail?productName=${encodeURIComponent(this.data.productName)}&specification=${encodeURIComponent(specItem.name)}&price=${encodeURIComponent(specItem.finalPrice)}` |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack(); |
||||
|
}, |
||||
|
|
||||
|
// 返回商品列表页面
|
||||
|
goBackToProductList() { |
||||
|
wx.redirectTo({ |
||||
|
url: '/pages/evaluate1/product-list' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "估价", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
<view class="container"> |
||||
|
<view class="header"> |
||||
|
<text class="title">选择规格</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<!-- 加载中状态 --> |
||||
|
<view wx:if="{{loading}}" class="loading"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view wx:if="{{error}}" class="error"> |
||||
|
<view class="error-icon">⚠️</view> |
||||
|
<text>{{error}}</text> |
||||
|
<button bindtap="loadSpecifications" data-product="{{productName}}" type="primary" class="retry-button">重试</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品信息 --> |
||||
|
<view wx:else class="product-info"> |
||||
|
<text class="product-label">商品名称:</text> |
||||
|
<text class="product-value">{{productName}}</text> |
||||
|
<button bindtap="goBackToProductList" type="default" class="back-button">返回上一步</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 规格列表 --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length > 0}}" class="spec-list"> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">可用规格</text> |
||||
|
<text class="section-count">{{specifications.length}}个</text> |
||||
|
</view> |
||||
|
<view class="spec-items"> |
||||
|
<view |
||||
|
wx:for="{{specifications}}" |
||||
|
wx:key="item.name" |
||||
|
class="spec-item" |
||||
|
data-spec="{{item}}" |
||||
|
bindtap="goToSpecDetail" |
||||
|
> |
||||
|
<view class="spec-info"> |
||||
|
<text class="spec-name">{{item.name}}</text> |
||||
|
</view> |
||||
|
<view class="spec-price-arrow"> |
||||
|
<text class="spec-price">¥{{item.finalPriceText}}</text> |
||||
|
<view class="spec-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无规格提示 --> |
||||
|
<view wx:if="{{!loading && !error && specifications.length === 0}}" class="hint"> |
||||
|
<view class="hint-icon">📋</view> |
||||
|
<text>该商品暂无可用规格</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,285 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
min-height: 100vh; |
||||
|
background-color: #f5f5f5; |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
background-color: #fff; |
||||
|
border-bottom: 1rpx solid #eee; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-info { |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: flex-start; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.back-button { |
||||
|
width: 100%; |
||||
|
height: 70rpx; |
||||
|
line-height: 70rpx; |
||||
|
font-size: 28rpx; |
||||
|
border-radius: 35rpx; |
||||
|
margin-top: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.product-label { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-value { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FF6B81; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 100rpx 0; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border: 6rpx solid #f3f3f3; |
||||
|
border-top: 6rpx solid #FF6B81; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
.error { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 40rpx; |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 60rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.error text { |
||||
|
display: block; |
||||
|
color: #ff4d4f; |
||||
|
margin-bottom: 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.retry-button { |
||||
|
width: 200rpx; |
||||
|
height: 70rpx; |
||||
|
line-height: 70rpx; |
||||
|
font-size: 28rpx; |
||||
|
border-radius: 35rpx; |
||||
|
background-color: #FF6B81; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
padding-left: 15rpx; |
||||
|
border-left: 5rpx solid #FF6B81; |
||||
|
} |
||||
|
|
||||
|
.section-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background-color: #f5f5f5; |
||||
|
padding: 5rpx 15rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-list, |
||||
|
.spec-list { |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); |
||||
|
} |
||||
|
|
||||
|
.product-items, |
||||
|
.spec-items { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #f9f9f9; |
||||
|
border: 1rpx solid #eee; |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.spec-item { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: flex-start; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #f9f9f9; |
||||
|
border: 1rpx solid #eee; |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
flex-direction: column; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.spec-price-arrow { |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
gap: 10rpx; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.product-item:hover, |
||||
|
.spec-item:hover { |
||||
|
background-color: #f0f0f0; |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1); |
||||
|
} |
||||
|
|
||||
|
.product-item.selected { |
||||
|
background-color: #FFE6E6; |
||||
|
border-color: #FF6B81; |
||||
|
color: #FF6B81; |
||||
|
} |
||||
|
|
||||
|
.product-name, |
||||
|
.spec-name { |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.spec-price { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FF6B81; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item.selected .product-name { |
||||
|
color: #FF6B81; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.product-arrow, |
||||
|
.spec-arrow { |
||||
|
font-size: 30rpx; |
||||
|
color: #999; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item.selected .product-arrow { |
||||
|
color: #FF6B81; |
||||
|
} |
||||
|
|
||||
|
.hint { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 100rpx 0; |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.hint-icon { |
||||
|
font-size: 80rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 375px) { |
||||
|
.content { |
||||
|
padding: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.product-list, |
||||
|
.spec-list { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item, |
||||
|
.spec-item { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name, |
||||
|
.spec-name { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productNames: [], |
||||
|
loading: false, |
||||
|
error: '' |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
this.loadProductNames(); |
||||
|
}, |
||||
|
|
||||
|
loadProductNames() { |
||||
|
this.setData({ loading: true, error: '' }); |
||||
|
|
||||
|
const api = require('../../utils/api'); |
||||
|
api.getProducts().then(products => { |
||||
|
// 提取唯一的productName
|
||||
|
const uniqueProductNames = [...new Set(products.map(product => product.productName).filter(Boolean))]; |
||||
|
this.setData({ |
||||
|
productNames: uniqueProductNames, |
||||
|
loading: false |
||||
|
}); |
||||
|
}).catch(err => { |
||||
|
console.error('获取商品列表失败:', err); |
||||
|
this.setData({ |
||||
|
error: '获取商品列表失败,请稍后重试', |
||||
|
loading: false |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
selectProduct(e) { |
||||
|
const productName = e.currentTarget.dataset.product; |
||||
|
console.log('选择商品:', productName); |
||||
|
// 将商品名称存储到本地存储,因为wx.switchTab不支持URL参数
|
||||
|
wx.setStorageSync('selectedProductName', productName); |
||||
|
// 使用wx.switchTab导航到tabBar页面
|
||||
|
wx.switchTab({ |
||||
|
url: '/pages/evaluate1/index', |
||||
|
success: function(res) { |
||||
|
console.log('跳转成功:', res); |
||||
|
}, |
||||
|
fail: function(err) { |
||||
|
console.error('跳转失败:', err); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "选择商品", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
<view class="container"> |
||||
|
<view class="header"> |
||||
|
<text class="title">选择商品</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<!-- 加载中状态 --> |
||||
|
<view wx:if="{{loading}}" class="loading"> |
||||
|
<view class="loading-spinner"></view> |
||||
|
<text>加载中...</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 错误提示 --> |
||||
|
<view wx:if="{{error}}" class="error"> |
||||
|
<view class="error-icon">⚠️</view> |
||||
|
<text>{{error}}</text> |
||||
|
<button bindtap="loadProductNames" type="primary" class="retry-button">重试</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 商品名称列表 --> |
||||
|
<view wx:else class="product-list"> |
||||
|
<view class="section-header"> |
||||
|
<text class="section-title">商品名称</text> |
||||
|
<text class="section-count">{{productNames.length}}个</text> |
||||
|
</view> |
||||
|
<view class="product-items"> |
||||
|
<view |
||||
|
wx:for="{{productNames}}" |
||||
|
wx:key="*this" |
||||
|
class="product-item" |
||||
|
data-product="{{item}}" |
||||
|
bindtap="selectProduct" |
||||
|
> |
||||
|
<text class="product-name">{{item}}</text> |
||||
|
<view class="product-arrow">→</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,180 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
min-height: 100vh; |
||||
|
background-color: #f5f5f5; |
||||
|
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
background-color: #fff; |
||||
|
border-bottom: 1rpx solid #eee; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
letter-spacing: 2rpx; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 100rpx 0; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.loading-spinner { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border: 6rpx solid #f3f3f3; |
||||
|
border-top: 6rpx solid #FF6B81; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
.error { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 40rpx; |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05); |
||||
|
} |
||||
|
|
||||
|
.error-icon { |
||||
|
font-size: 60rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.error text { |
||||
|
display: block; |
||||
|
color: #ff4d4f; |
||||
|
margin-bottom: 30rpx; |
||||
|
font-size: 28rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.retry-button { |
||||
|
width: 200rpx; |
||||
|
height: 70rpx; |
||||
|
line-height: 70rpx; |
||||
|
font-size: 28rpx; |
||||
|
border-radius: 35rpx; |
||||
|
background-color: #FF6B81; |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
padding-left: 15rpx; |
||||
|
border-left: 5rpx solid #FF6B81; |
||||
|
} |
||||
|
|
||||
|
.section-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
background-color: #f5f5f5; |
||||
|
padding: 5rpx 15rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-list { |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); |
||||
|
} |
||||
|
|
||||
|
.product-items { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 30rpx; |
||||
|
border-radius: 10rpx; |
||||
|
background-color: #f9f9f9; |
||||
|
border: 1rpx solid #eee; |
||||
|
transition: all 0.3s ease; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.product-item:hover { |
||||
|
background-color: #f0f0f0; |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1); |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.product-arrow { |
||||
|
font-size: 30rpx; |
||||
|
color: #999; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 375px) { |
||||
|
.content { |
||||
|
padding: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.product-list { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.product-item { |
||||
|
padding: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
.product-name { |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
productName: '', |
||||
|
specification: '', |
||||
|
price: 0, |
||||
|
quantity: 1, |
||||
|
totalPrice: 0, |
||||
|
loading: false, |
||||
|
error: '' |
||||
|
}, |
||||
|
onLoad(options) { |
||||
|
if (options.productName && options.specification && options.price) { |
||||
|
const price = parseFloat(options.price) || 0; |
||||
|
this.setData({ |
||||
|
productName: decodeURIComponent(options.productName), |
||||
|
specification: decodeURIComponent(options.specification), |
||||
|
price: price, |
||||
|
totalPrice: price |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 件数输入变化
|
||||
|
onQuantityChange(e) { |
||||
|
const quantity = parseInt(e.detail.value) || 1; |
||||
|
this.setData({ |
||||
|
quantity: quantity, |
||||
|
totalPrice: this.data.price * quantity |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 计算价格
|
||||
|
calculatePrice() { |
||||
|
const totalPrice = this.data.price * this.data.quantity; |
||||
|
this.setData({ |
||||
|
totalPrice: totalPrice |
||||
|
}); |
||||
|
wx.showToast({ |
||||
|
title: '计算完成', |
||||
|
icon: 'success', |
||||
|
duration: 1000 |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack() { |
||||
|
wx.navigateBack(); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "规格详情", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<view class="container"> |
||||
|
<view class="header"> |
||||
|
<text class="title">{{productName}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="content"> |
||||
|
<view class="spec-card"> |
||||
|
<view class="spec-item"> |
||||
|
<text class="label">规格:</text> |
||||
|
<text class="value">{{specification}}</text> |
||||
|
</view> |
||||
|
<view class="spec-item"> |
||||
|
<text class="label">单价:</text> |
||||
|
<text class="value price">¥{{price.toFixed(2)}}</text> |
||||
|
</view> |
||||
|
<view class="spec-info"> |
||||
|
<text>您已选择此规格进行估价</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="quantity-section"> |
||||
|
<text class="label">件数:</text> |
||||
|
<input |
||||
|
class="quantity-input" |
||||
|
type="number" |
||||
|
value="{{quantity}}" |
||||
|
bindinput="onQuantityChange" |
||||
|
min="1" |
||||
|
/> |
||||
|
</view> |
||||
|
|
||||
|
<view class="calculate-section"> |
||||
|
<button bindtap="calculatePrice" type="primary" class="calculate-button">计算价格</button> |
||||
|
</view> |
||||
|
|
||||
|
<view class="result-section"> |
||||
|
<text class="result-label">总价:</text> |
||||
|
<text class="result-value">¥{{totalPrice.toFixed(2)}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="action"> |
||||
|
<button bindtap="goBack" type="default">返回</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,127 @@ |
|||||
|
.container { |
||||
|
min-height: 100vh; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
padding: 30rpx 0; |
||||
|
text-align: center; |
||||
|
background-color: #fff; |
||||
|
border-bottom: 1rpx solid #eee; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-card { |
||||
|
background-color: #fff; |
||||
|
border-radius: 12rpx; |
||||
|
padding: 40rpx; |
||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08); |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.spec-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.label { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-right: 20rpx; |
||||
|
width: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.value { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FF6B81; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.price { |
||||
|
color: #FF6B81; |
||||
|
} |
||||
|
|
||||
|
.spec-info { |
||||
|
padding-top: 20rpx; |
||||
|
border-top: 1rpx solid #eee; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.spec-info text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.quantity-section { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.quantity-input { |
||||
|
flex: 1; |
||||
|
height: 80rpx; |
||||
|
border: 2rpx solid #ddd; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 0 20rpx; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.calculate-section { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.calculate-button { |
||||
|
width: 80%; |
||||
|
height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
border-radius: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.result-section { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 40rpx; |
||||
|
padding: 20rpx; |
||||
|
background-color: #f9f9f9; |
||||
|
border-radius: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.result-label { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
margin-right: 20rpx; |
||||
|
width: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.result-value { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #FF6B81; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.action { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
button { |
||||
|
width: 80%; |
||||
|
height: 80rpx; |
||||
|
font-size: 28rpx; |
||||
|
border-radius: 40rpx; |
||||
|
} |
||||
Loading…
Reference in new issue