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; }

View File

@@ -72,5 +72,48 @@
* **日志记录**:接入 **Sentry****阿里云日志服务**,记录生产环境中的所有前端报错(尤其是支付环节)。
* **压力测试**:上线前需进行接口压力测试,确保在促销活动期间数据库连接数不会爆满。
---
## 6. 无真机调试指南 (iOS & 鸿蒙调试方案)
针对您目前没有 iOS 和鸿蒙HarmonyOS Next实机的情况请务必执行以下**模拟调试流程**,以确保上线后 90% 以上的兼容性。
### A. iOS (苹果) 远程与模拟调试
1. **微信开发者工具“模拟器” (最简便)**:
* 在工具底部左侧选择 `iPhone 13/14 Pro`
* **重点检查**: 底部 Tabbar 是否被 iPhone 的 Home Indicator (底部黑条) 遮挡。如果是,请在 CSS 中使用 `padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);`
2. **Chrome 浏览器 iOS 仿真 (针对 H5 端)**:
* 将项目运行为 H5开启 Chrome Console (F12)。
* 选择 `iPhone` 设备仿真,手动测试 `v-if` 在不同屏幕高度下的布局抖动。
3. **免费云真机调试 (推荐)**:
* **腾讯云 WeTest / 阿里云移动测试**: 注册后通常有免费试用额度(如 30-60 分钟)。
* **操作**: 上传打包好的 APK (Android) 或在微信内测环境下选择 iOS 云真机,远程控制真机屏幕进行滑屏测试。
### B. 鸿蒙 (HarmonyOS Next) 深度兼容
由于鸿蒙系统现在进入了“纯血”阶段,与 Android 差异变大:
1. **华为开发者联盟“云调试” (官方推荐)**:
* **访问**: [华为远程真机调试](https://developer.huawei.com/consumer/cn/service/jsservicestu/appdebug.html)。
* **步骤**:
1. 注册并完成实名认证。
2. 选择“远程真机”。
3. 申请免费使用的 **Mate 60 / Pura 70 (HarmonyOS Next 预览版)**
* **调试重点**: 鸿蒙系统对 Webview 的内核限制较多,务必通过云真机测试“结算页”和“支付弹窗”是否能正常拉起。
2. **DevEco Studio 远程模拟器**:
* 安装华为官开发的 IDE `DevEco Studio`
* 使用内置的 `Device Manager` 启动 `Remote Emulator`。这比本地模拟器对电脑配置要求低,且渲染效果最接近真机。
### C. 关键代码兼容性“避坑”建议 (无真机必做)
* **日期处理**: 严禁使用 `new Date("2024-03-10")`,在 iOS 上会返回 `Invalid Date`。**必须**统一替换为 `new Date("2024/03/10")``new Date(2024, 2, 10)`
* **图片显示**: iOS 微信小程序对 WebP 格式支持有限,生产环境生产建议优先使用 **JPG/PNG** 或确保 CDN 开启了自适应转换。
* **输入框**: iOS 下 `input``focus` 自动拉起键盘可能会造成页面整体上移或错位,务必通过云真机确认“下单备注”等输入区域。
---
## 7. 微信小程序上线必改项 (Final Checklist)
1. **正式 AppID**: 修改 `manifest.json` 中的 `mp-weixin.appid` 为正式 ID。
2. **域名白名单**: 在微信后台添加 Supabase API 域名、图片 CDN 域名、物流查询域名。
3. **用户隐私政策**: 获取头像、位置等接口前,必须在微信后台配置隐私协议,且在 App 内部提供可见入口。
4. **打包优化**: 检查 `unpackage/dist/build/mp-weixin` 的大小,如超过 2MB必须启用“分包加载”。
---
*生成日期2026-03-10*

View File

@@ -62,7 +62,7 @@
<view class="shop-header" @click="goToShop">
<text class="shop-icon">🏪</text>
<text class="shop-name">{{ shopName }}</text>
<text class="arrow-right">></text>
<text class="arrow-right"></text>
</view>
<view v-for="item in orderItems" :key="item.id" class="product-item" @click="goToProduct(item.product_id)">
<image :src="item.image_url != null && item.image_url != '' ? item.image_url : '/static/default-product.png'" class="product-image" mode="aspectFill"/>

View File

@@ -16,12 +16,14 @@
<view class="product-info">
<view class="price-section">
<text class="current-price">¥{{ product.price }}</text>
<text v-if="memberPrice > 0 && memberPrice < product.price" class="member-price-tag">会员价 ¥{{ memberPrice }}</text>
<!-- 会员价功能已禁用 -->
<!-- <text v-if="memberPrice > 0 && memberPrice < product.price" class="member-price-tag">会员价 ¥{{ memberPrice }}</text> -->
<text v-if="product.original_price" class="original-price">¥{{ product.original_price }}</text>
</view>
<view v-if="memberDiscount > 0" class="member-discount-row">
<!-- 会员专享折扣功能已禁用 -->
<!-- <view v-if="memberDiscount > 0" class="member-discount-row">
<text class="member-discount-text">会员专享 {{ memberDiscount }}折优惠</text>
</view>
</view> -->
<text class="product-name">{{ product.name }}</text>
<text class="sales-info">已售{{ product.sales }}件 · 库存{{ product.stock }}件</text>
</view>
@@ -69,7 +71,7 @@
<view class="detail-cell spec-section" @click="showSpecModal" v-if="productSkus.length > 0">
<text class="cell-label">规格</text>
<view class="cell-content">
<text class="spec-selected">{{ selectedSpec ?? '请选择规格' }}</text>
<text class="spec-selected">{{ selectedSpec != '' ? selectedSpec : '请选择规格' }}</text>
</view>
<text class="cell-arrow"></text>
</view>
@@ -135,23 +137,54 @@
</view>
</view>
<!-- 规格选择弹窗 -->
<!-- 规格选择弹窗 (京东风格) -->
<view v-if="showSpec" class="spec-modal" @click="hideSpecModal">
<view class="spec-content" @click.stop>
<view class="spec-header">
<text class="spec-title">选择规格</text>
<text class="close-btn" @click="hideSpecModal">×</text>
<view class="spec-header-jd">
<image :src="getSelectedSkuImage()" class="spec-product-img" mode="aspectFill" />
<view class="spec-info-jd">
<view class="spec-price-row">
<text class="price-symbol">¥</text>
<text class="price-value">{{ getSelectedSkuPrice() }}</text>
</view>
<text class="spec-stock-jd">库存: {{ getSelectedSkuStock() }}件</text>
<text class="spec-choosed-jd">已选: {{ selectedSpec != '' ? selectedSpec : '请选择规格' }}</text>
</view>
<text class="close-btn-jd" @click="hideSpecModal">×</text>
</view>
<scroll-view class="spec-list" direction="vertical">
<view v-for="sku in productSkus" :key="sku.id"
class="spec-item"
:class="{ active: selectedSkuId === sku.id }"
@click="selectSku(sku)">
<text class="spec-name">{{ getSkuSpecText(sku) }}</text>
<text class="spec-price">¥{{ sku.price }}</text>
<text class="spec-stock">库存{{ sku.stock }}</text>
<scroll-view class="spec-list-jd" scroll-y="true">
<view class="spec-group">
<text class="group-title">规格</text>
<view class="group-tags">
<view v-for="sku in productSkus" :key="sku.id"
class="spec-tag"
:class="{ active: selectedSkuId === sku.id }"
@click="selectSku(sku)">
<text class="tag-text">{{ getSkuSpecText(sku) }}</text>
</view>
</view>
</view>
<!-- 数量选择移入弹窗内容 -->
<view class="spec-quantity-row">
<text class="group-title">数量</text>
<view class="quantity-selector-jd">
<view class="q-btn" @click="decreaseQuantity">
<text class="q-btn-text">-</text>
</view>
<input class="q-input" type="number" :value="quantity.toString()" @input="validateQuantity" />
<view class="q-btn" @click="increaseQuantity">
<text class="q-btn-text">+</text>
</view>
</view>
</view>
</scroll-view>
<view class="spec-footer-jd">
<button class="footer-btn cart" @click="addToCart">加入购物车</button>
<button class="footer-btn buy" @click="buyNow">立即购买</button>
</view>
</view>
</view>
@@ -692,7 +725,33 @@ export default {
}
}
uni.hideLoading()
},
getSelectedSkuImage(): string {
if (this.selectedSkuId != '') {
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
if (sku != null && sku.image_url != null && sku.image_url != '') {
return sku.image_url as string
}
}
return this.product.images.length > 0 ? this.product.images[0] : '/static/default-product.png'
},
getSelectedSkuPrice(): string {
if (this.selectedSkuId != '') {
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
if (sku != null) return sku.price.toFixed(2)
}
return this.product.price.toFixed(2)
},
getSelectedSkuStock(): number {
if (this.selectedSkuId != '') {
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
this.showSpecModal()
if (sku != null) return sku.stock
}
return this.product.stock
if (success === true) {
uni.showToast({ title: '领取成功', icon: 'success' })
} else {
@@ -723,7 +782,6 @@ export default {
selectSku(sku: ProductSkuType) {
this.selectedSkuId = sku.id
this.selectedSpec = this.getSkuSpecText(sku)
this.hideSpecModal()
},
getSkuSpecText(sku: ProductSkuType): string {
@@ -746,6 +804,7 @@ export default {
async addToCart() {
if (this.productSkus.length > 0 && (this.selectedSkuId == null || this.selectedSkuId === '')) {
this.showSpecModal()
uni.showToast({
title: '请选择规格',
icon: 'none'
@@ -766,6 +825,7 @@ export default {
if (success === true) {
uni.showToast({ title: '已添加到购物车', icon: 'success' })
this.hideSpecModal()
} else {
console.error('添加购物车返回失败')
uni.showToast({ title: '添加失败,请登录重试', icon: 'none' })
@@ -779,6 +839,7 @@ export default {
buyNow() {
if (this.productSkus.length > 0 && (this.selectedSkuId == null || this.selectedSkuId === '')) {
this.showSpecModal()
uni.showToast({
title: '请选择规格',
icon: 'none'
@@ -1375,70 +1436,200 @@ export default {
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
background-color: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: flex-end; /* UVUE 推荐用 flex 布局对齐 */
justify-content: flex-end;
flex-direction: column;
z-index: 999;
z-index: 1000;
}
.spec-content {
background-color: #fff;
width: 100%;
border-radius: 20rpx 20rpx 0 0;
border-radius: 24rpx 24rpx 0 0;
padding: 30rpx;
display: flex;
flex-direction: column;
max-height: 1000rpx;
max-height: 80vh;
}
.spec-header {
.spec-header-jd {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #eee;
flex-direction: row;
position: relative;
padding-bottom: 30rpx;
border-bottom: 1rpx solid #f2f2f2;
align-items: flex-end;
}
.spec-title {
font-size: 32rpx;
.spec-product-img {
width: 180rpx;
height: 180rpx;
border-radius: 12rpx;
margin-top: -60rpx;
background-color: #fff;
border: 4rpx solid #fff;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
flex-shrink: 0;
}
.spec-info-jd {
flex: 1;
margin-left: 24rpx;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-bottom: 10rpx;
}
.spec-price-row {
display: flex;
flex-direction: row;
align-items: baseline;
color: #fa2c19;
margin-bottom: 8rpx;
}
.price-symbol {
font-size: 24rpx;
font-weight: bold;
}
.price-value {
font-size: 36rpx;
font-weight: bold;
}
.spec-stock-jd {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.spec-choosed-jd {
font-size: 26rpx;
color: #333;
}
.spec-list {
flex: 1;
.close-btn-jd {
font-size: 48rpx;
color: #999;
position: absolute;
right: -10rpx;
top: -10rpx;
padding: 10rpx;
}
.spec-item {
.spec-list-jd {
flex: 1;
overflow-y: hidden;
}
.spec-group {
padding: 30rpx 0;
}
.group-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 24rpx;
display: block;
}
.group-tags {
display: flex;
flex-wrap: wrap;
}
.spec-tag {
background-color: #f6f6f6;
padding: 16rpx 32rpx;
border-radius: 40rpx;
margin-right: 20rpx;
margin-bottom: 20rpx;
border: 2rpx solid #f6f6f6;
}
.spec-tag.active {
background-color: #fcedeb;
border-color: #fa2c19;
}
.spec-tag.active .tag-text {
color: #fa2c19;
}
.tag-text {
font-size: 26rpx;
color: #333;
}
.spec-quantity-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
}
.quantity-selector-jd {
display: flex;
flex-direction: row;
align-items: center;
background-color: #f6f6f6;
border-radius: 8rpx;
}
.q-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
padding: 25rpx 0;
border-bottom: 1rpx solid #f5f5f5;
justify-content: center;
}
.spec-item.active {
background-color: #fff3e0;
.q-btn-text {
font-size: 36rpx;
color: #333;
}
.spec-name {
flex: 1;
.q-input {
width: 80rpx;
text-align: center;
font-size: 28rpx;
color: #333;
}
.spec-price {
font-size: 26rpx;
color: #ff5000;
margin-right: 20rpx;
.spec-footer-jd {
display: flex;
flex-direction: row;
padding: 20rpx 0 10rpx;
justify-content: space-between;
}
.spec-stock {
font-size: 24rpx;
color: #666;
width: 100rpx;
text-align: right;
.footer-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
font-weight: bold;
border-radius: 40rpx;
margin: 0 10rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.footer-btn.cart {
background: linear-gradient(90deg, #ffba0d, #ffc30d);
color: #fff;
}
.footer-btn.buy {
background: linear-gradient(90deg, #f2140c, #f2270c);
color: #fff;
}
/* 功能主治样式 */

View File

@@ -48,13 +48,10 @@
</view>
</view>
<!-- 主内容区域 -->
<scroll-view
<!-- 主内容区域:改为普通 view由页面整体滚动 -->
<view
v-else
direction="vertical"
class="main-content"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="loadMore"
class="main-content"
>
<!-- 初始状态(无搜索词) -->
<view v-if="searchKeyword == '' && showResults == false">
@@ -233,12 +230,13 @@
<!-- 底部安全区域 -->
<view class="safe-area"></view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
import type { Product } from '@/utils/supabaseService.uts'
@@ -251,6 +249,7 @@ const loading = ref(false)
const hasMore = ref(true)
const isError = ref(false) // 错误状态控制
const autoFocus = ref(true)
const activeSort = ref('default') // 当前排序方式: default, sales, price
const priceSortAsc = ref(false) // 价格排序是否为升序
@@ -474,6 +473,8 @@ const performSearch = async (): Promise<void> => {
return
}
console.log('Search execution started for keyword:', keyword)
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
@@ -484,7 +485,9 @@ const performSearch = async (): Promise<void> => {
}
try {
console.log('Calling searchProducts with params:', keyword, currentPage.value, sortBy, ascending)
const prodResp = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
console.log('searchProducts response received:', prodResp.data != null ? prodResp.data.length : 0, 'items')
let shopList: Array<ShopResultType> = []
if (currentPage.value === 1 && activeSort.value === 'default') {
@@ -539,7 +542,7 @@ const performSearch = async (): Promise<void> => {
hasMore.value = prodResp.hasmore
} catch(e) {
console.error('Search failed', e)
console.error('Search failed detailed error:', e)
} finally {
loading.value = false
}
@@ -691,19 +694,18 @@ const loadMore = async (): Promise<void> => {
} else if (activeSort.value === 'default') {
sortBy = 'default'
}
try {
const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
const respData = response.data != null ? response.data : []
for (let i: number = 0; i < respData.length; i++) {
const p = respData[i] as UTSJSONObject
const p = respData[i] as Product
let tag = ''
const tagsRaw = p.get('tags')
const tagsRaw = p.tags
if (tagsRaw != null) {
try {
const tagsStr = p.getString('tags')
const tagsStr = p.tags
if (tagsStr != null) {
const tags = JSON.parse(tagsStr)
const tags = JSON.parse(tagsStr as string)
if (Array.isArray(tags) && tags.length > 0) {
const firstTag = tags[0]
tag = firstTag != null ? (firstTag as string) : ''
@@ -713,14 +715,14 @@ const loadMore = async (): Promise<void> => {
}
const searchItem: SearchResultType = {
id: p.getString('id') ?? '',
name: p.getString('name') ?? '',
image: p.getString('main_image_url') ?? '/static/default.jpg',
price: p.getNumber('base_price') ?? 0,
specification: p.getString('specification') ?? '标准规格',
id: p.id ?? '',
name: p.name ?? '',
image: p.main_image_url ?? '/static/default.jpg',
price: p.base_price ?? 0,
specification: p.specification ?? '标准规格',
tag: tag,
sales: p.getNumber('sale_count') ?? 0,
merchant_id: p.getString('merchant_id') ?? ''
sales: p.sale_count ?? 0,
merchant_id: p.merchant_id ?? ''
}
searchResults.value.push(searchItem)
}
@@ -733,6 +735,12 @@ const loadMore = async (): Promise<void> => {
}
}
onReachBottom(() => {
if (showResults.value) {
loadMore()
}
})
const refreshGuessList = () => {
uni.showLoading({ title: '刷新中' })
setTimeout(() => {
@@ -835,6 +843,7 @@ const goBack = () => {
background-color: #f5f5f5;
display: flex;
flex-direction: column;
min-height: 100vh; /* 确保背景色覆盖全屏 */
}
/* 店铺搜索结果 */
@@ -913,6 +922,7 @@ const goBack = () => {
/* #ifdef APP-PLUS */
padding-top: 0; /* 在App端由style动态控制 */
/* #endif */
flex-shrink: 0; /* 禁止头部被压缩 */
}
.search-bar-container {
@@ -920,7 +930,7 @@ const goBack = () => {
flex-direction: row; /* UVUE 必须显式设置 row */
align-items: center;
padding: 10px 16px;
width: 100%; /* 确保占满宽度 */
box-sizing: border-box;
}
.back-btn {
@@ -932,6 +942,7 @@ const goBack = () => {
width: 32px; /* 固定宽度防止压缩 */
height: 32px;
margin-right: 12px;
flex-shrink: 0;
}
.back-icon {
@@ -1194,16 +1205,30 @@ const goBack = () => {
background: #fff;
border-radius: 8px;
overflow: hidden;
width: 48%;
width: 48%; /* 手机端 2列 */
margin-bottom: 12px;
}
/* 猜测列表响应式,参照 index.uvue 的 hot-products */
@media screen and (min-width: 769px) {
.guess-item {
width: 32%; /* 平板 3列 */
}
}
@media screen and (min-width: 1025px) {
.guess-item {
width: 23%; /* 电脑 4列 */
}
}
.guess-img {
width: 100%;
height: 170px;
border-radius: 8px;
margin-bottom: 8px;
background: #f5f5f5;
object-fit: cover;
}
.guess-name {
@@ -1272,110 +1297,86 @@ const goBack = () => {
color: #333;
}
/* 搜索结果 */
.search-results {
padding-bottom: 20px;
}
.results-header {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
flex-wrap: wrap; /* 允许换行以适应小屏 */
padding: 10px 12px;
background-color: #fff;
margin-bottom: 2px;
}
.results-title {
font-size: 15px;
font-weight: bold;
color: #333;
margin-right: 8px;
}
.filter-tabs {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
flex: 1; /* 自适应填充剩余空间 */
justify-content: flex-end; /* 靠右对齐 */
flex-direction: row;
align-items: center;
flex-wrap: wrap;
justify-content: flex-start;
}
.filter-tab {
font-size: 13px;
color: #666;
padding: 4px 8px; /* 增加点击区域 */
margin-left: 16px;
padding: 8px 12px;
border-radius: 20px;
border: 1px solid #e0e0e0;
transition: all 0.2s ease;
white-space: nowrap;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
}
.filter-tab.active {
color: #4CAF50;
font-weight: bold; /* REPLACED 500 */
background: #ff5000;
color: white;
border-color: #ff5000;
}
.filter-tab:hover {
background: #f5f5f5;
}
/* 搜索结果列表 */
.search-results {
padding-bottom: 20px;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start; /* 核心修复:确保内容不居中缩进 */
}
.results-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background-color: #fff;
margin-bottom: 2px;
width: 100%; /* 核心修复:确保标题栏撑满宽度 */
box-sizing: border-box;
}
.results-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 4px;
}
/* 响应式布局 */
/* 平板设备 (768px以上) */
@media screen and (min-width: 768px) {
.results-list {
padding: 0 16px;
}
.result-item {
width: 32%;
}
.guess-item {
width: 24%;
}
}
/* 桌面设备 (1024px以上) */
@media screen and (min-width: 1024px) {
.results-list {
padding: 0 24px;
max-width: 1200px;
margin: 0 auto;
}
.result-item {
width: 19%;
}
.guess-item {
width: 16%;
}
.product-image {
height: 160px;
}
.guess-grid {
max-width: 1200px;
margin: 0 auto;
}
.main-content {
max-width: 1200px;
margin: 0 auto;
}
}
/* 大屏幕 (1440px以上) */
@media screen and (min-width: 1440px) {
.result-item {
width: 16%;
}
.guess-item {
width: 12%;
}
justify-content: flex-start;
padding: 10px;
width: 100%;
box-sizing: border-box;
margin-top: 5px;
background-color: #fff; /* 为列表添加背景色 */
}
.result-item {
@@ -1384,16 +1385,43 @@ const goBack = () => {
background: #fff;
border-radius: 8px;
overflow: hidden;
width: 48%;
/* width: calc(50% - 20px); 手机端一行2个 */
width: 48%;
margin-bottom: 12px;
margin-right: 2%;
border: 1px solid #f0f0f0; /* 添加微弱边框增加层次感 */
}
/* 电脑端响应式覆盖 - 强制拉伸宽度 */
@media screen and (min-width: 1025px) {
.main-content {
width: 1200px; /* 改为固定宽度或更大百分比 */
max-width: 95%;
margin: 0 auto;
padding: 20px 32px;
}
.result-item {
width: 23%; /* 4列布局 */
margin-right: 2%;
}
}
/* 大桌面端 (1400px以上) */
@media screen and (min-width: 1400px) {
.result-item {
width: 23%; /* 保持一行4个或者根据需要调整为 18% (一行5个) */
}
}
.product-image {
width: 100%;
height: 170px;
border-radius: 8px;
margin-bottom: 8px;
background: #f5f5f5;
height: 170px; /* 与主页一致 */
/* aspect-ratio: 1 / 1; */
object-fit: cover;
background-color: #f5f5f5;
border-radius: 8px;
margin-bottom: 8px;
}
.product-name {

View File

@@ -45,9 +45,17 @@
<!-- 商品列表 -->
<view class="product-section">
<view class="section-title">全部商品</view>
<view class="product-grid">
<view v-for="product in products" :key="product.id" class="product-item" @click="goToProduct(product.id)">
<view class="results-header">
<text class="results-title">全部商品</text>
<view class="filter-tabs">
<text class="filter-tab active">综合</text>
<text class="filter-tab">销量</text>
<text class="filter-tab">价格</text>
</view>
</view>
<view class="results-list">
<view v-for="product in products" :key="product.id" class="result-item" @click="goToProduct(product.id)">
<image :src="product.images[0]" class="product-image" mode="aspectFill" />
<text class="product-name" :lines="2">{{ product.name }}</text>
<view class="product-bottom">
@@ -529,63 +537,63 @@ const goToProduct = (id: string) => {
margin-bottom: 10px;
display: flex;
flex-direction: column;
align-items: center; /* 使 PC 端内容居中 */
}
.shop-banner {
width: 100%;
max-width: 1200px;
height: 200px; /* PC 端稍微加高一点 */
height: 150px;
background-color: #eee;
}
.shop-info-card {
display: flex;
flex-direction: row;
align-items: center;
align-items: flex-start;
padding: 0 15px;
margin-top: -30px;
position: relative;
z-index: 1;
width: 100%;
max-width: 1200px;
box-sizing: border-box;
}
.shop-logo {
width: 80px; /* PC 端稍微加大 */
height: 80px;
width: 60px;
height: 60px;
border-radius: 8px;
border: 2px solid #fff;
background-color: #fff;
margin-right: 15px;
margin-right: 12px;
flex-shrink: 0;
}
.shop-basic-info {
flex: 1;
display: flex;
flex-direction: column;
padding-top: 30px;
padding-top: 35px;
}
.shop-name {
font-size: 22px; /* PC 端字体加大 */
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
margin-bottom: 6px;
line-height: 1.2;
}
.shop-stats {
display: flex;
flex-direction: row;
align-items: center;
}
.stat-item {
font-size: 14px;
font-size: 11px;
color: #666;
margin-right: 15px;
background-color: #f0f0f0;
padding: 4px 10px;
margin-right: 8px;
background-color: #f5f5f5;
padding: 2px 8px;
border-radius: 4px;
}
@@ -593,22 +601,23 @@ const goToProduct = (id: string) => {
display: flex;
flex-direction: row;
align-items: center;
padding-top: 30px;
padding-top: 40px;
flex-shrink: 0;
}
.action-btn {
border-radius: 20px;
margin-left: 15px;
border-radius: 17px;
margin-left: 8px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 8px 24px; /* PC 端按钮加大 */
padding: 4px 12px;
cursor: pointer;
}
.action-text {
font-size: 14px;
font-size: 12px;
}
.chat-btn {
@@ -623,6 +632,7 @@ const goToProduct = (id: string) => {
.follow-btn {
background-color: #ff5000;
border: 1px solid #ff5000;
min-width: 60px;
}
.follow-btn .action-text {
@@ -634,12 +644,70 @@ const goToProduct = (id: string) => {
}
.shop-desc {
color: #666;
padding: 15px 15px 0;
line-height: 1.6;
color: #999;
padding: 10px 15px 0;
line-height: 1.5;
width: 100%;
max-width: 1200px;
box-sizing: border-box;
font-size: 13px;
}
/* PC 端响应式覆盖 */
@media screen and (min-width: 1025px) {
.shop-header {
align-items: center;
}
.shop-banner {
height: 300px;
max-width: 1200px;
}
.shop-info-card {
max-width: 1200px;
margin-top: -40px;
}
.shop-logo {
width: 100px;
height: 100px;
margin-right: 20px;
}
.shop-basic-info {
padding-top: 45px;
}
.shop-name {
font-size: 24px;
margin-bottom: 12px;
}
.shop-stats .stat-item {
font-size: 14px;
padding: 6px 15px;
margin-right: 15px;
}
.shop-actions {
padding-top: 50px;
}
.action-btn {
padding: 8px 24px;
margin-left: 15px;
border-radius: 20px;
}
.action-text {
font-size: 14px;
}
.shop-desc {
max-width: 1200px;
font-size: 14px;
padding: 15px 15px;
}
}
/* Coupon Styles */
@@ -708,67 +776,121 @@ const goToProduct = (id: string) => {
}
.product-section {
padding: 20px;
padding: 20px 0;
width: 100%;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
padding-left: 10px;
border-left: 5px solid #ff5000;
}
.product-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.product-item {
display: flex;
flex-direction: column;
background: #fff;
border-radius: 8px;
overflow: hidden;
width: calc(50% - 10px); /* 默认两列 */
margin-right: 20px;
margin-bottom: 20px;
cursor: pointer;
transition: transform 0.2s;
align-items: flex-start;
}
.product-item:nth-child(2n) {
margin-right: 0;
.results-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background-color: #fff;
margin-bottom: 2px;
width: 100%;
box-sizing: border-box;
}
.product-item:hover {
.results-title {
font-size: 15px;
font-weight: bold;
color: #333;
padding-left: 10px;
border-left: 5px solid #ff5000;
}
.filter-tabs {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
justify-content: flex-start;
}
.filter-tab {
font-size: 13px;
color: #666;
padding: 8px 12px;
border-radius: 20px;
border: 1px solid #e0e0e0;
transition: all 0.2s ease;
white-space: nowrap;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
}
.filter-tab.active {
background: #ff5000;
color: white;
border-color: #ff5000;
}
.results-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
padding: 10px;
width: 100%;
box-sizing: border-box;
margin-top: 5px;
background-color: #fff;
}
.result-item {
display: flex;
flex-direction: column;
background: #fff;
border-radius: 8px;
overflow: hidden;
width: 48% !important;
margin-bottom: 12px;
margin-right: 0 !important;
border: 1px solid #f0f0f0;
box-sizing: border-box;
}
.result-item:nth-child(2n-1) {
margin-right: 4% !important;
}
.result-item:nth-child(2n) {
margin-right: 0 !important;
}
.result-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.product-image {
width: 100%;
height: 200px;
background: #f5f5f5;
object-fit: cover;
width: 100%;
height: 170px;
object-fit: cover;
background-color: #f5f5f5;
border-radius: 8px;
margin-bottom: 8px;
}
.product-name {
font-size: 14px;
color: #333;
margin: 10px 0;
line-height: 1.4;
height: 40px;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 10px;
font-size: 13px;
color: #333;
margin-bottom: 5px;
line-height: 1.4;
height: 36px;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 8px;
}
.product-bottom {
@@ -776,20 +898,20 @@ const goToProduct = (id: string) => {
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 10px 12px;
padding: 0 8px 8px;
}
.product-price {
font-size: 18px;
font-size: 15px;
color: #ff5000;
font-weight: bold;
}
.product-add-btn {
width: 28px;
height: 28px;
width: 24px;
height: 24px;
background-color: #ff5000;
border-radius: 14px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
@@ -797,50 +919,50 @@ const goToProduct = (id: string) => {
.add-icon {
color: #fff;
font-size: 18px;
font-size: 16px;
font-weight: bold;
}
/* PC/Tablet Responsive */
@media (min-width: 768px) {
.product-item {
width: calc(33.33% - 14px) !important;
}
.product-item:nth-child(2n) {
margin-right: 20px !important;
}
.product-item:nth-child(3n) {
margin-right: 0 !important;
}
/* 电脑端响应式覆盖 */
@media screen and (min-width: 1025px) {
.product-section {
max-width: 95%;
width: 1200px;
margin: 0 auto;
}
.result-item {
width: 23%;
margin-right: 2% !important;
}
.result-item:nth-child(2n) {
margin-right: 2% !important;
}
.result-item:nth-child(4n) {
margin-right: 0 !important;
}
}
@media (min-width: 1024px) {
.product-item {
width: calc(20% - 16px) !important; /* 五列 */
}
.product-item:nth-child(3n) {
margin-right: 20px !important;
}
.product-item:nth-child(5n) {
margin-right: 0 !important;
}
/* 大桌面端 (1400px以上) */
@media screen and (min-width: 1400px) {
.result-item {
width: 23.5%;
}
}
.shop-banner {
width: 100%;
height: 200px;
background-color: #f5f5f5;
}
@media screen and (min-width: 1025px) {
.shop-banner {
height: 300px; /* 大屏加宽 Banner */
border-radius: 0 0 20px 20px;
}
}
@media (min-width: 1440px) {
.product-item {
width: calc(16.66% - 17px) !important; /* 六列 */
}
.product-item:nth-child(5n) {
margin-right: 20px !important;
}
.product-item:nth-child(6n) {
margin-right: 0 !important;
}
}
</style>