fix: 商品详情UI优化、提示增强

This commit is contained in:
2025-12-18 10:27:19 +08:00
parent dbc5edb239
commit 4ce856562f

View File

@@ -1,94 +1,112 @@
<template>
<view class="container">
<!-- 顶部搜索栏保持优化后的结构但背景色微调融入原风格 -->
<view class="search-sticky">
<!-- 顶部吸顶区域自定义紧凑布局 -->
<view class="header-sticky-box">
<view class="header-row">
<!-- 1. 左侧自定义下拉筛选触发器 -->
<view class="category-trigger" @click.stop="toggleCategoryMenu">
<text class="trigger-text">{{ currentCategoryLabel }}</text>
<u-icon
:name="showCategoryMenu ? 'arrow-up' : 'arrow-down'"
size="24"
color="#333"
style="margin-left: 6rpx;"
></u-icon>
</view>
<!-- 搜索组件配置 -->
<u-search
v-model="searchKeyword"
placeholder="搜索药品 / 症状 / 品牌"
:show-action="false"
bg-color="#F5F7FA"
placeholder-color="#9CA3AF"
shape="round"
height="72"
@input="handleSearch"
@search="handleSearch"
@clear="clearSearch"
<!-- 2. 右侧搜索栏 (带搜索按钮) -->
<view class="search-box">
<u-search
v-model="searchKeyword"
placeholder="搜索药品/症状"
:show-action="true"
action-text="搜索"
bg-color="#F5F7FA"
placeholder-color="#9CA3AF"
shape="round"
height="64"
:input-style="{fontSize: '26rpx'}"
@custom="handleSearch"
@search="handleSearch"
@clear="clearSearch"
></u-search>
</view>
</view>
<!-- 自定义下拉菜单面板 (绝对定位) -->
<view
class="category-popup"
v-if="showCategoryMenu"
@click.stop=""
>
<template v-slot:icon>
<u-icon name="search" color="#9CA3AF" size="32"></u-icon>
</template>
</u-search>
</view>
<view class="search-sticky">
<u-dropdown>
<u-dropdown-item
v-model="selectedCategory"
title="类目筛选"
:options="categoryOptions"
@change="onCategoryChange">
</u-dropdown-item>
<u-dropdown-item
title=""
</u-dropdown-item>
<u-dropdown-item
title=""
</u-dropdown-item>
</u-dropdown>
<view
class="cat-item"
v-for="(opt, index) in categoryOptions"
:key="opt.value"
:class="{ active: selectedCategory === opt.value }"
@click="selectCategory(opt)"
>
<text>{{ opt.label }}</text>
<u-icon v-if="selectedCategory === opt.value" name="checkmark" color="#2B85E4" size="28"></u-icon>
</view>
</view>
<!-- 下拉菜单遮罩层 -->
<view class="mask-layer" v-if="showCategoryMenu" @click="closeCategoryMenu"></view>
</view>
<!-- 商品列表区域 -->
<view class="list-section">
<!-- 列表项卡片遍历渲染 products 数据 -->
<!-- 列表项卡片 -->
<view
v-for="(item, index) in products"
:key="item.id"
class="list-item"
@click="handClickProduct(item)"
>
<!-- 左侧药品图片容器 -->
<!-- 左侧药品图片 -->
<view class="image-wrapper">
<image
class="item-image"
:src="item.drug.image"
:src="item.drug.image || '/static/mine/avatar_1.png'"
mode="aspectFill"
lazy-load
/>
<!-- 预留缺货蒙层位置 -->
<!-- <view class="mask" v-if="!item.stock">缺货</view> -->
</view>
<!-- 右侧商品详细内容 -->
<!-- 右侧详细内容 -->
<view class="item-content">
<!-- 上半部分基础信息名称标签主治 -->
<view class="top-info">
<!-- 药品名称加粗强调限制2行 -->
<!-- 标题 -->
<view class="item-title">{{ item.drug.drug_name }}</view>
<!-- 标签行包含OTC标识和规格 -->
<!-- 标签行 -->
<view class="tags-row">
<!-- OTC/Rx 标签根据 is_otc 属性动态切换绿/红样式 -->
<text class="tag-otc" :class="item.drug.is_otc ? 'green' : 'red'">{{item.drug.is_otc ? 'OTC' : 'Rx'}}</text>
<!-- 规格文本灰色辅助信息 -->
<text class="tag-spec">{{ item.drug.specification }}</text>
<!-- 动态标签保健食品/OTC/Rx -->
<text
class="tag-label"
:class="getProductTag(item.drug).className"
>
{{ getProductTag(item.drug).label }}
</text>
<!-- 规格 -->
<text class="tag-spec">{{ item.drug.specification || '标准规格' }}</text>
</view>
<!-- 主治功能灰色小字辅助决策 -->
<!-- 主治功能 -->
<view class="item-function" v-if="item.drug.function">
主治{{ item.drug.function }}
</view>
</view>
<!-- 下半部分价格与操作按钮固定在底部 -->
<!-- 底部价格与操作 -->
<view class="item-bottom">
<!-- 价格区域红色强调 -->
<view class="price-section">
<text class="currency">¥</text>
<text class="price-val">{{ item.price }}</text>
</view>
<!-- 购物车按钮阻止冒泡(@click.stop)防止触发进入详情页 -->
<view class="cart-btn" @click.stop="showCartPopup(item)">
<u-icon name="plus" color="#ffffff" size="30"></u-icon>
</view>
@@ -96,13 +114,13 @@
</view>
</view>
<!-- 空状态当没有商品时显示 -->
<!-- 空状态 -->
<view v-if="products.length === 0" class="empty-state">
<u-empty text="暂无相关药品" mode="search"></u-empty>
<u-empty text="暂无相关药品" mode="search" margin-top="100"></u-empty>
</view>
</view>
<!-- 购物车悬浮球固定在页面右下角 -->
<!-- 购物车悬浮球 -->
<view
class="cart-float"
:style="{ left: cartPosition.x + 'px', top: cartPosition.y + 'px' }"
@@ -113,12 +131,11 @@
>
<view class="cart-icon-inner">
<u-icon name="shopping-cart" color="#ffffff" size="44"></u-icon>
<!-- 角标显示购物车数量 -->
<view class="badge" v-if="cartCount > 0">{{ cartCount > 99 ? '99+' : cartCount }}</view>
</view>
</view>
<!-- 购物车底部弹窗选择数量 -->
<!-- 购物车弹窗 -->
<u-popup
v-model="showPopup"
mode="bottom"
@@ -126,8 +143,7 @@
:closeable="true"
@close="closePopup"
>
<view class="popup-content">
<!-- 弹窗头部商品简要信息 -->
<view class="popup-content safe-area-inset-bottom">
<view class="product-header-row">
<image class="popup-img" :src="getProductImage" mode="aspectFill"></image>
<view class="popup-info">
@@ -137,7 +153,6 @@
</view>
</view>
<!-- 数量选择器 -->
<view class="quantity-section">
<text class="label">购买数量</text>
<u-number-box
@@ -149,55 +164,60 @@
></u-number-box>
</view>
<!-- 底部确认按钮 -->
<view class="popup-footer">
<button class="action-btn add-cart" @click="confirmAddToCart">加入购物车</button>
</view>
</view>
</u-popup>
<u-toast ref="uToast" />
</view>
</template>
<script>
// 引入API接口
import { getStoreDrugListBySalespersonApi } from "@/request/api/product";
import { getCartCountsApi, saveCartApi } from "@/request/api/cart";
export default {
data() {
return {
products: [], // 商品数据(从后端获取)
searchKeyword: "", // 搜索关键词
selectedCategory: 'all', // 选中的类目
categoryOptions: [ // 类目选项
products: [],
searchKeyword: "",
// 自定义下拉菜单相关
selectedCategory: 'all',
showCategoryMenu: false, // 控制下拉菜单显示
categoryOptions: [
{ label: '全部', value: 'all' },
{ label: 'OTC', value: 'otc' },
{ label: '处方药', value: 'prescription' },
{ label: '保健品', value: 'health_food' }
{ label: '保健品', value: 'health_food' }
],
scrollViewHeight: 0, // 滚动区域高度(预留)
doctorId: 0, // 医生ID参数
r_type: 0, // 注册类型参数
cartCount: 0, // 购物车总数量
// 购物车悬浮球位置
doctorId: 0,
r_type: 0,
cartCount: 0,
cartPosition: { x: 0, y: 0 },
// 拖拽相关状态
cartMove: { startX: 0, startY: 0, moving: false },
showPopup: false, // 控制弹窗显示
currentProduct: {}, // 当前选中的商品
quantity: 1 // 购买数量
showPopup: false,
currentProduct: {},
quantity: 1
};
},
computed: {
// 计算属性:安全获取当前选中商品的图片、名称、规格
// 获取当前选中的分类标签
currentCategoryLabel() {
const target = this.categoryOptions.find(item => item.value === this.selectedCategory);
return target ? target.label : '分类';
},
getProductImage() {
return this.currentProduct.drug && this.currentProduct.drug.image ? this.currentProduct.drug.image : '';
return this.currentProduct.drug && this.currentProduct.drug.image ? this.currentProduct.drug.image : '/static/mine/avatar_1.png';
},
getProductName() {
return this.currentProduct.drug && this.currentProduct.drug.drug_name ? this.currentProduct.drug.drug_name : '';
},
getProductSpec() {
return this.currentProduct.drug && this.currentProduct.drug.specification ? this.currentProduct.drug.specification : '';
return this.currentProduct.drug && this.currentProduct.drug.specification ? this.currentProduct.drug.specification : '标准规格';
}
},
onLoad(e) {
@@ -205,24 +225,45 @@ export default {
this.r_type = e.register_type;
},
methods: {
// 获取商品列表
// 切换下拉菜单显示
toggleCategoryMenu() {
this.showCategoryMenu = !this.showCategoryMenu;
},
// 关闭下拉菜单
closeCategoryMenu() {
this.showCategoryMenu = false;
},
// 选择分类
selectCategory(item) {
this.selectedCategory = item.value;
this.closeCategoryMenu();
this.getList(); // 触发列表更新
},
// 核心标签判断逻辑
getProductTag(drug) {
if (drug.type === 3) {
return { label: '保健食品', className: 'tag-orange' };
}
if (drug.is_otc) {
return { label: 'OTC', className: 'tag-green' };
} else {
return { label: 'Rx', className: 'tag-red' };
}
},
getList() {
const params = {
store_id: uni.getStorageSync('store_id') || '11001',
page: 1,
limit: 100
};
// 添加类目筛选
if (this.selectedCategory !== 'all') {
params.category = this.selectedCategory;
}
// 添加搜索关键字
if (this.searchKeyword) {
params.keyword = this.searchKeyword;
}
getStoreDrugListBySalespersonApi(params).then((res) => {
if (res.data.code == 0) {
this.products = res.data.result.records || [];
@@ -230,36 +271,24 @@ export default {
}
})
},
// 处理搜索调用后端API
// 执行搜索(点击按钮或回车)
handleSearch() {
// 防抖处理,避免频繁请求
clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
this.getList();
}, 500);
this.getList();
},
// 清空搜索
clearSearch() {
this.searchKeyword = "";
this.getList();
},
// 类目改变
onCategoryChange(value) {
this.selectedCategory = value;
this.getList();
},
// 计算可视区域高度
calculateScrollViewHeight() {
const systemInfo = uni.getSystemInfoSync();
this.scrollViewHeight = systemInfo.windowHeight - 160;
},
// 点击商品进入详情页
handClickProduct(item) {
uni.navigateTo({
url: `/subPackages/product/symptoms?id=${item.drug.id}&drug_name=${item.drug.drug_name}&is_otc=${item.drug.is_otc}&image=${item.drug.image}&price=${item.price}`
});
},
// 初始化悬浮球位置:右下角
// 悬浮球逻辑
initCartPosition() {
const systemInfo = uni.getSystemInfoSync();
this.cartPosition = {
@@ -267,13 +296,11 @@ export default {
y: systemInfo.windowHeight - 200
};
},
// 拖拽开始
onTouchStart(e) {
this.cartMove.startX = e.touches[0].clientX;
this.cartMove.startY = e.touches[0].clientY;
this.cartMove.moving = false;
},
// 拖拽中
onTouchMove(e) {
this.cartMove.moving = true;
const moveX = e.touches[0].clientX - this.cartMove.startX;
@@ -283,7 +310,6 @@ export default {
this.cartMove.startX = e.touches[0].clientX;
this.cartMove.startY = e.touches[0].clientY;
},
// 拖拽结束:吸边处理
onTouchEnd() {
if (!this.cartMove.moving) return;
const systemInfo = uni.getSystemInfoSync();
@@ -291,10 +317,8 @@ export default {
const screenHeight = systemInfo.windowHeight;
let finalX = this.cartPosition.x;
let finalY = this.cartPosition.y;
// 边界限制
finalX = Math.max(10, Math.min(finalX, screenWidth - 70));
finalY = Math.max(10, Math.min(finalY, screenHeight - 70));
// 左右吸附
if (finalX < screenWidth / 2) {
finalX = 20;
} else {
@@ -302,38 +326,29 @@ export default {
}
this.cartPosition = { x: finalX, y: finalY };
},
// 跳转购物车页面
goToCart() {
uni.navigateTo({ url: '/subPackages/shop/shop-cate' });
},
// 显示加购弹窗
showCartPopup(item) {
this.currentProduct = item;
this.quantity = 1;
this.showPopup = true;
},
// 关闭弹窗
closePopup() {
this.showPopup = false;
},
quantityChange(value) {},
// 确认加入购物车
confirmAddToCart() {
try {
saveCartApi({
store_id: uni.getStorageSync('store_id') || '11001',
drug_id: this.currentProduct.drug.id,
quantity: this.quantity
}).then(() => {
this.updateCartCount();
this.closePopup();
uni.showToast({ title: `已加入购物车`, icon: 'success' });
})
} catch (error) {
uni.showToast({ title: '失败', icon: 'none' });
}
saveCartApi({
store_id: uni.getStorageSync('store_id') || '11001',
drug_id: this.currentProduct.drug.id,
quantity: this.quantity
}).then(() => {
this.updateCartCount();
this.closePopup();
this.$refs.uToast.show({ title: '已加入购物车', type: 'success' });
})
},
// 更新购物车数量
updateCartCount() {
let that = this;
getCartCountsApi({
@@ -349,19 +364,11 @@ export default {
},
onShow() {
this.getList();
this.calculateScrollViewHeight();
this.initCartPosition();
this.updateCartCount();
},
onUnload() {
// 清理定时器
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
},
onReady() {
uni.onWindowResize(() => {
this.calculateScrollViewHeight();
this.initCartPosition();
});
},
@@ -372,7 +379,6 @@ export default {
</script>
<style lang="scss" scoped>
/* 页面主容器:保留原有品牌色(淡蓝渐变) */
.container {
background: linear-gradient(180deg, #f0f5ff 0%, #ffffff 100%);
min-height: 100vh;
@@ -380,117 +386,170 @@ export default {
padding-bottom: 40rpx;
}
/* 顶部搜索栏:吸顶效果 */
.search-sticky {
/* 顶部吸顶盒子 */
.header-sticky-box {
position: sticky;
top: 0;
z-index: 100;
/* 使用高透明度白色背景,既保证文字可读,又透出底部渐变 */
background: rgba(255, 255, 255, 0.95);
padding: 16rpx 24rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.02);
.search-section {
background: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
padding: 20rpx 24rpx;
.header-row {
display: flex;
align-items: center;
gap: 16rpx;
.category-filter {
flex-shrink: 0;
width: 200rpx;
}
/* 自定义分类触发器 */
.category-trigger {
display: flex;
align-items: center;
margin-right: 20rpx;
padding: 10rpx 0;
.trigger-text {
font-size: 30rpx;
font-weight: 600;
color: #333;
max-width: 140rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
/* 搜索框容器 */
.search-box {
flex: 1;
}
/* 自定义下拉弹窗 */
.category-popup {
position: absolute;
top: 100%; /* 吸附在 header 下方 */
left: 24rpx; /* 对齐左边距 */
width: 240rpx;
background: #fff;
border-radius: 12rpx;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.1);
z-index: 101;
padding: 10rpx 0;
.cat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
font-size: 28rpx;
color: #333;
&:active {
background: #f8f8f8;
}
&.active {
color: #2B85E4;
font-weight: bold;
background: #F0F7FF;
}
}
}
/* 遮罩层 */
.mask-layer {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0); /* 透明遮罩,只用于点击关闭 */
z-index: 99;
}
}
.list-section {
padding: 24rpx;
}
/* --- 核心优化:商品卡片样式 --- */
/* 商品卡片 */
.list-item {
display: flex; /* 使用 Flex 布局实现左图右文 */
background: #ffffff; /* 纯白背景突出商品 */
border-radius: 20rpx; /* 大圆角,增加亲和力 */
padding: 24rpx; /* 内边距,保持呼吸感 */
margin-bottom: 20rpx; /* 卡片间距 */
/* 阴影优化:使用品牌蓝色的极淡阴影,清爽不脏 */
display: flex;
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(65, 117, 238, 0.08);
transition: transform 0.1s; /* 添加点击缩放动画 */
transition: transform 0.1s;
&:active {
transform: scale(0.99); /* 点击时的微缩反馈 */
transform: scale(0.99);
}
}
/* 左侧图片容器 */
.image-wrapper {
position: relative;
margin-right: 24rpx; /* 图片与文字的间距 */
margin-right: 24rpx;
.item-image {
width: 200rpx; /* 固定宽度 */
height: 200rpx; /* 固定高度,正方形 */
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
background-color: #f8f8f8; /* 图片加载前的占位背景 */
background-color: #f8f8f8;
}
}
/* 右侧内容区域 */
.item-content {
flex: 1; /* 占满剩余空间 */
flex: 1;
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: space-between; /* 上下两端对齐:顶部是信息,底部是价格 */
flex-direction: column;
justify-content: space-between;
overflow: hidden;
/* 上部信息区 */
.top-info {
.item-title {
font-size: 30rpx;
font-weight: 700; /* 标题加粗 */
color: #1F2937; /* 深黑灰,易读 */
margin-bottom: 10rpx;
font-weight: 700;
color: #1F2937;
margin-bottom: 12rpx;
line-height: 1.4;
/* 限制显示两行,超出省略 */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 标签行布局 */
.tags-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
margin-bottom: 12rpx;
flex-wrap: wrap;
/* OTC/Rx 标签样式 */
.tag-otc {
/* 通用标签基础样式 */
.tag-label {
font-size: 20rpx;
padding: 0 8rpx;
border-radius: 4rpx;
margin-right: 10rpx;
padding: 2rpx 10rpx;
border-radius: 6rpx;
margin-right: 12rpx;
border: 1px solid;
line-height: 28rpx;
font-weight: 500;
/* 绿/红 配色方案 */
&.green { color: #10B981; border-color: #10B981; }
&.red { color: #EF4444; border-color: #EF4444; }
/* 颜色变种 */
&.tag-green { color: #10B981; border-color: #10B981; background: #ECFDF5; }
&.tag-red { color: #EF4444; border-color: #EF4444; background: #FEF2F2; }
&.tag-orange { color: #F59E0B; border-color: #F59E0B; background: #FFFBEB; }
}
/* 规格文本样式 */
.tag-spec {
font-size: 24rpx;
color: #6B7280; /* 灰色 */
font-size: 22rpx;
color: #9CA3AF;
background: #F3F4F6;
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
}
/* 主治功能描述 */
.item-function {
font-size: 22rpx;
color: #9CA3AF; /* 浅灰,弱化显示 */
/* 限制单行显示 */
color: #9CA3AF;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
@@ -498,46 +557,38 @@ export default {
}
}
/* 底部价格与按钮区 */
.item-bottom {
display: flex;
justify-content: space-between; /* 价格在左,按钮在右 */
align-items: flex-end; /* 底部对齐 */
margin-top: 10rpx;
justify-content: space-between;
align-items: flex-end;
/* 价格样式 */
.price-section {
color: #EF4444; /* 价格红 */
color: #EF4444;
font-weight: bold;
.currency { font-size: 24rpx; margin-right: 2rpx; }
.price-val { font-size: 36rpx; } /* 数字放大 */
.price-val { font-size: 36rpx; font-family: 'DIN Alternate', sans-serif; }
}
/* 购物车按钮样式 */
.cart-btn {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
/* 按钮背景:保持品牌蓝色渐变 */
background: linear-gradient(135deg, #4A90E2, #5B8FF9);
display: flex;
align-items: center;
justify-content: center;
/* 按钮投影 */
box-shadow: 0 4rpx 10rpx rgba(74, 144, 226, 0.3);
}
}
}
/* 购物车悬浮球样式 */
/* 购物车悬浮球 */
.cart-float {
position: fixed;
z-index: 999;
.cart-icon-inner {
width: 100rpx;
height: 100rpx;
/* 悬浮球背景:品牌蓝色渐变 */
background: linear-gradient(135deg, #4A90E2, #5B8FF9);
border-radius: 50%;
display: flex;
@@ -545,8 +596,6 @@ export default {
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(74, 144, 226, 0.4);
position: relative;
/* 数量角标 */
.badge {
position: absolute;
top: -6rpx;
@@ -561,15 +610,15 @@ export default {
}
}
/* 弹窗内容样式 */
/* 弹窗内容 */
.popup-content {
padding: 30rpx 30rpx 60rpx;
padding: 30rpx 30rpx 40rpx;
background: #fff;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
.product-header-row {
display: flex;
margin-bottom: 40rpx;
.popup-img {
width: 180rpx;
height: 180rpx;
@@ -577,13 +626,11 @@ export default {
margin-right: 24rpx;
background: #f8f8f8;
}
.popup-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
.price-row {
color: #EF4444;
font-size: 28rpx;
@@ -600,12 +647,7 @@ export default {
justify-content: space-between;
align-items: center;
margin-bottom: 60rpx;
.label {
font-size: 30rpx;
color: #1F2937;
font-weight: 500;
}
.label { font-size: 30rpx; color: #1F2937; font-weight: 500; }
}
.popup-footer {
@@ -617,18 +659,14 @@ export default {
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 600;
&.add-cart {
background: linear-gradient(90deg, #4A90E2, #5B8FF9);
color: #fff;
}
&:active { opacity: 0.9; }
}
}
}
.empty-state {
margin-top: 100rpx;
}
</style>
.empty-state { margin-top: 100rpx; }
</style>