consumer模块完成95%,在和商家端对接聊天购物闭环

This commit is contained in:
2026-02-06 17:10:31 +08:00
parent 06b7369494
commit e2f1dfb097
1454 changed files with 5425 additions and 210555 deletions

View File

@@ -15,12 +15,12 @@
<text class="search-placeholder">请输入药品名称、症状或品牌</text>
<!-- 扫码图标 -->
<view class="nav-icon-btn" @click="onScan">
<view class="nav-icon-btn" @click.stop="onScan">
<text class="nav-icon">🔳</text>
</view>
<!-- 相机图标 -->
<view class="nav-camera-btn" @click="onCamera">
<view class="nav-camera-btn" @click.stop="onCamera">
<text class="nav-camera-icon">📷</text>
</view>
@@ -47,40 +47,12 @@
@scroll="handleScroll"
>
<!-- 健康资讯轮播 (Moved Up) -->
<!-- 健康资讯轮播 (Hidden) -->
<!--
<view class="health-news">
<view class="news-header">
<text class="news-title">健康资讯</text>
<text class="news-more" @click="navigateToNews">更多 ></text>
</view>
<swiper
class="news-swiper"
:autoplay="true"
:interval="4000"
:duration="500"
:circular="true"
:indicator-dots="true"
indicator-color="rgba(255,255,255,0.3)"
indicator-active-color="#4CAF50"
>
<swiper-item
v-for="news in healthNews"
:key="news.id"
class="news-item"
>
<view class="news-content" @click="viewNewsDetail(news)">
<image
class="news-image"
:src="news.image"
mode="aspectFill"
/>
<view class="news-overlay">
<text class="news-tag">{{ news.tag }}</text>
<text class="news-caption">{{ news.title }}</text>
</view>
</view>
</swiper-item>
</swiper>
...
</view>
-->
<!-- 智能健康卡片 (Hidden) -->
<!-- <view class="smart-health-card" :style="{ marginTop: (statusBarHeight + 44 + 10) + 'px' }">
@@ -98,12 +70,19 @@
</view> -->
<!-- 智能分类网格 - 完全响应式 -->
<view class="smart-categories">
<view class="smart-categories" :style="{ marginTop: (statusBarHeight + 44 + 10) + 'px' }">
<view class="section-header">
<text class="section-title">智能分类</text>
<text class="section-desc">快速定位所需药品</text>
<view class="category-tabs-pills">
<view :class="['tab-pill', { active: categoryTab == 'category' }]" @click="categoryTab = 'category'">
<text class="tab-text">智能分类</text>
</view>
<view :class="['tab-pill', { active: categoryTab == 'brand' }]" @click="categoryTab = 'brand'">
<text class="tab-text">品牌甄选</text>
</view>
</view>
<text class="section-desc">快速定位</text>
</view>
<view class="category-grid">
<view class="category-grid" v-if="categoryTab === 'category'">
<view
v-for="category in categories"
:key="category.id"
@@ -118,6 +97,22 @@
<text class="card-desc">{{ category.desc }}</text>
</view>
</view>
<view class="category-grid" v-else>
<view
v-for="brand in brands"
:key="brand.id"
class="category-card"
@click="switchBrand(brand)"
style="--card-color: #5785e5"
>
<image v-if="brand.logo_url" :src="brand.logo_url" mode="aspectFit" class="brand-logo" style="width: 40px; height: 40px; border-radius: 20px;" />
<view v-else class="card-icon">
<text>🏢</text>
</view>
<text class="card-name">{{ brand.name }}</text>
</view>
</view>
</view>
<!-- 健康资讯轮播 (Original Position - Removed) -->
@@ -210,7 +205,7 @@
</view>
<view class="product-action">
<view class="cart-btn" @click="addToCart(product)">
<view class="cart-btn" @click.stop="addToCart(product)">
<text class="cart-icon">+</text>
<text class="cart-text">加入购物车</text>
</view>
@@ -276,7 +271,7 @@
import { ref, reactive, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import supabaseService from '@/utils/supabaseService.uts'
import type { Product, Category } from '@/utils/supabaseService.uts'
import type { Product, Category, Brand } from '@/utils/supabaseService.uts'
import { getCurrentUser } from '@/utils/store.uts'
// 响应式数据
@@ -305,7 +300,9 @@ const scrollThreshold = 30 // 降低滚动阈值,使其更灵敏
const scrollingUp = ref(false)
// 分类数据 - 从Supabase获取
const categoryTab = ref<string>('category')
const categories = ref<Category[]>([])
const brands = ref<Brand[]>([])
// 排序标签
const sortTabs = [
@@ -344,26 +341,34 @@ const loadCategories = async () => {
try {
const categoriesData = await supabaseService.getCategories()
// 映射字段根据ml_categories表结构映射
categories.value = categoriesData.map((cat: any) => ({
const mappedCategories = categoriesData.map((cat: any) => ({
id: cat.id,
name: cat.name,
icon: cat.icon_url || '📦', // 使用icon_url字段
desc: cat.description || '', // 使用description字段
color: '#4CAF50' // 默认颜色表中可能没有color字段
}))
// 保持原始顺序或按ID排序移除随机打乱
categories.value = mappedCategories
} catch (error) {
console.error('加载分类数据失败:', error)
// 如果加载失败,使用默认分类作为后备
categories.value = [
{ id: 'cold', name: '感冒发烧', icon: '🤧', desc: '解热镇痛', color: '#2196F3' },
{ id: 'stomach', name: '肠胃用药', icon: '🤢', desc: '消化系统', color: '#4CAF50' },
{ id: 'pain', name: '止痛消炎', icon: '💊', desc: '镇痛消炎', color: '#F44336' },
{ id: 'skin', name: '皮肤用药', icon: '🤕', desc: '皮肤护理', color: '#9C27B0' },
{ id: 'vitamin', name: '维生素', icon: '🍊', desc: '营养补充', color: '#FF9800' }
]
categories.value = []
}
}
// 获取品牌数据
const loadBrands = async () => {
try {
const brandsData = await supabaseService.getBrands()
// 保持原始顺序
brands.value = brandsData
} catch (e) {
console.error('加载品牌失败:', e)
brands.value = []
}
}
// 获取热销商品(根据当前排序方式)
const loadHotProducts = async (targetLimit: number = 6) => {
try {
@@ -403,6 +408,10 @@ const loadHotProducts = async (targetLimit: number = 6) => {
}
console.log('加载到的商品数量:', products.length)
if (products.length > 0) {
console.log('Sample Product Merchant IDs:')
products.slice(0, 3).forEach(p => console.log(` - Product: ${p.name}, MerchantID: ${p.merchant_id}`))
}
hotProducts.value = products
} catch (error) {
console.error('加载热销商品失败:', error)
@@ -425,6 +434,7 @@ const initData = async () => {
console.error('加载用户资料失败:', error)
}
await loadCategories()
await loadBrands()
await loadHotProducts()
await loadRecommendedProducts()
}
@@ -581,8 +591,8 @@ const switchCategory = (category: any) => {
console.log('=== switchCategory函数开始执行 ===')
console.log('分类ID:', category.id, '分类名称:', category.name)
// 清除可能存在的旧数据
uni.removeStorageSync('selectedCategory')
// 使用Storage传递参数确保switchTab后能被读取
uni.setStorageSync('selectedCategory', category.id)
// 生成唯一的时间戳和随机参数,确保每次跳转都是新的页面
const timestamp = Date.now()
@@ -590,26 +600,24 @@ const switchCategory = (category: any) => {
// 构建带参数的URL直接通过URL传递分类信息
const url = `/pages/mall/consumer/category?categoryId=${category.id}&name=${encodeURIComponent(category.name)}&timestamp=${timestamp}&random=${randomParam}`
console.log('跳转URL:', url)
console.log('分类ID参数:', category.id)
console.log('时间戳:', timestamp)
console.log('随机参数:', randomParam)
// 使用uni.reLaunch跳转到分类页面关闭所有页面并打开新页面
// 这样可以确保每次跳转都是全新的页面实例,避免页面缓存问题
// 虽然这会关闭当前主页,但可以确保分类页面总是重新加载
uni.reLaunch({
url: url,
success: () => {
console.log('✅ 使用reLaunch跳转到分类页面成功')
console.log('=== switchCategory函数执行完成 ===')
},
fail: (err) => {
console.error('❌ 跳转到分类页面失败:', err)
console.log('=== switchCategory函数执行完成 ===')
}
})
uni.switchTab({
url: '/pages/mall/consumer/category',
success: () => {
// 通过 event channel 或 globalData 传递
const app = getApp()
if (app.globalData != null) {
app.globalData['selectedCategory'] = category.id
}
}
})
}
const switchBrand = (brand: Brand) => {
// 假设跳转到搜索结果页或者分类页带 filter
uni.navigateTo({
url: `/pages/mall/consumer/search?keyword=${encodeURIComponent(brand.name)}&type=brand&brandId=${brand.id}`
})
}
// 切换排序
@@ -635,15 +643,27 @@ const viewNewsDetail = (news: any) => {
}
// 下拉刷新
const onRefresh = () => {
const onRefresh = async () => {
refreshing.value = true
setTimeout(() => {
refreshing.value = false
uni.showToast({
title: '刷新成功',
icon: 'success'
})
}, 1500)
try {
// 重新加载数据
await initData()
} catch (e) {
console.error('刷新数据失败:', e)
} finally {
// 延迟关闭刷新动画,确保用户能看到刷新过程
setTimeout(() => {
refreshing.value = false
// 延迟显示提示,避免与动画冲突
setTimeout(() => {
uni.showToast({
title: '刷新成功',
icon: 'success'
})
}, 200)
}, 800)
}
}
// 加载更多
@@ -692,8 +712,7 @@ const loadMore = async () => {
}
// 添加到购物车
const addToCart = async (product: any, e: any | null = null) => {
e?.stopPropagation()
const addToCart = async (product: any) => {
uni.showLoading({ title: '添加中...' })
try {
// 尝试调用 Supabase 服务添加
@@ -750,44 +769,6 @@ const navigateToPrescription = () => uni.navigateTo({ url: '/pages/medicine/pres
const navigateToOTC = () => uni.navigateTo({ url: '/pages/medicine/otc' })
const navigateToHealthTools = () => uni.navigateTo({ url: '/pages/medicine/tools' })
const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders' })
// 扫码功能
const onScan = (e: any | null) => {
e?.stopPropagation()
uni.scanCode({
success: (res) => {
console.log('扫码结果:' + res.result)
uni.showToast({
title: '扫码成功',
icon: 'success'
})
// 这里可以添加基于扫码结果的逻辑,比如跳转到商品详情
},
fail: (err) => {
console.error('扫码失败:', err)
}
})
}
// 相机功能e: any | null) => {
e?.stopPropagation()
const onCamera = () => {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: (res) => {
console.log('拍照结果:', res.tempFilePaths)
uni.showToast({
title: '拍摄成功',
icon: 'success'
})
// 这里可以添加基于图片的逻辑,比如图搜
},
fail: (err) => {
console.error('拍照失败:', err)
}
})
}
</script>
<style>
@@ -1004,10 +985,51 @@ const onCamera = () => {
margin-bottom: 20px;
}
.category-tabs-pills {
display: flex;
flex-direction: row;
background-color: #f0f2f5;
padding: 3px;
border-radius: 20px;
align-items: center;
}
.tab-pill {
padding: 6px 18px;
border-radius: 17px;
transition: all 0.3s;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.tab-pill.active {
background-color: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.tab-text {
font-size: 14px;
color: #888;
font-weight: 500;
}
.tab-pill.active .tab-text {
color: #4CAF50;
font-weight: 600;
}
.section-title {
font-size: 18px;
font-weight: bold;
color: #333;
color: #666;
transition: color 0.3s;
}
.section-title.active {
color: #4CAF50;
font-size: 20px;
}
.section-desc {