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" : "瀚海回收",
"appid" : "",
"appid" : "__UNI__197A38F",
"description" : "",
"versionName" : "1.0.0",
"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-header">
<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>
<uni-icons type="right" size="12" color="#ff7a0e"></uni-icons>
</view>
@ -74,7 +74,7 @@
</view>
<view class="quantity-control">
<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>
</view>
</view>
@ -133,11 +133,12 @@
<text class="panel-title">已选商品明细</text>
</view>
<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" />
<view class="panel-item-info">
<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>
</view>
<view class="panel-quantity-control">
@ -290,6 +291,22 @@
</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>
</template>
@ -330,6 +347,10 @@ export default {
pendingBrandIndex: null, // index
showPriceInfoPopup: false,
isWaitingForBrandSelection: false, //
showBrandReducePopup: false, //
reduceItem: null, //
reduceBrandList: [], //
viewedRuleItems: new Set(), // ID
}
},
computed: {
@ -341,13 +362,29 @@ export default {
//
totalCount() {
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)
},
//
totalPrice() {
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)
return total.toFixed(1)
},
@ -365,8 +402,33 @@ export default {
return { min, max }
},
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() {
if (!this.brandSearch) return this.brandList
@ -428,7 +490,12 @@ export default {
getCategoryItemCount(index) {
const categoryId = this.categories[index]?.id
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) {
@ -446,19 +513,100 @@ export default {
const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[index]
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
if (item.isPin === 'Y' && (item.quantity || 0) === 0 && delta > 0) {
if ((item.quantity || 0) === 0 && delta > 0) {
this.pendingBrandIndex = index
this.isWaitingForBrandSelection = true;
this.showRules(item); //
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) {
//
if (this.viewedRuleItems.has(item.id)) {
//
this.isWaitingForBrandSelection = false;
this.getGoodsBrandList(item.id);
this.showBrandPopup = true;
return;
}
//
this.$api('getGoodsRecycleRule', { goodsId: item.id }, res => {
if (res.code === 200 && res.result) {
@ -466,7 +614,7 @@ export default {
} else {
this.ruleHtml = '<p>暂无回收规则</p>'
}
this.showRulePopup = true
this.showRulePopup = true
})
},
showMore() {
@ -511,14 +659,25 @@ export default {
},
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))
uni.navigateTo({
url: `/pages/subcomponent/pickup?fromRecycle=true&items=${itemsStr}`
@ -527,10 +686,10 @@ export default {
checkBrand(index) {
const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[index]
if (item?.shopCion) {
if (item?.id) {
this.pendingBrandIndex = index
this.getGoodsBrandList(item.shopCion)
this.showBrandPopup = true
this.getGoodsBrandList(item.id)
this.showBrandPopup = true
}
},
closeBrandPopup() {
@ -552,8 +711,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => {
item.quantity = 0
if (item.brandQuantities) {
item.brandQuantities = {}
}
})
})
//
this.viewedRuleItems.clear()
//
await new Promise(resolve => setTimeout(resolve, 1000))
@ -576,10 +740,44 @@ export default {
this.showDetailPanel = !this.showDetailPanel
},
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() {
this.showRulePopup = true
@ -591,13 +789,12 @@ export default {
this.isWaitingForBrandSelection = false; //
const categoryId = this.categories[this.currentCategory]?.id;
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() {
@ -639,14 +836,25 @@ export default {
const categoryId = this.categories[this.currentCategory]?.id
const item = this.allProducts[categoryId]?.[this.pendingBrandIndex]
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
}
},
getGoodsBrandList(iconId) {
this.$api('getGoodsBrandList', { iconId }, res => {
getGoodsBrandList(productId) {
this.$api('getGoodsBrandList', { productId }, res => {
// console.log(res,'res')
if (res && res.success && res.result && res.result.records) {
this.brandList = res.result.records.map(item => {
@ -725,8 +933,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => {
this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
})
})
//
this.viewedRuleItems.clear()
this.showDetailPanel = false
this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false
@ -764,8 +977,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => {
this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
})
})
//
this.viewedRuleItems.clear()
this.showDetailPanel = false
this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false
@ -776,8 +994,13 @@ export default {
Object.values(this.allProducts).forEach(categoryItems => {
categoryItems.forEach(item => {
this.$set(item, 'quantity', 0)
if (item.brandQuantities) {
this.$set(item, 'brandQuantities', {})
}
})
})
//
this.viewedRuleItems.clear()
//
this.showDetailPanel = false
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>

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

@ -55,9 +55,15 @@
<view class="card-title process-title">订单详情</view>
<view class="order-items">
<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="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="price-row">
<text class="price">{{ item.unitPrice }}/</text>
@ -324,7 +330,10 @@ export default {
shopId: item.id,
num: item.quantity
};
if (item.pinId) {
// ID
if (item.brandId) {
orderItem.pinId = item.brandId;
} else if (item.pinId) {
orderItem.pinId = item.pinId;
}
return orderItem;
@ -604,14 +613,51 @@ export default {
padding: 0 30rpx;
.order-item {
display: flex;
align-items: center;
align-items: flex-start;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&: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 {
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; }
.price-row {
display: flex;


Loading…
Cancel
Save