Browse Source

feat(回收): 添加品牌商品支持及数量管理功能

- 在manifest.json中添加appid配置
- 实现品牌商品的展示、选择和数量管理
- 添加减少数量时的品牌选择弹窗
- 重构商品数量计算逻辑以支持品牌商品
- 更新订单详情页以显示品牌信息
master
前端-胡立永 1 month ago
parent
commit
30a87772d7
4 changed files with 2319 additions and 48 deletions
  1. +1
    -1
      manifest.json
  2. +1927
    -0
      pages/component/recycle copy.vue
  3. +339
    -41
      pages/component/recycle.vue
  4. +52
    -6
      pages/subcomponent/pickup.vue

+ 1
- 1
manifest.json View File

@ -1,6 +1,6 @@
{ {
"name" : "瀚海回收", "name" : "瀚海回收",
"appid" : "",
"appid" : "__UNI__197A38F",
"description" : "", "description" : "",
"versionName" : "1.0.0", "versionName" : "1.0.0",
"versionCode" : "100", "versionCode" : "100",


+ 1927
- 0
pages/component/recycle copy.vue
File diff suppressed because it is too large
View File


+ 339
- 41
pages/component/recycle.vue View File

@ -52,8 +52,8 @@
<view class="goods-info-wrap"> <view class="goods-info-wrap">
<view class="goods-header"> <view class="goods-header">
<text class="goods-name">{{item.name}}</text> <text class="goods-name">{{item.name}}</text>
<view class="brand-check-placeholder" v-if="item.isPin == 'Y'?treu:false">
<view class="brand-check" @click="checkBrand(index)">
<view class="brand-check-placeholder" >
<view class="brand-check" @click="checkBrand(index)">
<text>查看品牌</text> <text>查看品牌</text>
<uni-icons type="right" size="12" color="#ff7a0e"></uni-icons> <uni-icons type="right" size="12" color="#ff7a0e"></uni-icons>
</view> </view>
@ -74,7 +74,7 @@
</view> </view>
<view class="quantity-control"> <view class="quantity-control">
<button class="btn-minus" @click="updateQuantity(index, -1)">-</button> <button class="btn-minus" @click="updateQuantity(index, -1)">-</button>
<text class="quantity">{{item.quantity || 0}}</text>
<text class="quantity">{{getItemTotalQuantity(item)}}</text>
<button class="btn-plus" @click="updateQuantity(index, 1)">+</button> <button class="btn-plus" @click="updateQuantity(index, 1)">+</button>
</view> </view>
</view> </view>
@ -133,11 +133,12 @@
<text class="panel-title">已选商品明细</text> <text class="panel-title">已选商品明细</text>
</view> </view>
<scroll-view class="panel-list popup-panel-list" scroll-y> <scroll-view class="panel-list popup-panel-list" scroll-y>
<view v-for="(item, idx) in selectedProducts" :key="idx" class="panel-item">
<view v-for="(item, idx) in selectedProducts" :key="item.uniqueKey || idx" class="panel-item">
<image v-if="item.image" :src="item.image" class="panel-item-img" mode="aspectFit" /> <image v-if="item.image" :src="item.image" class="panel-item-img" mode="aspectFit" />
<view class="panel-item-info"> <view class="panel-item-info">
<text class="panel-item-name">{{item.name}}</text> <text class="panel-item-name">{{item.name}}</text>
<text class="panel-item-desc">{{2222}}</text>
<text class="panel-item-desc" v-if="item.brandName">品牌{{item.brandName}}</text>
<text class="panel-item-desc" v-else>{{item.service}}</text>
<text class="panel-item-price">¥{{item.price}}/</text> <text class="panel-item-price">¥{{item.price}}/</text>
</view> </view>
<view class="panel-quantity-control"> <view class="panel-quantity-control">
@ -290,6 +291,22 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 减少数量时的品牌选择弹窗 -->
<view v-if="showBrandReducePopup" class="brand-reduce-popup-mask" @click.self="closeBrandReducePopup">
<view class="brand-reduce-popup">
<view class="brand-reduce-popup-header">
<text class="brand-reduce-popup-close" @click="closeBrandReducePopup">关闭</text>
<text class="brand-reduce-popup-title">选择要减少的品牌</text>
</view>
<scroll-view class="brand-reduce-popup-list" scroll-y>
<view v-for="brand in reduceBrandList" :key="brand.brandId" class="brand-item" @click="selectReduceBrand(brand)">
<image :src="brand.image" class="brand-logo" mode="aspectFit" />
<text class="brand-name">{{brand.name}}</text>
</view>
</scroll-view>
</view>
</view>
</view> </view>
</template> </template>
@ -330,6 +347,10 @@ export default {
pendingBrandIndex: null, // index pendingBrandIndex: null, // index
showPriceInfoPopup: false, showPriceInfoPopup: false,
isWaitingForBrandSelection: false, // isWaitingForBrandSelection: false, //
showBrandReducePopup: false, //
reduceItem: null, //
reduceBrandList: [], //
viewedRuleItems: new Set(), // ID
} }
}, },
computed: { computed: {
@ -341,13 +362,29 @@ export default {
// //
totalCount() { totalCount() {
return Object.values(this.allProducts).reduce((total, categoryItems) => { return Object.values(this.allProducts).reduce((total, categoryItems) => {
return total + categoryItems.reduce((sum, item) => sum + (item.quantity || 0), 0)
return total + categoryItems.reduce((sum, item) => {
//
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
return sum + Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
}
// 使quantity
return sum + (item.quantity || 0)
}, 0)
}, 0) }, 0)
}, },
// //
totalPrice() { totalPrice() {
const total = Object.values(this.allProducts).reduce((categoryTotal, categoryItems) => { const total = Object.values(this.allProducts).reduce((categoryTotal, categoryItems) => {
return categoryTotal + categoryItems.reduce((sum, item) => sum + (item.quantity || 0) * Number(item.price), 0)
return categoryTotal + categoryItems.reduce((sum, item) => {
let itemQuantity = 0
//
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
itemQuantity = Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
} else {
itemQuantity = item.quantity || 0
}
return sum + itemQuantity * Number(item.price)
}, 0)
}, 0) }, 0)
return total.toFixed(1) return total.toFixed(1)
}, },
@ -365,8 +402,33 @@ export default {
return { min, max } return { min, max }
}, },
selectedProducts() { selectedProducts() {
//
return Object.values(this.allProducts).flat().filter(item => item.quantity > 0)
//
const products = []
Object.values(this.allProducts).flat().forEach(item => {
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
//
Object.entries(item.brandQuantities).forEach(([brandId, quantity]) => {
if (quantity > 0) {
const brandInfo = this.getBrandInfo(brandId)
products.push({
...item,
quantity: quantity,
brandId: brandId,
brandName: brandInfo ? brandInfo.name : '未知品牌',
brandImage: brandInfo ? brandInfo.image : '',
uniqueKey: `${item.id}_${brandId}` //
})
}
})
} else if (item.quantity > 0) {
//
products.push({
...item,
uniqueKey: item.id
})
}
})
return products
}, },
filteredBrandList() { filteredBrandList() {
if (!this.brandSearch) return this.brandList if (!this.brandSearch) return this.brandList
@ -428,7 +490,12 @@ export default {
getCategoryItemCount(index) { getCategoryItemCount(index) {
const categoryId = this.categories[index]?.id const categoryId = this.categories[index]?.id
const categoryItems = this.allProducts[categoryId] || [] const categoryItems = this.allProducts[categoryId] || []
return categoryItems.reduce((sum, item) => sum + (item.quantity || 0), 0)
return categoryItems.reduce((sum, item) => {
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
return sum + Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
}
return sum + (item.quantity || 0)
}, 0)
}, },
// //
switchCategory(index) { switchCategory(index) {
@ -446,19 +513,100 @@ export default {
const categoryId = this.categories[this.currentCategory]?.id const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[index] const item = this.allProducts[categoryId]?.[index]
if (!item) return if (!item) return
// delta
if (delta < 0) {
//
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 1) {
//
this.reduceItem = { item, index, delta }
this.reduceBrandList = Object.entries(item.brandQuantities)
.filter(([brandId, quantity]) => quantity > 0)
.map(([brandId, quantity]) => {
const brandInfo = this.getBrandInfo(brandId)
return {
brandId,
quantity,
name: brandInfo ? brandInfo.name : '未知品牌',
image: brandInfo ? brandInfo.image : ''
}
})
this.showBrandReducePopup = true
return
} else if (item.brandQuantities && Object.keys(item.brandQuantities).length === 1) {
//
const brandId = Object.keys(item.brandQuantities)[0]
const currentQty = item.brandQuantities[brandId] || 0
const newQty = Math.max(0, currentQty + delta)
this.$set(item.brandQuantities, brandId, newQty)
// 0
if (newQty === 0) {
this.$delete(item.brandQuantities, brandId)
}
return
} else {
// 使
let newQuantity = (item.quantity || 0) + delta
if (newQuantity < 0) newQuantity = 0
this.$set(item, 'quantity', newQuantity)
return
}
}
// 0 // 0
if (item.isPin === 'Y' && (item.quantity || 0) === 0 && delta > 0) {
if ((item.quantity || 0) === 0 && delta > 0) {
this.pendingBrandIndex = index this.pendingBrandIndex = index
this.isWaitingForBrandSelection = true; this.isWaitingForBrandSelection = true;
this.showRules(item); // this.showRules(item); //
return return
} }
let newQuantity = (item.quantity || 0) + delta
if (newQuantity < 0) newQuantity = 0
this.$set(item, 'quantity', newQuantity)
},
//
selectReduceBrand(brandInfo) {
const { item, index, delta } = this.reduceItem
const currentQty = item.brandQuantities[brandInfo.brandId] || 0
const newQty = Math.max(0, currentQty + delta)
this.$set(item.brandQuantities, brandInfo.brandId, newQty)
// 0
if (newQty === 0) {
this.$delete(item.brandQuantities, brandInfo.brandId)
}
this.closeBrandReducePopup()
},
//
closeBrandReducePopup() {
this.showBrandReducePopup = false
this.reduceItem = null
this.reduceBrandList = []
},
//
getBrandInfo(brandId) {
return this.brandList.find(brand => brand.id === brandId)
},
//
getItemTotalQuantity(item) {
if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
return Object.values(item.brandQuantities).reduce((sum, qty) => sum + qty, 0)
}
return item.quantity || 0
}, },
// //
showRules(item) { showRules(item) {
//
if (this.viewedRuleItems.has(item.id)) {
//
this.isWaitingForBrandSelection = false;
this.getGoodsBrandList(item.id);
this.showBrandPopup = true;
return;
}
// //
this.$api('getGoodsRecycleRule', { goodsId: item.id }, res => { this.$api('getGoodsRecycleRule', { goodsId: item.id }, res => {
if (res.code === 200 && res.result) { if (res.code === 200 && res.result) {
@ -466,7 +614,7 @@ export default {
} else { } else {
this.ruleHtml = '<p>暂无回收规则</p>' this.ruleHtml = '<p>暂无回收规则</p>'
} }
this.showRulePopup = true
this.showRulePopup = true
}) })
}, },
showMore() { showMore() {
@ -511,14 +659,25 @@ export default {
}, },
goToPickup() { goToPickup() {
// //
const selectedItems = this.selectedProducts.map(item => ({
id: item.id,
name: item.name,
icon: item.image,
quantity: item.quantity,
unitPrice: item.price,
desc: '允许脏破烂,160码以上'
}))
const selectedItems = this.selectedProducts.map(item => {
const baseItem = {
id: item.id,
name: item.name,
icon: item.image,
quantity: item.quantity,
unitPrice: item.price,
desc: item.brandName ? `品牌:${item.brandName}` : '允许脏破烂,160码以上'
}
//
if (item.brandId) {
baseItem.brandId = item.brandId
baseItem.brandName = item.brandName
baseItem.brandImage = item.brandImage
}
return baseItem
})
const itemsStr = encodeURIComponent(JSON.stringify(selectedItems)) const itemsStr = encodeURIComponent(JSON.stringify(selectedItems))
uni.navigateTo({ uni.navigateTo({
url: `/pages/subcomponent/pickup?fromRecycle=true&items=${itemsStr}` url: `/pages/subcomponent/pickup?fromRecycle=true&items=${itemsStr}`
@ -527,10 +686,10 @@ export default {
checkBrand(index) { checkBrand(index) {
const categoryId = this.categories[this.currentCategory]?.id const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[index] const item = this.allProducts[categoryId]?.[index]
if (item?.shopCion) {
if (item?.id) {
this.pendingBrandIndex = index this.pendingBrandIndex = index
this.getGoodsBrandList(item.shopCion)
this.showBrandPopup = true
this.getGoodsBrandList(item.id)
this.showBrandPopup = true
} }
}, },
closeBrandPopup() { closeBrandPopup() {
@ -552,8 +711,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => { Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => { categoryItems.forEach(item => {
item.quantity = 0 item.quantity = 0
if (item.brandQuantities) {
item.brandQuantities = {}
}
}) })
}) })
//
this.viewedRuleItems.clear()
// //
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise(resolve => setTimeout(resolve, 1000))
@ -576,10 +740,44 @@ export default {
this.showDetailPanel = !this.showDetailPanel this.showDetailPanel = !this.showDetailPanel
}, },
updateQuantityByProduct(item, delta) { updateQuantityByProduct(item, delta) {
if (!item.quantity) item.quantity = 0
item.quantity += delta
if (item.quantity < 0) item.quantity = 0
this.updateTotal()
//
if (item.brandId) {
// ID
const originalItem = this.findOriginalItem(item.id)
if (originalItem && originalItem.brandQuantities) {
const currentQty = originalItem.brandQuantities[item.brandId] || 0
const newQty = Math.max(0, currentQty + delta)
this.$set(originalItem.brandQuantities, item.brandId, newQty)
// 0
if (newQty === 0) {
this.$delete(originalItem.brandQuantities, item.brandId)
}
//
item.quantity = newQty
}
} else {
//
if (!item.quantity) item.quantity = 0
item.quantity += delta
if (item.quantity < 0) item.quantity = 0
//
const originalItem = this.findOriginalItem(item.id)
if (originalItem) {
this.$set(originalItem, 'quantity', item.quantity)
}
}
},
//
findOriginalItem(itemId) {
for (const categoryItems of Object.values(this.allProducts)) {
const item = categoryItems.find(i => i.id === itemId)
if (item) return item
}
return null
}, },
openRulePopup() { openRulePopup() {
this.showRulePopup = true this.showRulePopup = true
@ -591,13 +789,12 @@ export default {
this.isWaitingForBrandSelection = false; // this.isWaitingForBrandSelection = false; //
const categoryId = this.categories[this.currentCategory]?.id; const categoryId = this.categories[this.currentCategory]?.id;
const item = this.allProducts[categoryId]?.[this.pendingBrandIndex]; const item = this.allProducts[categoryId]?.[this.pendingBrandIndex];
if (item?.isPin === 'Y') {
this.getGoodsBrandList(item.shopCion);
this.showBrandPopup = true; //
} else {
//
this.pendingBrandIndex = null;
}
//
this.viewedRuleItems.add(item.id);
this.getGoodsBrandList(item.id);
this.showBrandPopup = true; //
} }
}, },
loadMoreGoods() { loadMoreGoods() {
@ -639,14 +836,25 @@ export default {
const categoryId = this.categories[this.currentCategory]?.id const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[this.pendingBrandIndex] const item = this.allProducts[categoryId]?.[this.pendingBrandIndex]
if (item) { if (item) {
this.$set(item, 'quantity', 1)
this.$set(item, 'pinId', this.brandConfirmInfo.id);
//
if (!item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
//
const currentQty = item.brandQuantities[this.brandConfirmInfo.id] || 0
this.$set(item.brandQuantities, this.brandConfirmInfo.id, currentQty + 1)
// quantity
if (item.quantity) {
this.$set(item, 'quantity', 0)
}
} }
this.pendingBrandIndex = null this.pendingBrandIndex = null
} }
}, },
getGoodsBrandList(iconId) {
this.$api('getGoodsBrandList', { iconId }, res => {
getGoodsBrandList(productId) {
this.$api('getGoodsBrandList', { productId }, res => {
// console.log(res,'res') // console.log(res,'res')
if (res && res.success && res.result && res.result.records) { if (res && res.success && res.result && res.result.records) {
this.brandList = res.result.records.map(item => { this.brandList = res.result.records.map(item => {
@ -725,8 +933,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => { Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => { categoryItems.forEach(item => {
this.$set(item, 'quantity', 0) this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
}) })
}) })
//
this.viewedRuleItems.clear()
this.showDetailPanel = false this.showDetailPanel = false
this.$forceUpdate() this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false getApp().globalData.shouldClearRecycle = false
@ -764,8 +977,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => { Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => { categoryItems.forEach(item => {
this.$set(item, 'quantity', 0) this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
}) })
}) })
//
this.viewedRuleItems.clear()
this.showDetailPanel = false this.showDetailPanel = false
this.$forceUpdate() this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false getApp().globalData.shouldClearRecycle = false
@ -776,8 +994,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => { Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => { categoryItems.forEach(item => {
this.$set(item, 'quantity', 0) this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
}) })
}) })
//
this.viewedRuleItems.clear()
// //
this.showDetailPanel = false this.showDetailPanel = false
this.$forceUpdate() this.$forceUpdate()
@ -1923,4 +2146,79 @@ export default {
} }
} }
} }
/* 减少数量时的品牌选择弹窗 */
.brand-reduce-popup-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0,0,0,0.35);
z-index: 5001;
display: flex;
align-items: center;
justify-content: center;
}
.brand-reduce-popup {
width: 70vw;
max-width: 270px;
background: #fff;
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
display: flex;
flex-direction: column;
align-items: center;
padding: 48rpx 20rpx 36rpx 20rpx;
position: relative;
}
.brand-reduce-popup-header {
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx 24rpx 0 24rpx;
font-size: 32rpx;
font-weight: bold;
position: relative;
}
.brand-reduce-popup-close {
position: absolute;
left: 24rpx;
font-size: 28rpx;
color: #888;
}
.brand-reduce-popup-title {
font-size: 32rpx;
color: #222;
font-weight: bold;
}
.brand-reduce-popup-list {
flex: 1;
overflow-y: auto;
max-height: 60vh;
padding: 0 24rpx;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
width: 0 !important;
display: none; /* Chrome, Safari, Opera */
}
}
.brand-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1px solid #f0f0f0;
}
.brand-logo {
width: 60rpx;
height: 60rpx;
margin-right: 20rpx;
border-radius: 8rpx;
background: #f8f8f8;
}
.brand-name {
font-size: 28rpx;
color: #222;
}
</style> </style>

