Kaynağa Gözat

feat(购物车): 添加商品选择弹窗组件并修改添加购物车逻辑

添加商品选择弹窗组件,允许用户选择商品品相和数量
修改推荐商品和热门推荐组件的添加购物车逻辑,通过弹窗获取用户选择
更新添加购物车方法以接收包含商品、品相和数量的数据对象
ylong 1 hafta önce
ebeveyn
işleme
48937993af

+ 12 - 1
pages-sell/components/hot-recommend-item/index.vue

@@ -21,12 +21,20 @@
 				</view>
 			</view>
 		</view>
+
+		<!-- Product Selection Popup -->
+		<SelectGoodPopup ref="popup" @confirm="onPopupConfirm"></SelectGoodPopup>
 	</view>
 </template>
 
 <script>
+import SelectGoodPopup from '../select-good-popup/index.vue';
+
 export default {
 	name: 'HotRecommendItem',
+	components: {
+		SelectGoodPopup
+	},
 	props: {
 		item: {
 			type: Object,
@@ -36,7 +44,10 @@ export default {
 	},
 	methods: {
 		handleAddToCart() {
-			this.$emit('add-cart', this.item);
+			this.$refs.popup.open(this.item);
+		},
+		onPopupConfirm(data) {
+			this.$emit('add-cart', data);
 		}
 	}
 }

+ 14 - 1
pages-sell/components/recommend-item/index.vue

@@ -34,12 +34,20 @@
 			</view>
 			<text class="desc-content">{{ item.desc || '暂无简介' }}</text>
 		</view>
+
+		<!-- Product Selection Popup -->
+		<SelectGoodPopup ref="popup" @confirm="onPopupConfirm"></SelectGoodPopup>
 	</view>
 </template>
 
 <script>
+import SelectGoodPopup from '../select-good-popup/index.vue';
+
 export default {
 	name: 'RecommendItem',
+	components: {
+		SelectGoodPopup
+	},
 	props: {
 		item: {
 			type: Object,
@@ -52,7 +60,12 @@ export default {
 	},
 	methods: {
 		handleAddToCart() {
-			this.$emit('add-cart', this.item);
+			// Open the popup using ref
+			this.$refs.popup.open(this.item);
+		},
+		onPopupConfirm(data) {
+			// Emit the confirmed data
+			this.$emit('add-cart', data);
 		}
 	}
 }

+ 376 - 0
pages-sell/components/select-good-popup/index.vue

@@ -0,0 +1,376 @@
+<template>
+	<u-popup v-model="visible" mode="bottom" border-radius="24" :safe-area-inset-bottom="true" :mask-close-able="true"
+		@close="close">
+		<view class="popup-content">
+			<!-- Header -->
+			<view class="header">
+				<text class="title">选择商品品相</text>
+				<image src="/pages-sell/static/select-good/icon-close.png" class="close-icon" @click="close"></image>
+			</view>
+
+			<!-- Product Info -->
+			<view class="product-info">
+				<image :src="currentProduct.cover" class="book-cover" mode="aspectFill"></image>
+				<view class="info-right">
+					<view class="price-row">
+						<text class="currency">¥</text>
+						<text class="price">{{ currentProduct.price }}</text>
+						<view class="drop-tag">
+							<text>↓可降至 ¥{{ currentProduct.minPrice || '8.67' }}</text>
+						</view>
+					</view>
+					<view class="tag-row">
+						<view class="quality-tag">
+							<text>品相{{ currentQualityName }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- Tips -->
+			<view class="tips-row">
+				<text>不同品相有什么区别</text>
+				<image src="/pages-sell/static/select-good/icon-tips.png" class="tips-icon"></image>
+			</view>
+
+			<!-- Promo Note -->
+			<view class="promo-note">
+				<text>下单时用书购余额支付可享余额价 ( 售价 8 折优惠 )</text>
+			</view>
+
+			<!-- Options -->
+			<view class="options-list">
+				<view class="option-item" v-for="(opt, index) in qualityOptions" :key="index"
+					:class="{ active: currentQuality === opt.value }" @click="selectQuality(opt.value)">
+					<image v-if="currentQuality === opt.value" src="/pages-sell/static/select-good/selected.png"
+						class="bg-image"></image>
+					<view class="left">
+						<text class="opt-name">{{ opt.name }}</text>
+						<view class="opt-discount" :class="{ active: currentQuality === opt.value }">
+							<text>{{ opt.discount }}折</text>
+						</view>
+					</view>
+					<view class="right">
+						<text>¥{{ opt.price }} ( 余额价 ¥{{ opt.balancePrice }} )</text>
+					</view>
+				</view>
+			</view>
+
+			<!-- Quantity -->
+			<view class="quantity-row">
+				<text class="label">数量</text>
+				<u-number-box v-model="quantity" :min="1" :max="99"></u-number-box>
+			</view>
+
+			<!-- Footer Buttons -->
+			<view class="footer-btns">
+				<view class="btn btn-orange">
+					<text class="price">¥0.3</text>
+					<text class="desc">分享一人可降 0.5 元</text>
+				</view>
+				<view class="btn btn-green" @click="handleConfirm">
+					<text class="price">¥0.5</text>
+					<text class="desc">加入购物车</text>
+				</view>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+export default {
+	name: 'SelectGoodPopup',
+	data() {
+		return {
+			visible: false,
+			quantity: 1,
+			currentQuality: 'medium',
+			currentProduct: {},
+			qualityOptions: [
+				{ name: '中等', value: 'medium', discount: '5.3', price: '33.3', balancePrice: '8.6' },
+				{ name: '良品', value: 'good', discount: '5.3', price: '33.3', balancePrice: '8.6' },
+				{ name: '良品', value: 'good2', discount: '5.3', price: '33.3', balancePrice: '8.6' }
+			]
+		};
+	},
+	computed: {
+		currentQualityName() {
+			const opt = this.qualityOptions.find(o => o.value === this.currentQuality);
+			return opt ? opt.name : '';
+		}
+	},
+	methods: {
+		open(product) {
+			this.currentProduct = product || {};
+			this.visible = true;
+			this.quantity = 1;
+			this.currentQuality = 'medium';
+		},
+		close() {
+			this.visible = false;
+		},
+		selectQuality(val) {
+			this.currentQuality = val;
+		},
+		handleConfirm() {
+			this.$emit('confirm', {
+				product: this.currentProduct,
+				quality: this.currentQuality,
+				quantity: this.quantity
+			});
+			this.close();
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.popup-content {
+	padding: 30rpx 30rpx 20rpx;
+	background-color: #fff;
+	position: relative;
+}
+
+.header {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	position: relative;
+	margin-bottom: 30rpx;
+	padding-bottom: 30rpx;
+	border-bottom: 2rpx dashed #eee;
+
+	.title {
+		font-size: 34rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.close-icon {
+		position: absolute;
+		right: 0;
+		top: 0;
+		width: 24rpx;
+		height: 24rpx;
+		padding: 10rpx;
+		box-sizing: content-box;
+	}
+}
+
+.product-info {
+	display: flex;
+	margin-bottom: 30rpx;
+
+	.book-cover {
+		width: 140rpx;
+		height: 180rpx;
+		border-radius: 8rpx;
+		margin-right: 24rpx;
+		background-color: #f5f5f5;
+	}
+
+	.info-right {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		justify-content: space-between;
+		padding: 10rpx 0;
+
+		.price-row {
+			display: flex;
+			align-items: center;
+
+			.currency {
+				font-size: 36rpx;
+				color: #D81A00;
+				font-weight: bold;
+			}
+
+			.price {
+				font-size: 40rpx;
+				color: #D81A00;
+				font-weight: bold;
+				margin-right: 20rpx;
+				line-height: 1;
+			}
+
+			.drop-tag {
+				background: #E8F9EA;
+				padding: 4rpx 12rpx;
+				border-radius: 4rpx;
+
+				text {
+					color: #38C248;
+					font-size: 24rpx;
+					font-weight: 500;
+				}
+			}
+		}
+
+		.tag-row {
+			.quality-tag {
+				display: inline-block;
+				background: linear-gradient(0deg, #FFAB26 0%, #FFD426 100%);
+				border-radius: 21rpx 0px 21rpx 0px;
+				padding: 2rpx 24rpx;
+				margin-bottom: 24rpx;
+
+				text {
+					color: #fff;
+					font-size: 24rpx;
+					font-weight: 500;
+				}
+			}
+		}
+	}
+}
+
+.tips-row {
+	display: flex;
+	align-items: center;
+	margin-bottom: 16rpx;
+
+	text {
+		font-size: 26rpx;
+		color: #8D8D8D;
+		margin-right: 10rpx;
+	}
+
+	.tips-icon {
+		width: 36rpx;
+		height: 36rpx;
+	}
+}
+
+.promo-note {
+	margin-bottom: 40rpx;
+
+	text {
+		font-size: 26rpx;
+		color: #8D8D8D;
+	}
+}
+
+.options-list {
+	margin-bottom: 40rpx;
+
+	.option-item {
+		position: relative;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		background: #F8F8F8;
+		border-radius: 12rpx;
+		padding: 24rpx 30rpx;
+		margin-bottom: 24rpx;
+		border: 2rpx solid #dfdfdf;
+		transition: all 0.2s;
+
+		&.active {
+			border-color: transparent;
+		}
+
+		.bg-image {
+			position: absolute;
+			top: -6rpx;
+			left: -1%;
+			width: 102%;
+			height: 110rpx;
+			z-index: 0;
+		}
+
+		.left,
+		.right {
+			position: relative;
+			z-index: 1;
+		}
+
+		.left {
+			display: flex;
+			align-items: center;
+
+			.opt-name {
+				font-size: 30rpx;
+				font-weight: bold;
+				color: #333;
+				margin-right: 16rpx;
+			}
+
+			.opt-discount {
+				background: #D8D8D8;
+				border-radius: 8rpx 0 8rpx 0;
+				padding: 2rpx 10rpx;
+
+				text {
+					color: #fff;
+					font-size: 22rpx;
+				}
+
+				&.active {
+					background: #38C248;
+				}
+			}
+		}
+
+		.right {
+			text {
+				font-size: 28rpx;
+				color: #333;
+			}
+		}
+	}
+}
+
+.quantity-row {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 50rpx;
+
+	.label {
+		font-size: 30rpx;
+		font-weight: bold;
+		color: #333;
+	}
+}
+
+.footer-btns {
+	display: flex;
+	justify-content: space-between;
+	padding-bottom: 10rpx;
+
+	.btn {
+		flex: 1;
+		height: 100rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		font-family: Source Han Sans SC;
+		font-weight: 500;
+
+		.price {
+			font-size: 38rpx;
+			color: #fff;
+			line-height: 1.2;
+		}
+
+		.desc {
+			font-size: 24rpx;
+			color: #fff;
+		}
+
+		&.btn-orange {
+			background: linear-gradient(0deg, #EFA941 0%, #FFB84F 100%);
+			width: 340rpx;
+			border-radius: 50rpx 0 0 50rpx;
+		}
+
+		&.btn-green {
+			width: 340rpx;
+			background: linear-gradient(0deg, #38C248 0%, #5FEA6F 100%);
+			border-radius: 0 50rpx 50rpx 0;
+		}
+	}
+}
+</style>

+ 3 - 1
pages-sell/pages/search-result.vue

@@ -124,7 +124,9 @@ export default {
         onSearch(val) {
             console.log('Search:', val);
         },
-        addToCart(item) {
+        addToCart(data) {
+            // data now contains { product, quality, quantity } from the popup
+            console.log('Added to cart:', data);
             uni.showToast({
                 title: '已加入购物车',
                 icon: 'success'

BIN
pages-sell/static/select-good/icon-close.png


BIN
pages-sell/static/select-good/icon-tips.png


BIN
pages-sell/static/select-good/selected.png