feat: 商品详情交互开发

This commit is contained in:
duanshuwen
2025-08-03 18:06:06 +08:00
parent 42c5354978
commit 5e0d53fc20
22 changed files with 1906 additions and 582 deletions

View File

@@ -1,68 +1,68 @@
<template>
<view class="demo-container">
<TopNavBar title="商品确认组件演示" :fixed="true" />
<view class="demo-header">
<text class="demo-title">GoodConfirm 组件演示</text>
</view>
<view class="content-wrapper">
<view class="demo-section">
<view class="section-title">基础用法</view>
<view class="demo-item">
<button class="demo-btn" @click="showBasicDemo">显示基础确认弹窗</button>
</view>
</view>
<view class="demo-section">
<view class="section-title">自定义商品数据</view>
<view class="demo-item">
<button class="demo-btn" @click="showCustomDemo">显示自定义商品弹窗</button>
</view>
</view>
<view class="demo-section">
<view class="section-title">功能特性</view>
<view class="feature-list">
<view class="feature-item"> 基于 uni-popup 弹出层组件</view>
<view class="feature-item"> 商品信息展示图片标题价格标签</view>
<view class="feature-item"> 数量选择控制加减按钮手动输入</view>
<view class="feature-item"> 实时总价计算</view>
<view class="feature-item"> 确认购买和关闭事件</view>
<view class="feature-item"> 响应式设计适配移动端</view>
<view class="feature-item"> 优雅的动画效果</view>
<view class="demo-section">
<view class="section-title">基础用法</view>
<button class="demo-btn" @click="showConfirm">显示商品确认弹窗</button>
<button class="demo-btn" @click="setQuantity(5)" style="margin-left: 12px;">设置5人测试横向滚动</button>
</view>
<view class="demo-section">
<view class="section-title">当前状态</view>
<view class="status-info">
<text>Stepper数量: {{ quantity }}</text>
<text>表单项数量: {{ userFormCount }}</text>
<text>总价: ¥{{ totalPrice }}</text>
<text class="debug-info">实时quantity值: {{ quantity }}</text>
<text class="feature-highlight"> 支持横向滚动浏览多个游客信息</text>
<text class="feature-highlight">🗑 支持删除游客信息至少保留一位</text>
</view>
</view>
<view class="demo-section" v-if="lastOrderData">
<view class="section-title">最后提交的数据</view>
<view class="order-data">
<text>商品: {{ lastOrderData.goodsData.commodityName }}</text>
<text>数量: {{ lastOrderData.quantity }}</text>
<text>总价: ¥{{ lastOrderData.totalPrice }}</text>
<text>用户信息:</text>
<view class="user-list">
<view
v-for="(user, index) in lastOrderData.userFormList"
:key="index"
class="user-item"
>
<text>游客{{ index + 1 }}: {{ user.name || '未填写' }} - {{ user.phone || '未填写' }}</text>
</view>
</view>
</view>
</view>
<!-- 基础演示弹窗 -->
<GoodConfirm
ref="basicDemoRef"
:goodsData="basicGoodsData"
@confirm="handleBasicConfirm"
@close="handleBasicClose"
/>
<!-- 自定义演示弹窗 -->
<GoodConfirm
ref="customDemoRef"
:goodsData="customGoodsData"
@confirm="handleCustomConfirm"
@close="handleCustomClose"
ref="confirmRef"
:goodsData="goodsData"
@confirm="handleConfirm"
@close="handleClose"
/>
</view>
</template>
<script setup>
import { ref } from 'vue'
import TopNavBar from '@/components/TopNavBar/index.vue'
import { ref, computed } from 'vue'
import GoodConfirm from './index.vue'
// 引用
const basicDemoRef = ref(null)
const customDemoRef = ref(null)
const confirmRef = ref(null)
const quantity = ref(1)
const userFormCount = ref(1)
const lastOrderData = ref(null)
// 基础商品数据
const basicGoodsData = ref({
commodityName: '【成人票】云从朵花温泉门票',
price: 399,
timeTag: '随时可退',
const goodsData = ref({
commodityName: '云从朵花温泉票(成人票)',
price: 70,
timeTag: '条件退款',
commodityPhotoList: [
{
photoUrl: '/static/test/mk_img_1.png'
@@ -70,62 +70,61 @@ const basicGoodsData = ref({
]
})
// 自定义商品数据
const customGoodsData = ref({
commodityName: '【亲子套票】海洋世界一日游',
price: 268,
timeTag: '限时优惠',
commodityPhotoList: [
{
photoUrl: '/static/test/mk_img_1.png'
}
]
const totalPrice = computed(() => {
return (goodsData.value.price * quantity.value).toFixed(0)
})
// 方法定义
const showBasicDemo = () => {
basicDemoRef.value?.showPopup()
const showConfirm = () => {
confirmRef.value?.showPopup()
}
const showCustomDemo = () => {
customDemoRef.value?.showPopup()
}
const handleBasicConfirm = (orderData) => {
console.log('基础演示确认订单:', orderData)
uni.showModal({
title: '订单确认',
content: `商品:${orderData.goodsData.commodityName}\n数量${orderData.quantity}\n总价¥${orderData.totalPrice}`,
showCancel: false
})
}
const handleBasicClose = () => {
console.log('基础演示关闭弹窗')
}
const handleCustomConfirm = (orderData) => {
console.log('自定义演示确认订单:', orderData)
uni.showModal({
const handleConfirm = (orderData) => {
console.log('确认订单:', orderData)
lastOrderData.value = orderData
quantity.value = orderData.quantity
userFormCount.value = orderData.userFormList.length
uni.showToast({
title: '订单确认成功',
content: `商品:${orderData.goodsData.commodityName}\n数量${orderData.quantity}\n总价¥${orderData.totalPrice}`,
showCancel: false
icon: 'success'
})
}
const handleCustomClose = () => {
console.log('自定义演示关闭弹窗')
const handleClose = () => {
console.log('弹窗关闭')
}
const setQuantity = (num) => {
quantity.value = num
userFormCount.value = num
// 如果弹窗已打开需要通过组件内部的quantity来更新
if (confirmRef.value) {
// 这里可以通过ref访问组件内部状态但由于组件封装我们通过重新打开来演示
uni.showToast({
title: `已设置${num}人,请重新打开弹窗查看效果`,
icon: 'none',
duration: 2000
})
}
}
</script>
<style scoped lang="scss">
.demo-container {
min-height: 100vh;
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.content-wrapper {
padding: 100px 20px 20px;
.demo-header {
text-align: center;
margin-bottom: 30px;
.demo-title {
font-size: 24px;
font-weight: 600;
color: #333;
}
}
.demo-section {
@@ -134,57 +133,89 @@ const handleCustomClose = () => {
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #ff6b35;
margin-bottom: 15px;
}
}
.demo-item {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.demo-btn {
width: 100%;
height: 48px;
background: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
transition: all 0.2s;
.demo-btn {
width: 100%;
height: 48px;
background: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
color: #fff;
border: none;
border-radius: 24px;
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
&:active {
transform: translateY(1px);
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
}
&::after {
border: none;
}
}
&:active {
transform: translateY(1px);
box-shadow: 0 1px 4px rgba(255, 107, 53, 0.3);
.status-info {
display: flex;
flex-direction: column;
gap: 8px;
text {
font-size: 14px;
color: #666;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 6px;
&.feature-highlight {
background: linear-gradient(135deg, #ff6b35 0%, #ff8f65 100%);
color: white;
font-weight: bold;
}
&::after {
border: none;
&.debug-info {
background: #e6f7ff;
color: #1890ff;
font-weight: bold;
border: 1px solid #91d5ff;
}
}
}
.feature-list {
.feature-item {
padding: 8px 0;
font-size: 14px;
.order-data {
display: flex;
flex-direction: column;
gap: 8px;
text {
font-size: 14px;
color: #333;
line-height: 1.5;
}
}
.user-list {
margin-top: 8px;
padding-left: 12px;
.user-item {
margin-bottom: 4px;
text {
font-size: 13px;
color: #666;
line-height: 20px;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
}
}