consumer模块完成度95%,优化安卓端界面和小程序测试

This commit is contained in:
cyh666666
2026-03-11 17:17:32 +08:00
parent 5517c93666
commit 77f9968d18
622 changed files with 3100 additions and 1995 deletions

View File

@@ -3,30 +3,30 @@
<view class="checkout-page">
<scroll-view class="checkout-content" direction="vertical">
<!-- 收货地址 -->
<view class="address-section" @click="selectAddress">
<view class="section-card address-section" @click="selectAddress">
<view class="address-icon-wrapper">
<text class="location-icon">📍</text>
</view>
<view v-if="selectedAddress" class="address-info">
<view class="address-header">
<text class="recipient">{{ selectedAddress!!.recipient_name }}</text>
<text class="phone">{{ selectedAddress!!.phone }}</text>
<view v-if="selectedAddress!!.is_default" class="default-tag">
<text class="tag-text">默认</text>
</view>
</view>
<text class="address-detail">{{ getFullAddress(selectedAddress!!) }}</text>
<text class="recipient">{{ selectedAddress!!.recipient_name }}</text>
<text class="phone">{{ selectedAddress!!.phone }}</text>
<view v-if="selectedAddress!!.is_default" class="default-tag">
<text class="tag-text">默认</text>
</view>
</view>
<text class="address-detail">{{ getFullAddress(selectedAddress!!) }}</text>
</view>
<view v-else class="no-address">
<text class="no-address-text">请选择收货地址</text>
<text class="no-address-arrow"></text>
</view>
<view class="address-arrow-wrapper">
<text class="address-arrow"></text>
</view>
</view>
<!-- 商品列表 (按店铺分组) -->
<view class="products-section">
<!-- 调试信息 -->
<view v-if="checkoutItems.length > 0" class="debug-info">
<text class="debug-text">共 {{ checkoutItems.length }} 件商品</text>
</view>
<view class="section-card products-section">
<view v-if="shopGroups.length > 0">
<view v-for="group in shopGroups" :key="group.shopId" class="shop-group">
<view class="shop-header">
@@ -34,27 +34,25 @@
<text class="shop-name">{{ group.shopName }}</text>
</view>
<!-- 商品列表 -->
<view v-for="item in group.items" :key="item.id" class="product-item">
<image class="product-image" :src="item.product_image" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{ item.product_name }}</text>
<text v-if="item.sku_specifications" class="product-spec">{{ formatSpecs(item.sku_specifications) }}</text>
<view class="product-bottom">
<view class="product-name-row">
<text class="product-name">{{ item.product_name }}</text>
<text class="product-price">¥{{ item.price }}</text>
</view>
<view class="product-spec-row">
<text v-if="item.sku_specifications" class="product-spec">{{ formatSpecs(item.sku_specifications) }}</text>
<text class="product-quantity">×{{ item.quantity }}</text>
</view>
<!-- 商品小计移至图片右侧 -->
<view class="item-subtotal-row">
<text class="item-subtotal-label">小计:</text>
<text class="item-subtotal-price">¥{{ (item.price * item.quantity).toFixed(2) }}</text>
</view>
</view>
</view>
<!-- 店铺小计 -->
<view class="shop-subtotal">
<text class="subtotal-label">配送方式</text>
<text class="subtotal-value">快递 免邮</text>
</view>
<view class="shop-subtotal">
<text class="subtotal-text">小计: </text>
<text class="subtotal-price">¥{{ getGroupTotal(group) }}</text>
</view>
</view>
</view>
<view v-else class="no-products">
@@ -63,70 +61,76 @@
</view>
<!-- 配送方式 -->
<view class="delivery-section">
<text class="section-title">配送方式</text>
<view class="delivery-options">
<view v-for="option in deliveryOptions"
:key="option.id"
:class="['delivery-option', { selected: selectedDelivery === option.id }]"
@click="selectDelivery(option)">
<text class="option-name">{{ option.name }}</text>
<text class="option-price">¥{{ option.price }}</text>
<text v-if="selectedDelivery === option.id" class="option-selected">✓</text>
<view class="section-card delivery-section">
<view class="delivery-row">
<text class="section-title">配送方式</text>
<view class="delivery-selector">
<view v-for="option in deliveryOptions"
:key="option.id"
:class="['delivery-pill', { selected: selectedDelivery === option.id }]"
@click="selectDelivery(option)">
<text class="pill-name">{{ option.name }}</text>
</view>
</view>
</view>
<view class="delivery-detail" v-if="selectedDelivery">
<text class="detail-desc">{{ deliveryOptions.find(opt => opt.id === selectedDelivery)?.description }}</text>
<text class="detail-price">费用: ¥{{ deliveryOptions.find(opt => opt.id === selectedDelivery)?.price.toFixed(2) }}</text>
</view>
</view>
<!-- 优惠券 -->
<view class="coupon-section" @click="selectCoupon">
<text class="section-title">优惠券</text>
<view class="coupon-info">
<text v-if="selectedCoupon != null" class="coupon-selected">{{ selectedCoupon.template?.name ?? '已选择优惠券 (¥' + (selectedCoupon.template?.discount_value ?? 0) + ')' }}</text>
<text v-else class="coupon-placeholder">选择优惠券</text>
<text class="coupon-arrow"></text>
<view class="section-card coupon-section" @click="selectCoupon">
<view class="coupon-row">
<text class="section-title">优惠券</text>
<view class="coupon-right-content">
<text v-if="selectedCoupon != null" class="coupon-selected-name">{{ selectedCoupon.template?.name ?? '已选择优惠券' }}</text>
<text v-else class="coupon-placeholder">暂无可用优惠券</text>
<text class="arrow-icon"></text>
</view>
</view>
</view>
<!-- 买家留言 -->
<view class="remark-section">
<text class="section-title">买家留言</text>
<textarea class="remark-input"
v-model="remark"
placeholder="选填,请先和商家协商一致"
maxlength="100" />
<view class="section-card remark-section">
<view class="remark-row">
<text class="section-title">买家留言</text>
<input class="remark-input-compact"
v-model="remark"
placeholder="选填,给商家留言"
maxlength="100" />
</view>
</view>
<!-- 价格明细 -->
<view class="price-section">
<text class="section-title">价格明细</text>
<view class="price-detail">
<view class="price-row">
<text class="price-label">商品总价</text>
<text class="price-value">¥{{ totalAmount.toFixed(2) }}</text>
<view class="section-card price-section">
<view class="price-grid">
<view class="price-item-inline">
<text class="price-item-label">商品</text>
<text class="price-item-value">¥{{ totalAmount.toFixed(2) }}</text>
</view>
<view class="price-row">
<text class="price-label">运费</text>
<text class="price-value">+¥{{ deliveryFee.toFixed(2) }}</text>
<view class="price-item-inline">
<text class="price-item-label">运费</text>
<text class="price-item-value">+¥{{ deliveryFee.toFixed(2) }}</text>
</view>
<view v-if="discountAmount > 0" class="price-row">
<text class="price-label">优惠减免</text>
<text class="price-value discount">-¥{{ discountAmount.toFixed(2) }}</text>
</view>
<view class="price-row total">
<text class="price-label">应付金额</text>
<text class="price-value total-price">¥{{ actualAmount.toFixed(2) }}</text>
<view v-if="discountAmount > 0" class="price-item-inline">
<text class="price-item-label">优惠</text>
<text class="price-item-value discount-text">-¥{{ discountAmount.toFixed(2) }}</text>
</view>
</view>
</view>
<view class="safe-area-bottom"></view>
</scroll-view>
<!-- 底部结算栏 -->
<view class="bottom-bar">
<view class="price-summary">
<text class="summary-label">合计:</text>
<text class="summary-price">¥{{ actualAmount.toFixed(2) }}</text>
<view class="footer-action-bar">
<view class="footer-left">
<text class="footer-total-label">合计:</text>
<text class="footer-currency">¥</text>
<text class="footer-price">{{ actualAmount.toFixed(2) }}</text>
</view>
<button class="submit-btn" @click="submitOrder">提交订单</button>
<button class="footer-submit-btn" @click="submitOrder">提交订单</button>
</view>
<!-- 地址选择弹窗 -->
@@ -414,10 +418,10 @@ function getObjectKeys(obj: object): string[] {
const checkoutItems = ref<Array<CheckoutItemType>>([])
const selectedAddress = ref<AddressItem | null>(null)
const deliveryOptions = ref<Array<DeliveryOptionType>>([
{ id: 'standard', name: '快递配送', price: 8.00, description: '1-3天送达' },
{ id: 'express', name: '加急配送', price: 15.00, description: '当天送达' }
{ id: 'express', name: '物流快递', price: 8.00, description: '普通快递配送' },
{ id: 'local', name: '同城配送', price: 15.00, description: '同城极速上门' }
])
const selectedDelivery = ref<string>('standard')
const selectedDelivery = ref<string>('express')
const selectedCoupon = ref<UserCouponType | null>(null)
const remark = ref<string>('')
const showAddressPopup = ref<boolean>(false)
@@ -1427,15 +1431,17 @@ const goToLogin = () => {
left: 0;
right: 0;
bottom: 0;
background-color: #f5f5f5;
background-color: #f8f8f8;
overflow: hidden;
align-items: center; /* PC端居中显示 */
}
/* 顶部栏 */
.checkout-header {
background-color: #ffffff;
padding: 15px;
border-bottom: 1px solid #e5e5e5;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
width: 100%;
}
.header-title {
@@ -1448,83 +1454,474 @@ const goToLogin = () => {
.checkout-content {
flex: 1;
width: 100%;
max-width: 800px; /* 限制PC端内容宽度 */
min-height: 0;
background-color: #f5f5f5;
background-color: #f8f8f8;
}
/* 卡片容器 */
.no-products {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 30px 0;
}
.no-products-text { font-size: 14px; color: #999999; }
.section-card {
background-color: #ffffff;
margin: 12px;
padding: 18px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.02);
}
/* 自适应适配 */
@media screen and (min-width: 768px) {
.section-card {
margin: 16px 0;
}
.delivery-options-grid {
display: flex;
flex-direction: row !important;
flex-wrap: wrap;
gap: 12px;
}
.delivery-card {
flex: 1;
min-width: 280px;
margin-bottom: 0 !important;
}
/* 底部结算栏在大屏居中并限宽 */
.footer-action-bar {
max-width: 800px;
left: 50% !important;
right: auto !important;
transform: translateX(-50%);
border-radius: 16px 16px 0 0;
box-shadow: 0 -4px 16px rgba(0,0,0,0.05);
}
}
.address-section {
margin-top: 12px;
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
position: relative;
background-color: #ffffff;
margin-bottom: 10px;
padding: 20px 15px;
}
.address-icon-wrapper {
margin-right: 12px;
display: flex;
align-items: center;
}
.address-info { flex: 1; }
.address-header { display: flex; align-items: center; margin-bottom: 10px; }
.recipient { font-size: 16px; font-weight: bold; color: #333333; margin-right: 15px; }
.phone { font-size: 14px; color: #666666; margin-right: 10px; }
.default-tag { background-color: #ff4757; padding: 2px 8px; border-radius: 10px; }
.tag-text { color: #ffffff; font-size: 12px; }
.address-detail { font-size: 14px; color: #333333; line-height: 1.4; }
.no-address { flex: 1; display: flex; justify-content: space-between; align-items: center; }
.location-icon {
font-size: 24px;
color: #ff5000;
}
.address-info {
flex: 1;
display: flex;
flex-direction: column;
}
.address-header {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 4px;
}
.recipient { font-size: 17px; font-weight: bold; color: #333333; margin-right: 12px; }
.phone { font-size: 14px; color: #666666; margin-right: 8px; }
.default-tag { background-color: #fff0eb; border: 0.5px solid #ff5000; padding: 0 6px; border-radius: 4px; }
.tag-text { color: #ff5000; font-size: 11px; }
.address-detail {
font-size: 13px;
color: #666;
line-height: 1.5;
lines: 2;
text-overflow: ellipsis;
overflow: hidden;
}
.address-arrow-wrapper {
margin-left: 8px;
}
.address-arrow {
color: #ccc;
font-size: 20px;
}
.no-address { flex: 1; display: flex; align-items: center; }
.no-address-text { font-size: 16px; color: #999999; }
.no-address-arrow { color: #999999; font-size: 18px; }
.products-section { background-color: #ffffff; margin-bottom: 10px; padding: 0 15px; }
.debug-info { background-color: #f8f9fa; padding: 10px 15px; border-bottom: 1px solid #e5e5e5; margin-bottom: 10px; }
.debug-text { font-size: 12px; color: #666; text-align: center; }
.shop-group { background-color: #fff; margin: 10px 0; border-radius: 12px; padding: 10px; }
.shop-header { display: flex; flex-direction: row; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #f0f0f0; }
.shop-icon { font-size: 18px; margin-right: 8px; }
.shop-name { font-size: 16px; font-weight: bold; color: #333; }
.shop-subtotal { display: flex; justify-content: flex-end; align-items: center; padding-top: 10px; margin-top: 5px; border-top: 1px dashed #f0f0f0; font-size: 14px; }
.subtotal-label { color: #666; margin-right: 10px; }
.subtotal-value { color: #333; }
.subtotal-text { color: #333; margin-right: 5px; }
.subtotal-price { color: #ff4757; font-weight: bold; font-size: 16px; }
.products-section { padding: 0; }
.debug-info { padding: 10px 15px; border-bottom: 1px solid #f5f5f5; margin-bottom: 10px; }
.debug-text { font-size: 12px; color: #999; text-align: center; }
.shop-group { background-color: #fff; padding: 0; }
.shop-header { display: flex; flex-direction: row; align-items: center; padding: 5px 0 12px; }
.shop-icon { font-size: 17px; margin-right: 6px; }
.shop-name { font-size: 15px; font-weight: bold; color: #333; }
.shop-subtotal { display: flex; justify-content: flex-end; align-items: center; padding: 12px 0 5px; margin-top: 5px; border-top: 1px dashed #f0f0f0; }
.subtotal-label { color: #888; margin-right: 8px; font-size: 13px; }
.subtotal-value { color: #666; font-size: 13px; }
.subtotal-text { color: #333; margin-right: 5px; font-size: 14px; }
.subtotal-price { color: #ff5000; font-weight: bold; font-size: 16px; }
.product-item { display: flex; padding: 15px 0; border-bottom: 1px solid #f5f5f5; }
.product-item:last-child { border-bottom: none; }
.product-image { width: 80px; height: 80px; border-radius: 5px; margin-right: 15px; }
.product-item { display: flex; flex-direction: row; padding: 12px 0; }
.product-image { width: 85px; height: 85px; border-radius: 8px; margin-right: 12px; background-color: #f8f8f8; }
.product-info { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
.product-name { font-size: 14px; color: #333333; line-height: 1.4; margin-bottom: 5px; lines: 2; text-overflow: ellipsis; overflow: hidden; }
.product-spec { font-size: 12px; color: #999999; margin-bottom: 10px; }
.product-bottom { display: flex; justify-content: space-between; align-items: center; }
.product-price { font-size: 16px; color: #ff4757; font-weight: bold; }
.product-quantity { font-size: 14px; color: #666666; }
.product-name-row { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; }
.product-name { flex: 1; font-size: 14px; color: #333333; line-height: 1.4; lines: 2; text-overflow: ellipsis; overflow: hidden; margin-right: 12px; }
.product-price { font-size: 15px; color: #333; font-weight: normal; }
.product-spec-row { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.product-spec { font-size: 12px; color: #999999; background-color: #f7f7f7; padding: 2px 6px; border-radius: 4px; }
.product-quantity { font-size: 12px; color: #999999; }
.delivery-section, .coupon-section, .remark-section, .price-section { background-color: #ffffff; margin-bottom: 10px; padding: 15px; }
.section-title { font-size: 16px; font-weight: bold; color: #333333; margin-bottom: 15px; }
/* 配送方式重构 */
.delivery-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.delivery-options { display: flex; flex-direction: column; }
.delivery-option { display: flex; align-items: center; justify-content: space-between; padding: 15px; border: 1px solid #e5e5e5; border-radius: 8px; margin-bottom: 10px; }
.delivery-option:last-child { margin-bottom: 0; }
.delivery-option.selected { border-color: #007aff; background-color: #f0f8ff; }
.option-name { font-size: 14px; color: #333333; }
.option-price { font-size: 14px; color: #ff4757; font-weight: bold; }
.option-selected { color: #007aff; font-size: 16px; }
.delivery-selector {
display: flex;
flex-direction: row;
gap: 8px;
}
.coupon-info { display: flex; align-items: center; justify-content: space-between; padding: 15px; border: 1px solid #e5e5e5; border-radius: 8px; }
.coupon-selected { font-size: 14px; color: #007aff; }
.coupon-placeholder { font-size: 14px; color: #999999; }
.coupon-arrow { color: #999999; font-size: 16px; }
.delivery-pill {
padding: 4px 12px;
background-color: #f5f5f5;
border-radius: 16px;
border: 1px solid transparent;
}
.remark-input { width: 100%; min-height: 40px; padding: 10px; border: 1px solid #e5e5e5; border-radius: 8px; font-size: 14px; color: #333333; }
.delivery-pill.selected {
background-color: #fff9f6;
border-color: #ff5000;
}
.price-detail { padding: 15px; background-color: #f8f9fa; border-radius: 8px; }
.price-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }
.price-row.total { border-top: 1px solid #e5e5e5; margin-top: 8px; padding-top: 15px; }
.price-label { font-size: 14px; color: #666666; }
.price-value { font-size: 14px; color: #333333; }
.price-value.discount { color: #4caf50; }
.price-value.total-price { font-size: 18px; color: #ff4757; font-weight: bold; }
.pill-name {
font-size: 12px;
color: #666;
}
.bottom-bar { background-color: #ffffff; padding: 15px; border-top: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; }
.price-summary { display: flex; align-items: flex-end; }
.summary-label { font-size: 14px; color: #333333; margin-right: 5px; }
.summary-price { font-size: 20px; color: #ff4757; font-weight: bold; }
.submit-btn { background-color: #007aff; color: #ffffff; padding: 0 40px; height: 45px; border-radius: 22.5px; font-size: 16px; font-weight: bold; border: none; }
.delivery-pill.selected .pill-name {
color: #ff5000;
}
.delivery-detail {
margin-top: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
padding-top: 8px;
border-top: 0.5px solid #f9f9f9;
}
.detail-desc {
font-size: 11px;
color: #999;
}
.detail-price {
font-size: 11px;
color: #ff5000;
}
/* 优惠券重构 */
.coupon-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.coupon-right-content {
display: flex;
flex-direction: row;
align-items: center;
}
/* 留言重构 */
.remark-row {
display: flex;
flex-direction: row;
align-items: center;
}
.remark-input-compact {
flex: 1;
margin-left: 15px;
font-size: 14px;
color: #333;
height: 30px;
}
/* 价格明细横向排列 */
.price-grid {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.price-item-inline {
display: flex;
flex-direction: row;
align-items: baseline;
}
.price-item-label {
font-size: 12px;
color: #999;
margin-right: 4px;
}
.price-item-value {
font-size: 14px;
color: #333;
font-weight: 500;
}
.item-subtotal-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
margin-top: 4px;
}
.item-subtotal-label {
font-size: 12px;
color: #999;
margin-right: 4px;
}
.item-subtotal-price {
font-size: 14px;
color: #ff5000;
font-weight: bold;
}
/* 配送方式网格 */
.delivery-options-grid {
display: flex;
flex-direction: column;
}
.delivery-card {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #f0f0f0;
border-radius: 10px;
margin-bottom: 10px;
background-color: #fafafa;
}
.delivery-card.selected {
border-color: #ff5000;
background-color: #fff9f6;
}
.option-main {
flex: 1;
}
.option-name {
font-size: 15px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
display: block;
}
.option-desc {
font-size: 12px;
color: #999;
display: block;
}
.option-side {
display: flex;
align-items: center;
}
.option-price {
font-size: 14px;
color: #333;
margin-right: 10px;
}
.select-icon {
width: 18px;
height: 18px;
background-color: #ff5000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.check-mark {
color: #ffffff;
font-size: 12px;
margin-top: -1px;
}
/* 优惠券样式重构 */
.coupon-content {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.coupon-left {
display: flex;
align-items: center;
}
.coupon-tag {
background-color: #ff5000;
color: #fff;
font-size: 10px;
padding: 1px 4px;
border-radius: 2px;
margin-left: 8px;
}
.coupon-right {
display: flex;
align-items: center;
}
.coupon-selected-name {
font-size: 14px;
color: #ff5000;
}
.coupon-placeholder {
font-size: 14px;
color: #999;
}
.arrow-icon {
font-size: 18px;
color: #ccc;
margin-left: 5px;
}
/* 留言输入 */
.remark-input-new {
width: 100%;
background-color: #f9f9f9;
border-radius: 8px;
padding: 12px;
font-size: 14px;
min-height: 48px;
}
/* 价格明细列表 */
.price-list {
margin-top: 5px;
}
.price-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
}
.price-item-label {
font-size: 14px;
color: #666;
}
.price-item-value {
font-size: 14px;
color: #333;
}
.discount-text {
color: #ff5000;
}
.section-title { font-size: 15px; font-weight: bold; color: #333333; }
/* 底部操作栏 */
.footer-action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: #ffffff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 16px;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
border-top: 1px solid #f0f0f0;
z-index: 100;
}
.footer-left {
display: flex;
flex-direction: row;
align-items: baseline;
}
.footer-total-label {
font-size: 14px;
color: #333;
margin-right: 4px;
}
.footer-currency {
font-size: 14px;
color: #ff5000;
font-weight: bold;
}
.footer-price {
font-size: 22px;
color: #ff5000;
font-weight: bold;
}
.footer-submit-btn {
background-color: #ff5000;
color: #ffffff;
padding: 0 28px;
height: 42px;
line-height: 42px;
border-radius: 21px;
font-size: 16px;
font-weight: bold;
border: none;
margin: 0;
}
.safe-area-bottom {
height: 100px; /* 留出底部操作栏和安全区的空间 */
width: 100%;
}
/* 弹窗样式 */
.address-popup-mask, .address-form-mask, .confirm-popup-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9998; }