+ 52
- 6
pages/subcomponent/pickup.vue View File

@ -55,9 +55,15 @@
<view class="card-title process-title">订单详情</view> <view class="card-title process-title">订单详情</view>
<view class="order-items"> <view class="order-items">
<view class="order-item" v-for="(item, index) in showAllItems ? selectedItems : selectedItems.slice(0, 3)" :key="index"> <view class="order-item" v-for="(item, index) in showAllItems ? selectedItems : selectedItems.slice(0, 3)" :key="index">
<image :src="item.icon" mode="aspectFit"></image>
<view class="item-left">
<image :src="item.icon" mode="aspectFit"></image>
<image v-if="item.brandImage" :src="item.brandImage" class="brand-logo" mode="aspectFit"></image>
</view>
<view class="item-info"> <view class="item-info">
<view class="name">{{ item.name }}</view>
<view class="name-brand-row">
<text class="name">{{ item.name }}</text>
<text v-if="item.brandName" class="brand-tag">{{ item.brandName }}</text>
</view>
<view class="desc">{{ item.desc }}</view> <view class="desc">{{ item.desc }}</view>
<view class="price-row"> <view class="price-row">
<text class="price">{{ item.unitPrice }}/</text> <text class="price">{{ item.unitPrice }}/</text>
@ -324,7 +330,10 @@ export default {
shopId: item.id, shopId: item.id,
num: item.quantity num: item.quantity
}; };
if (item.pinId) {
// ID
if (item.brandId) {
orderItem.pinId = item.brandId;
} else if (item.pinId) {
orderItem.pinId = item.pinId; orderItem.pinId = item.pinId;
} }
return orderItem; return orderItem;
@ -604,14 +613,51 @@ export default {
padding: 0 30rpx; padding: 0 30rpx;
.order-item { .order-item {
display: flex; display: flex;
align-items: center;
align-items: flex-start;
padding: 30rpx 0; padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5; border-bottom: 1rpx solid #f5f5f5;
&:last-child { border-bottom: none; } &:last-child { border-bottom: none; }
image { width: 80rpx; height: 80rpx; margin-right: 20rpx; }
.item-left {
position: relative;
margin-right: 20rpx;
image {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
}
.brand-logo {
position: absolute;
bottom: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #fff;
background: #fff;
}
}
.item-info { .item-info {
flex: 1; flex: 1;
.name { font-size: 30rpx; color: #333; font-weight: 500; }
.name-brand-row {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-bottom: 4rpx;
.name {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-right: 12rpx;
}
.brand-tag {
background: #FFE8CC;
color: #FF9500;
font-size: 20rpx;
padding: 4rpx 8rpx;
border-radius: 8rpx;
border: 1rpx solid #FFD4A0;
}
}
.desc { font-size: 24rpx; color: #999; margin: 4rpx 0 8rpx 0; } .desc { font-size: 24rpx; color: #999; margin: 4rpx 0 8rpx 0; }
.price-row { .price-row {
display: flex; display: flex;


Loading…
Cancel
Save