20260123
This commit is contained in:
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "i18n",
|
||||
"version": "1.0.0",
|
||||
"main": "index.uts",
|
||||
"types": "index.uts",
|
||||
"uni_modules": {
|
||||
"uni_modules": true
|
||||
}
|
||||
}
|
||||
834
pages/mall/consumer/category - 副本 (2).uvue
Normal file
834
pages/mall/consumer/category - 副本 (2).uvue
Normal file
@@ -0,0 +1,834 @@
|
||||
<!-- pages/mall/consumer/category.uvue -->
|
||||
<template>
|
||||
<view class="category-page">
|
||||
<!-- 顶部搜索栏 -->
|
||||
<view class="search-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="search-container">
|
||||
<view class="search-box" @click="navigateToSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
<text class="search-placeholder">症状/药品/品牌智能搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类内容区 -->
|
||||
<view class="category-content">
|
||||
<!-- 左侧一级分类 -->
|
||||
<scroll-view scroll-y class="primary-category">
|
||||
<view
|
||||
v-for="item in primaryCategories"
|
||||
:key="item.id"
|
||||
:class="['primary-item', { active: activePrimary === item.id }]"
|
||||
@click="selectPrimaryCategory(item.id)"
|
||||
:style="{ backgroundColor: activePrimary === item.id ? item.color : 'transparent' }"
|
||||
>
|
||||
<text class="primary-icon">{{ item.icon }}</text>
|
||||
<text class="primary-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧商品列表 -->
|
||||
<scroll-view scroll-y class="product-content">
|
||||
<!-- 分类标题 -->
|
||||
<view class="category-header">
|
||||
<text class="category-title">{{ currentCategoryName }}</text>
|
||||
<text class="category-desc">{{ currentCategoryDesc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 商品网格 -->
|
||||
<view v-if="productList.length > 0" class="product-grid">
|
||||
<view
|
||||
v-for="product in productList"
|
||||
:key="product.id"
|
||||
class="product-card"
|
||||
@click="navigateToProduct(product)"
|
||||
>
|
||||
<view class="product-badge" v-if="product.badge">{{ product.badge }}</view>
|
||||
<image
|
||||
class="product-image"
|
||||
:src="product.image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-spec">{{ product.specification }}</text>
|
||||
|
||||
<view class="price-section">
|
||||
<view class="current-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{ product.price }}</text>
|
||||
</view>
|
||||
<text class="original-price" v-if="product.originalPrice > product.price">
|
||||
¥{{ product.originalPrice }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="product-meta">
|
||||
<text class="manufacturer">{{ product.manufacturer }}</text>
|
||||
<view class="sales-info">
|
||||
<text class="sales-count">已售{{ product.sales }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="empty-state">
|
||||
<text class="empty-icon">💊</text>
|
||||
<text class="empty-text">暂无相关药品</text>
|
||||
<text class="empty-desc">该分类下暂无商品,敬请期待</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="hasMore" class="load-more">
|
||||
<text class="load-text">上拉加载更多</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const primaryCategories = ref<any[]>([])
|
||||
const productList = ref<any[]>([])
|
||||
const activePrimary = ref<string>('cold')
|
||||
const cartCount = ref(3)
|
||||
const hasMore = ref(true)
|
||||
|
||||
// 获取当前分类信息
|
||||
const currentCategoryName = ref('感冒发烧')
|
||||
const currentCategoryDesc = ref('解热镇痛')
|
||||
|
||||
// 医药分类数据(与主页一致)
|
||||
const medicineCategories = [
|
||||
{ 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' },
|
||||
{ id: 'chronic', name: '慢性病', icon: '🫀', desc: '长期管理', color: '#795548' },
|
||||
{ id: 'child', name: '儿童用药', icon: '👶', desc: '儿童专用', color: '#00BCD4' },
|
||||
{ id: 'external', name: '外用药品', icon: '🧴', desc: '外用制剂', color: '#8BC34A' },
|
||||
{ id: 'device', name: '医疗器械', icon: '🩺', desc: '医疗设备', color: '#607D8B' },
|
||||
{ id: 'health', name: '健康食品', icon: '🥗', desc: '保健食品', color: '#FFC107' }
|
||||
]
|
||||
|
||||
// Mock 商品数据
|
||||
const mockProducts = {
|
||||
cold: [
|
||||
{
|
||||
id: 'cold1',
|
||||
name: '布洛芬缓释胶囊',
|
||||
specification: '0.3g*24粒',
|
||||
price: 18.5,
|
||||
originalPrice: 25.8,
|
||||
image: 'https://picsum.photos/300/300?random=cold1',
|
||||
manufacturer: '修正药业',
|
||||
sales: 2560,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'cold2',
|
||||
name: '板蓝根颗粒',
|
||||
specification: '10g*20袋',
|
||||
price: 22.8,
|
||||
originalPrice: 29.9,
|
||||
image: 'https://picsum.photos/300/300?random=cold2',
|
||||
manufacturer: '白云山',
|
||||
sales: 1890,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'cold3',
|
||||
name: '连花清瘟胶囊',
|
||||
specification: '0.35g*36粒',
|
||||
price: 42.8,
|
||||
originalPrice: 48.0,
|
||||
image: 'https://picsum.photos/300/300?random=cold3',
|
||||
manufacturer: '以岭药业',
|
||||
sales: 3200,
|
||||
badge: '爆款'
|
||||
},
|
||||
{
|
||||
id: 'cold4',
|
||||
name: '对乙酰氨基酚片',
|
||||
specification: '0.5g*12片',
|
||||
price: 8.9,
|
||||
originalPrice: 12.0,
|
||||
image: 'https://picsum.photos/300/300?random=cold4',
|
||||
manufacturer: '强生制药',
|
||||
sales: 1420,
|
||||
badge: '特价'
|
||||
},
|
||||
{
|
||||
id: 'cold5',
|
||||
name: '感冒清热颗粒',
|
||||
specification: '3g*10袋',
|
||||
price: 16.5,
|
||||
originalPrice: 19.9,
|
||||
image: 'https://picsum.photos/300/300?random=cold5',
|
||||
manufacturer: '同仁堂',
|
||||
sales: 980,
|
||||
badge: '新品'
|
||||
},
|
||||
{
|
||||
id: 'cold6',
|
||||
name: '复方氨酚烷胺片',
|
||||
specification: '10片/盒',
|
||||
price: 12.8,
|
||||
originalPrice: 15.0,
|
||||
image: 'https://picsum.photos/300/300?random=cold6',
|
||||
manufacturer: '三九医药',
|
||||
sales: 1650,
|
||||
badge: '家庭装'
|
||||
}
|
||||
],
|
||||
|
||||
stomach: [
|
||||
{
|
||||
id: 'stomach1',
|
||||
name: '胃康灵胶囊',
|
||||
specification: '0.4g*24粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.5,
|
||||
image: 'https://picsum.photos/300/300?random=stomach1',
|
||||
manufacturer: '三九医药',
|
||||
sales: 890,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'stomach2',
|
||||
name: '奥美拉唑肠溶胶囊',
|
||||
specification: '20mg*14粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: 'https://picsum.photos/300/300?random=stomach2',
|
||||
manufacturer: '阿斯利康',
|
||||
sales: 1250,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'stomach3',
|
||||
name: '健胃消食片',
|
||||
specification: '0.8g*32片',
|
||||
price: 15.9,
|
||||
originalPrice: 19.9,
|
||||
image: 'https://picsum.photos/300/300?random=stomach3',
|
||||
manufacturer: '江中制药',
|
||||
sales: 2100,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'stomach4',
|
||||
name: '蒙脱石散',
|
||||
specification: '3g*10袋',
|
||||
price: 18.6,
|
||||
originalPrice: 22.0,
|
||||
image: 'https://picsum.photos/300/300?random=stomach4',
|
||||
manufacturer: '益普生',
|
||||
sales: 1780,
|
||||
badge: '止泻'
|
||||
},
|
||||
{
|
||||
id: 'stomach5',
|
||||
name: '多潘立酮片',
|
||||
specification: '10mg*30片',
|
||||
price: 22.8,
|
||||
originalPrice: 26.5,
|
||||
image: 'https://picsum.photos/300/300?random=stomach5',
|
||||
manufacturer: '西安杨森',
|
||||
sales: 950,
|
||||
badge: '促消化'
|
||||
},
|
||||
{
|
||||
id: 'stomach6',
|
||||
name: '铝碳酸镁咀嚼片',
|
||||
specification: '0.5g*20片',
|
||||
price: 25.9,
|
||||
originalPrice: 29.9,
|
||||
image: 'https://picsum.photos/300/300?random=stomach6',
|
||||
manufacturer: '拜耳',
|
||||
sales: 1320,
|
||||
badge: '护胃'
|
||||
}
|
||||
],
|
||||
|
||||
pain: [
|
||||
{
|
||||
id: 'pain1',
|
||||
name: '阿莫西林胶囊',
|
||||
specification: '0.25g*24粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain1',
|
||||
manufacturer: '华北制药',
|
||||
sales: 1560,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'pain2',
|
||||
name: '双氯芬酸钠缓释片',
|
||||
specification: '75mg*10片',
|
||||
price: 19.8,
|
||||
originalPrice: 24.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain2',
|
||||
manufacturer: '诺华制药',
|
||||
sales: 1280,
|
||||
badge: '止痛'
|
||||
},
|
||||
{
|
||||
id: 'pain3',
|
||||
name: '云南白药胶囊',
|
||||
specification: '0.25g*32粒',
|
||||
price: 35.9,
|
||||
originalPrice: 42.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain3',
|
||||
manufacturer: '云南白药',
|
||||
sales: 2350,
|
||||
badge: '经典'
|
||||
},
|
||||
{
|
||||
id: 'pain4',
|
||||
name: '塞来昔布胶囊',
|
||||
specification: '0.2g*10粒',
|
||||
price: 48.6,
|
||||
originalPrice: 55.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain4',
|
||||
manufacturer: '辉瑞',
|
||||
sales: 890,
|
||||
badge: '抗炎'
|
||||
},
|
||||
{
|
||||
id: 'pain5',
|
||||
name: '布洛芬片',
|
||||
specification: '0.1g*24片',
|
||||
price: 12.5,
|
||||
originalPrice: 15.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain5',
|
||||
manufacturer: '中美史克',
|
||||
sales: 1680,
|
||||
badge: '经济装'
|
||||
},
|
||||
{
|
||||
id: 'pain6',
|
||||
name: '头孢克肟胶囊',
|
||||
specification: '0.1g*6粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.0,
|
||||
image: 'https://picsum.photos/300/300?random=pain6',
|
||||
manufacturer: '广州白云山',
|
||||
sales: 1120,
|
||||
badge: '抗生素'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 补充其他分类的默认数据(简化版)
|
||||
const generateDefaultProducts = (categoryId: string) => {
|
||||
const baseProducts = [
|
||||
{ name: '通用药品1', price: 25.8, manufacturer: '知名药企', sales: 1200 },
|
||||
{ name: '通用药品2', price: 18.5, manufacturer: '知名药企', sales: 950 },
|
||||
{ name: '通用药品3', price: 32.0, manufacturer: '知名药企', sales: 1450 },
|
||||
{ name: '通用药品4', price: 22.8, manufacturer: '知名药企', sales: 880 },
|
||||
{ name: '通用药品5', price: 28.9, manufacturer: '知名药企', sales: 1100 },
|
||||
{ name: '通用药品6', price: 19.9, manufacturer: '知名药企', sales: 920 }
|
||||
]
|
||||
|
||||
return baseProducts.map((product, index) => ({
|
||||
id: `${categoryId}${index + 1}`,
|
||||
...product,
|
||||
specification: '规格待定',
|
||||
originalPrice: product.price * 1.2,
|
||||
image: `https://picsum.photos/300/300?random=${categoryId}${index}`,
|
||||
badge: index === 0 ? '热销' : index === 1 ? '推荐' : ''
|
||||
}))
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
initPage()
|
||||
})
|
||||
|
||||
// 初始化页面
|
||||
const initPage = () => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 加载分类数据
|
||||
primaryCategories.value = medicineCategories
|
||||
|
||||
// 默认加载感冒发烧分类
|
||||
selectPrimaryCategory('cold')
|
||||
}
|
||||
|
||||
// 选择一级分类
|
||||
const selectPrimaryCategory = (categoryId: string) => {
|
||||
activePrimary.value = categoryId
|
||||
|
||||
// 更新当前分类信息
|
||||
const category = medicineCategories.find(cat => cat.id === categoryId)
|
||||
if (category) {
|
||||
currentCategoryName.value = category.name
|
||||
currentCategoryDesc.value = category.desc
|
||||
}
|
||||
|
||||
// 加载对应商品
|
||||
if (mockProducts[categoryId]) {
|
||||
productList.value = mockProducts[categoryId]
|
||||
} else {
|
||||
productList.value = generateDefaultProducts(categoryId)
|
||||
}
|
||||
|
||||
hasMore.value = true
|
||||
}
|
||||
|
||||
// 添加到购物车
|
||||
const addToCart = (product: any) => {
|
||||
uni.showToast({
|
||||
title: '已添加到购物车',
|
||||
icon: 'success'
|
||||
})
|
||||
cartCount.value++
|
||||
}
|
||||
|
||||
// 导航函数
|
||||
const navigateToSearch = () => uni.navigateTo({ url: '/pages/medicine/search' })
|
||||
const navigateToCart = () => uni.navigateTo({ url: '/pages/medicine/cart' })
|
||||
const navigateToProduct = (product: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/medicine/detail?id=${product.id}`
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.category-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
max-width: 600px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 25px;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.search-box:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 18px;
|
||||
color: #4CAF50;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
font-size: 15px;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 22px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cart-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #FF5722;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
border: 2px solid #4CAF50;
|
||||
}
|
||||
|
||||
/* 分类内容区 */
|
||||
.category-content {
|
||||
display: flex;
|
||||
margin-top: 60px;
|
||||
padding: 0 16px;
|
||||
max-width: 1400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 左侧一级分类 */
|
||||
.primary-category {
|
||||
width: 120px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 12px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
margin: 4px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.primary-item:hover {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.primary-item.active {
|
||||
color: white !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 8px;
|
||||
min-width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 右侧内容区 */
|
||||
.product-content {
|
||||
flex: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
margin-bottom: 20px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.category-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 商品网格 */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid #e0e0e0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.product-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
background: #FF5722;
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.product-spec {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.price-section {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.current-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.price-symbol {
|
||||
font-size: 14px;
|
||||
color: #FF5722;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #FF5722;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.product-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.manufacturer {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.sales-count {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.product-action {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cart-btn:hover {
|
||||
background: #388E3C;
|
||||
}
|
||||
|
||||
.cart-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 60px;
|
||||
color: #4CAF50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.load-text {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
/* ===== 响应式设计 ===== */
|
||||
|
||||
/* 小屏手机 (小于414px) */
|
||||
@media screen and (max-width: 414px) {
|
||||
.category-content {
|
||||
flex-direction: column;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
width: calc(25% - 8px);
|
||||
margin: 4px;
|
||||
padding: 10px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0 12px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中屏手机/小平板 (415px-768px) */
|
||||
@media screen and (min-width: 415px) and (max-width: 768px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 平板设备 (769px-1024px) */
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 桌面端 (1025px以上) */
|
||||
@media screen and (min-width: 1025px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大桌面端 (1400px以上) */
|
||||
@media screen and (min-width: 1400px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,526 +1,131 @@
|
||||
<!-- 商品分类页面 -->
|
||||
<template>
|
||||
<view class="category-page">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-header">
|
||||
<view class="search-box" @click="goToSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
<text class="search-placeholder">搜索商品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="category-container">
|
||||
<!-- 左侧分类导航 -->
|
||||
<scroll-view class="category-nav" scroll-y>
|
||||
<view v-for="category in categories"
|
||||
:key="category.id"
|
||||
:class="['nav-item', { active: activeCategoryId === category.id }]"
|
||||
@click="selectCategory(category)">
|
||||
<text class="nav-text">{{ category.name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<scroll-view class="category-content" scroll-y>
|
||||
<!-- 当前分类的banner -->
|
||||
<view v-if="currentCategory" class="category-banner">
|
||||
<image class="banner-image" :src="currentCategory.image_url || '/static/default-banner.png'" />
|
||||
</view>
|
||||
|
||||
<!-- 子分类 -->
|
||||
<view v-if="subCategories.length > 0" class="sub-category-section">
|
||||
<view class="section-title">{{ currentCategory?.name }}分类</view>
|
||||
<view class="sub-category-grid">
|
||||
<view v-for="subCategory in subCategories"
|
||||
:key="subCategory.id"
|
||||
class="sub-category-item"
|
||||
@click="navigateToSubCategory(subCategory)">
|
||||
<image class="sub-category-icon" :src="subCategory.icon_url || '/static/default-category.png'" />
|
||||
<text class="sub-category-name">{{ subCategory.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 品牌专区 -->
|
||||
<view v-if="brands.length > 0" class="brand-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">品牌推荐</text>
|
||||
<text class="more-btn" @click="viewAllBrands">更多 ›</text>
|
||||
</view>
|
||||
<scroll-view class="brand-scroll" scroll-x>
|
||||
<view class="brand-list">
|
||||
<view v-for="brand in brands"
|
||||
:key="brand.id"
|
||||
class="brand-item"
|
||||
@click="viewBrandProducts(brand)">
|
||||
<image class="brand-logo" :src="brand.logo_url || '/static/default-brand.png'" />
|
||||
<text class="brand-name">{{ brand.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 热销商品 -->
|
||||
<view v-if="hotProducts.length > 0" class="hot-products-section">
|
||||
<view class="section-title">热销商品</view>
|
||||
<view class="hot-products-grid">
|
||||
<view v-for="product in hotProducts"
|
||||
:key="product.id"
|
||||
class="product-item"
|
||||
@click="viewProductDetail(product)">
|
||||
<image class="product-image" :src="getProductFirstImage(product)" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<view class="product-price-row">
|
||||
<text class="current-price">¥{{ product.price }}</text>
|
||||
<text v-if="product.original_price && product.original_price > product.price"
|
||||
class="original-price">¥{{ product.original_price }}</text>
|
||||
</view>
|
||||
<view class="sales-info">
|
||||
<text class="sales-text">已售{{ product.sales }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="isLoadingProducts" class="loading-more">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import type { ProductType } from '@/types/mall-types.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type CategoryType = {
|
||||
id: string
|
||||
name: string
|
||||
parent_id: string | null
|
||||
icon_url: string | null
|
||||
image_url: string | null
|
||||
sort_order: number
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
type BrandType = {
|
||||
id: string
|
||||
name: string
|
||||
logo_url: string | null
|
||||
description: string | null
|
||||
}
|
||||
|
||||
const categories = ref<Array<CategoryType>>([])
|
||||
const activeCategoryId = ref<string>('')
|
||||
const currentCategory = ref<CategoryType | null>(null)
|
||||
const subCategories = ref<Array<CategoryType>>([])
|
||||
const brands = ref<Array<BrandType>>([])
|
||||
const hotProducts = ref<Array<ProductType>>([])
|
||||
const isLoadingProducts = ref<boolean>(false)
|
||||
|
||||
// 监听分类切换
|
||||
watch(activeCategoryId, (newId) => {
|
||||
if (newId) {
|
||||
loadCategoryData(newId)
|
||||
}
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadCategories()
|
||||
})
|
||||
|
||||
// 加载一级分类
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const { data, error } = await supa
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.eq('is_active', true)
|
||||
.is('parent_id', null)
|
||||
.order('sort_order', { ascending: true })
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载分类失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
categories.value = data ?? []
|
||||
|
||||
// 默认选中第一个分类
|
||||
if (categories.value.length > 0) {
|
||||
activeCategoryId.value = categories.value[0].id
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载分类异常:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分类数据
|
||||
const loadCategoryData = async (categoryId: string) => {
|
||||
// 1. 获取当前分类信息
|
||||
try {
|
||||
const { data: categoryData, error: categoryError } = await supa
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.eq('id', categoryId)
|
||||
.single()
|
||||
|
||||
if (categoryError !== null) {
|
||||
console.error('加载分类详情失败:', categoryError)
|
||||
return
|
||||
}
|
||||
|
||||
currentCategory.value = categoryData
|
||||
|
||||
// 2. 加载子分类
|
||||
const { data: subData, error: subError } = await supa
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.eq('parent_id', categoryId)
|
||||
.eq('is_active', true)
|
||||
.order('sort_order', { ascending: true })
|
||||
|
||||
if (subError !== null) {
|
||||
console.error('加载子分类失败:', subError)
|
||||
} else {
|
||||
subCategories.value = subData ?? []
|
||||
}
|
||||
|
||||
// 3. 加载品牌
|
||||
const { data: brandData, error: brandError } = await supa
|
||||
.from('brands')
|
||||
.select('*')
|
||||
.eq('is_active', true)
|
||||
.order('sort_order', { ascending: true })
|
||||
.limit(8)
|
||||
|
||||
if (brandError !== null) {
|
||||
console.error('加载品牌失败:', brandError)
|
||||
} else {
|
||||
brands.value = brandData ?? []
|
||||
}
|
||||
|
||||
// 4. 加载热销商品
|
||||
loadHotProducts(categoryId)
|
||||
} catch (err) {
|
||||
console.error('加载分类数据异常:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载热销商品
|
||||
const loadHotProducts = async (categoryId: string) => {
|
||||
isLoadingProducts.value = true
|
||||
|
||||
try {
|
||||
const { data, error } = await supa
|
||||
.from('products')
|
||||
.select('*')
|
||||
.eq('status', 1)
|
||||
.eq('category_id', categoryId)
|
||||
.order('sales', { ascending: false })
|
||||
.limit(12)
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载热销商品失败:', error)
|
||||
return
|
||||
}
|
||||
|
||||
hotProducts.value = data ?? []
|
||||
} catch (err) {
|
||||
console.error('加载热销商品异常:', err)
|
||||
} finally {
|
||||
isLoadingProducts.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取商品第一张图片
|
||||
const getProductFirstImage = (product: ProductType): string => {
|
||||
return product.images?.[0] || '/static/default-product.png'
|
||||
}
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (category: CategoryType) => {
|
||||
activeCategoryId.value = category.id
|
||||
}
|
||||
|
||||
// 导航到子分类
|
||||
const navigateToSubCategory = (subCategory: CategoryType) => {
|
||||
// 可以跳转到子分类的商品列表页
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-list?categoryId=${subCategory.id}&title=${encodeURIComponent(subCategory.name)}`
|
||||
})
|
||||
}
|
||||
|
||||
// 查看品牌商品
|
||||
const viewBrandProducts = (brand: BrandType) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-list?brandId=${brand.id}&title=${encodeURIComponent(brand.name)}`
|
||||
})
|
||||
}
|
||||
|
||||
// 查看商品详情
|
||||
const viewProductDetail = (product: ProductType) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/product-detail?id=${product.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 查看所有品牌
|
||||
const viewAllBrands = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/brands'
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到搜索页
|
||||
const goToSearch = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/search'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.category-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
background-color: #ffffff;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 20px;
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
color: #999999;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.category-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.category-nav {
|
||||
width: 100px;
|
||||
background-color: #f8f8f8;
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 15px 10px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background-color: #ffffff;
|
||||
color: #007aff;
|
||||
border-left: 3px solid #007aff;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-text {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
}
|
||||
<!-- 只替换 <style> 部分,其他保持不变 -->
|
||||
<style>
|
||||
/* ...(前面的样式如 .category-page, .search-bar 等保持不变)... */
|
||||
|
||||
/* 分类内容区 —— 关键修复 */
|
||||
.category-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-top: 60px;
|
||||
padding: 0 16px;
|
||||
max-width: 1400px;
|
||||
margin: 60px auto 0 auto; /* 更安全的居中 */
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
min-height: calc(100vh - 60px); /* 避免内容太短时布局塌陷 */
|
||||
}
|
||||
|
||||
.category-banner {
|
||||
padding: 15px;
|
||||
/* 左侧一级分类 */
|
||||
.primary-category {
|
||||
width: 120px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 12px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
height: fit-content; /* 防止拉伸 */
|
||||
max-height: calc(100vh - 80px); /* 避免溢出屏幕 */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
border-radius: 8px;
|
||||
/* 右侧商品区域 —— 必须 flex: 1 才能占满剩余空间 */
|
||||
.product-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; /* 防止 flex 子项溢出 */
|
||||
background: #f8fafc; /* 与背景一致,或可设为 white */
|
||||
padding: 16px 0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.sub-category-section,
|
||||
.brand-section,
|
||||
.hot-products-section {
|
||||
padding: 15px;
|
||||
/* 商品网格容器需要滚动时也能正常工作 */
|
||||
.product-content > * {
|
||||
flex-shrink: 0; /* 防止 header/grid 被压缩 */
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 15px;
|
||||
/* ===== 响应式:小屏手机 (小于等于 414px) ===== */
|
||||
@media screen and (max-width: 414px) {
|
||||
.category-content {
|
||||
flex-direction: column;
|
||||
padding: 0 12px;
|
||||
margin-top: 55px; /* 匹配搜索栏高度 */
|
||||
}
|
||||
|
||||
.primary-category {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.primary-item {
|
||||
width: calc(25% - 8px);
|
||||
margin: 4px;
|
||||
padding: 10px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.primary-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 0 12px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
/* 中屏及以上保持网格列数变化,但布局仍是左右 */
|
||||
@media screen and (min-width: 415px) {
|
||||
.product-grid {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
color: #007aff;
|
||||
font-size: 14px;
|
||||
@media screen and (min-width: 415px) and (max-width: 768px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.sub-category-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -5px;
|
||||
@media screen and (min-width: 769px) and (max-width: 1024px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.sub-category-item {
|
||||
width: 33.33%;
|
||||
padding: 0 5px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@media screen and (min-width: 1025px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.sub-category-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sub-category-name {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.brand-scroll {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.brand-list {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.brand-item {
|
||||
width: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hot-products-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -5px;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
width: 50%;
|
||||
padding: 0 5px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.current-price {
|
||||
font-size: 14px;
|
||||
color: #ff4757;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.sales-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sales-text {
|
||||
font-size: 11px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
@media screen and (min-width: 1400px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1550
pages/mall/consumer/index1.uvue
Normal file
1550
pages/mall/consumer/index1.uvue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,11 @@
|
||||
<view class="nav-icons">
|
||||
<view class="icon-item" @click="navigateToMessages">
|
||||
<text class="icon">💬</text>
|
||||
<text v-if="unreadMessageCount > 0" class="message-badge">{{ unreadMessageCount }}</text>
|
||||
</view>
|
||||
<view class="icon-item" @click="navigateToCart">
|
||||
<text class="icon">🛒</text>
|
||||
<text v-if="cartCount > 0" class="cart-badge">{{ cartCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -107,35 +109,12 @@
|
||||
<view class="action-icon brand">🏷️</view>
|
||||
<text class="action-text">品牌特卖</text>
|
||||
</view>
|
||||
<view class="action-item" @click="navigateToGroupBuy">
|
||||
<view class="action-icon group">👥</view>
|
||||
<text class="action-text">拼团购</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 限时秒杀 -->
|
||||
<view class="flash-sale-section">
|
||||
<view class="section-header">
|
||||
<view class="header-content">
|
||||
<view class="title-with-icon">
|
||||
<text class="flash-icon">⚡</text>
|
||||
<text class="section-title">限时秒杀</text>
|
||||
</view>
|
||||
<view class="countdown-wrapper">
|
||||
<text class="countdown-label">距结束</text>
|
||||
<view class="time-box">
|
||||
<text class="time">{{ countdown.hours }}</text>
|
||||
<text class="colon">:</text>
|
||||
<text class="time">{{ countdown.minutes }}</text>
|
||||
<text class="colon">:</text>
|
||||
<text class="time">{{ countdown.seconds }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="more-link" @click="navigateToFlashSale">
|
||||
<text>更多</text>
|
||||
<text class="arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 限时秒杀商品网格 -->
|
||||
@@ -175,16 +154,6 @@
|
||||
<text class="recommend-icon">❤️</text>
|
||||
<text class="section-title">为你推荐</text>
|
||||
</view>
|
||||
<view class="sort-options">
|
||||
<text
|
||||
v-for="option in sortOptions"
|
||||
:key="option.id"
|
||||
:class="['sort-option', { active: activeSort === option.id }]"
|
||||
@click="switchSort(option.id)"
|
||||
>
|
||||
{{ option.name }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 瀑布流容器 -->
|
||||
@@ -301,6 +270,8 @@ const activeSort = ref<string>('recommend')
|
||||
const statusBarHeight = ref<number>(0)
|
||||
const scrollViewHeight = ref<number>(0)
|
||||
const scrollLeft = ref<number>(0)
|
||||
const cartCount = ref<number>(0)
|
||||
const unreadMessageCount = ref<number>(3)
|
||||
|
||||
// 倒计时数据
|
||||
const countdown = reactive({
|
||||
@@ -548,6 +519,9 @@ onMounted(() => {
|
||||
// 加载数据
|
||||
loadMockData()
|
||||
setupCountdown()
|
||||
|
||||
// 模拟获取购物车数量和未读消息数
|
||||
cartCount.value = Math.floor(Math.random() * 5) + 1
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -659,7 +633,7 @@ const loadMore = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 导航函数
|
||||
// 导航函数 - 修改消息为跳转到消息tab页
|
||||
const navigateToSearch = () => uni.navigateTo({ url: '/pages/mall/consumer/search' })
|
||||
const navigateToCategory = (category?: any) => {
|
||||
if (category) {
|
||||
@@ -672,7 +646,7 @@ const navigateToProduct = (product: any) => {
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/product-detail?id=${product.id}` })
|
||||
}
|
||||
const navigateToCoupons = () => uni.navigateTo({ url: '/pages/mall/consumer/coupons' })
|
||||
const navigateToMessages = () => uni.navigateTo({ url: '/pages/mall/consumer/messages' })
|
||||
const navigateToMessages = () => uni.switchTab({ url: '/pages/mall/consumer/messages' })
|
||||
const navigateToFlashSale = () => uni.navigateTo({ url: '/pages/mall/consumer/flash-sale' })
|
||||
const navigateToBrand = () => uni.navigateTo({ url: '/pages/mall/consumer/brand' })
|
||||
const navigateToCart = () => uni.switchTab({ url: '/pages/mall/consumer/cart' })
|
||||
@@ -757,12 +731,28 @@ const navigateToGroupBuy = () => uni.navigateTo({ url: '/pages/mall/consumer/gro
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.message-badge, .cart-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #ff3b30;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
background: #f5f5f5;
|
||||
|
||||
1612
pages/mall/consumer/index松散.uvue
Normal file
1612
pages/mall/consumer/index松散.uvue
Normal file
File diff suppressed because it is too large
Load Diff
1793
pages/mall/consumer/index紧凑.uvue
Normal file
1793
pages/mall/consumer/index紧凑.uvue
Normal file
File diff suppressed because it is too large
Load Diff
634
pages/mall/consumer/messages - 副本.uvue
Normal file
634
pages/mall/consumer/messages - 副本.uvue
Normal file
@@ -0,0 +1,634 @@
|
||||
<!-- pages/mall/consumer/messages.uvue -->
|
||||
<template>
|
||||
<view class="messages-page">
|
||||
<!-- 顶部标题栏 -->
|
||||
<view class="messages-header">
|
||||
<text class="header-title">消息</text>
|
||||
<view class="header-actions">
|
||||
<text class="action-icon" @click="clearAllUnread">📝</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息分类标签 -->
|
||||
<view class="message-tabs">
|
||||
<view
|
||||
v-for="tab in messageTabs"
|
||||
:key="tab.id"
|
||||
:class="['tab-item', { active: activeTab === tab.id }]"
|
||||
@click="switchTab(tab.id)"
|
||||
>
|
||||
<text class="tab-name">{{ tab.name }}</text>
|
||||
<text v-if="tab.unread > 0" class="tab-badge">{{ tab.unread }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="messages-content"
|
||||
refresher-enabled
|
||||
:refresher-triggered="refreshing"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<!-- 系统通知 -->
|
||||
<view v-if="activeTab === 'system'" class="message-section">
|
||||
<view
|
||||
v-for="message in systemMessages"
|
||||
:key="message.id"
|
||||
:class="['message-item', { unread: !message.read }]"
|
||||
@click="viewSystemMessage(message)"
|
||||
>
|
||||
<view class="message-icon-wrapper">
|
||||
<text class="message-icon">📢</text>
|
||||
</view>
|
||||
<view class="message-content">
|
||||
<view class="message-header">
|
||||
<text class="message-title">{{ message.title }}</text>
|
||||
<text class="message-time">{{ message.time }}</text>
|
||||
</view>
|
||||
<text class="message-preview">{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单消息 -->
|
||||
<view v-if="activeTab === 'order'" class="message-section">
|
||||
<view
|
||||
v-for="message in orderMessages"
|
||||
:key="message.id"
|
||||
:class="['message-item', { unread: !message.read }]"
|
||||
@click="viewOrderMessage(message)"
|
||||
>
|
||||
<view class="message-icon-wrapper">
|
||||
<text class="message-icon">📦</text>
|
||||
</view>
|
||||
<view class="message-content">
|
||||
<view class="message-header">
|
||||
<text class="message-title">{{ message.title }}</text>
|
||||
<text class="message-time">{{ message.time }}</text>
|
||||
</view>
|
||||
<text class="message-preview">{{ message.content }}</text>
|
||||
<text class="order-info" v-if="message.order_no">订单号: {{ message.order_no }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 客服消息 -->
|
||||
<view v-if="activeTab === 'service'" class="message-section">
|
||||
<view
|
||||
v-for="message in serviceMessages"
|
||||
:key="message.id"
|
||||
:class="['message-item', { unread: !message.read }]"
|
||||
@click="startCustomerService(message)"
|
||||
>
|
||||
<view class="message-icon-wrapper">
|
||||
<image
|
||||
v-if="message.avatar"
|
||||
class="message-avatar"
|
||||
:src="message.avatar"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="message-icon">💁</text>
|
||||
</view>
|
||||
<view class="message-content">
|
||||
<view class="message-header">
|
||||
<text class="message-title">{{ message.title }}</text>
|
||||
<text class="message-time">{{ message.time }}</text>
|
||||
</view>
|
||||
<text class="message-preview">{{ message.content }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 优惠活动 -->
|
||||
<view v-if="activeTab === 'promo'" class="message-section">
|
||||
<view
|
||||
v-for="message in promoMessages"
|
||||
:key="message.id"
|
||||
:class="['message-item', { unread: !message.read }]"
|
||||
@click="viewPromoMessage(message)"
|
||||
>
|
||||
<view class="message-icon-wrapper">
|
||||
<text class="message-icon">🎁</text>
|
||||
</view>
|
||||
<view class="message-content">
|
||||
<view class="message-header">
|
||||
<text class="message-title">{{ message.title }}</text>
|
||||
<text class="message-time">{{ message.time }}</text>
|
||||
</view>
|
||||
<text class="message-preview">{{ message.content }}</text>
|
||||
<view v-if="message.coupon" class="coupon-tag">
|
||||
<text class="coupon-text">{{ message.coupon }}优惠券</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && currentMessages.length === 0" class="empty-messages">
|
||||
<text class="empty-icon">💬</text>
|
||||
<text class="empty-title">暂无消息</text>
|
||||
<text class="empty-desc">暂时没有新消息</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部固定按钮 -->
|
||||
<view class="floating-action">
|
||||
<button class="action-button" @click="contactCustomerService">
|
||||
<text class="button-icon">💁</text>
|
||||
<text class="button-text">联系客服</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref<string>('system')
|
||||
const refreshing = ref<boolean>(false)
|
||||
const loading = ref<boolean>(false)
|
||||
const unreadCount = ref<number>(5)
|
||||
|
||||
// 消息分类标签
|
||||
const messageTabs = reactive([
|
||||
{ id: 'system', name: '系统通知', unread: 3 },
|
||||
{ id: 'order', name: '订单消息', unread: 2 },
|
||||
{ id: 'service', name: '客服消息', unread: 0 },
|
||||
{ id: 'promo', name: '优惠活动', unread: 1 }
|
||||
])
|
||||
|
||||
// Mock 系统通知数据
|
||||
const systemMessages = reactive([
|
||||
{
|
||||
id: 'sys001',
|
||||
title: '系统维护通知',
|
||||
content: '平台将于今晚23:00-01:00进行系统维护,届时部分功能可能无法使用。',
|
||||
time: '2023-11-23 15:30',
|
||||
read: false,
|
||||
type: 'system'
|
||||
},
|
||||
{
|
||||
id: 'sys002',
|
||||
title: '隐私政策更新',
|
||||
content: '我们已更新隐私政策,请查阅相关条款。',
|
||||
time: '2023-11-22 10:15',
|
||||
read: true,
|
||||
type: 'system'
|
||||
},
|
||||
{
|
||||
id: 'sys003',
|
||||
title: '账户安全提醒',
|
||||
content: '检测到您的账户在异地登录,如果不是您本人操作,请及时修改密码。',
|
||||
time: '2023-11-21 18:45',
|
||||
read: false,
|
||||
type: 'system'
|
||||
}
|
||||
])
|
||||
|
||||
// Mock 订单消息数据
|
||||
const orderMessages = reactive([
|
||||
{
|
||||
id: 'order001',
|
||||
title: '订单发货通知',
|
||||
content: '您的订单202311230001已发货,点击查看物流信息。',
|
||||
time: '2023-11-23 14:20',
|
||||
read: false,
|
||||
type: 'order',
|
||||
order_no: '202311230001'
|
||||
},
|
||||
{
|
||||
id: 'order002',
|
||||
title: '订单支付成功',
|
||||
content: '您的订单202311220001支付成功,商家正在备货中。',
|
||||
time: '2023-11-22 09:30',
|
||||
read: false,
|
||||
type: 'order',
|
||||
order_no: '202311220001'
|
||||
},
|
||||
{
|
||||
id: 'order003',
|
||||
title: '订单确认收货',
|
||||
content: '您的订单202311210001已完成,期待您的评价。',
|
||||
time: '2023-11-21 16:15',
|
||||
read: true,
|
||||
type: 'order',
|
||||
order_no: '202311210001'
|
||||
}
|
||||
])
|
||||
|
||||
// Mock 客服消息数据
|
||||
const serviceMessages = reactive([
|
||||
{
|
||||
id: 'service001',
|
||||
title: '在线客服',
|
||||
content: '您好,有什么可以帮助您的吗?',
|
||||
time: '2023-11-23 10:05',
|
||||
read: true,
|
||||
type: 'service',
|
||||
avatar: 'https://picsum.photos/50/50?random=1'
|
||||
},
|
||||
{
|
||||
id: 'service002',
|
||||
title: '售后客服',
|
||||
content: '关于您申请的退款,已处理完成。',
|
||||
time: '2023-11-22 15:20',
|
||||
read: true,
|
||||
type: 'service',
|
||||
avatar: 'https://picsum.photos/50/50?random=2'
|
||||
}
|
||||
])
|
||||
|
||||
// Mock 优惠活动数据
|
||||
const promoMessages = reactive([
|
||||
{
|
||||
id: 'promo001',
|
||||
title: '新人专享券',
|
||||
content: '您有一张新人专享优惠券已到账,有效期3天。',
|
||||
time: '2023-11-23 08:00',
|
||||
read: false,
|
||||
type: 'promo',
|
||||
coupon: '50元'
|
||||
},
|
||||
{
|
||||
id: 'promo002',
|
||||
title: '双11大促',
|
||||
content: '双11狂欢购物节,全场满300减50。',
|
||||
time: '2023-11-22 12:30',
|
||||
read: true,
|
||||
type: 'promo',
|
||||
coupon: '满300减50'
|
||||
}
|
||||
])
|
||||
|
||||
// 计算当前显示的消息
|
||||
const currentMessages = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'system': return systemMessages
|
||||
case 'order': return orderMessages
|
||||
case 'service': return serviceMessages
|
||||
case 'promo': return promoMessages
|
||||
default: return []
|
||||
}
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadMessages()
|
||||
})
|
||||
|
||||
// 加载消息
|
||||
const loadMessages = () => {
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
// 这里应该调用API获取消息数据
|
||||
loading.value = false
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
const switchTab = (tabId: string) => {
|
||||
activeTab.value = tabId
|
||||
}
|
||||
|
||||
// 查看系统消息
|
||||
const viewSystemMessage = (message: any) => {
|
||||
message.read = true
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/message-detail?id=${message.id}&type=system`
|
||||
})
|
||||
}
|
||||
|
||||
// 查看订单消息
|
||||
const viewOrderMessage = (message: any) => {
|
||||
message.read = true
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/order-detail?id=${message.order_no}`
|
||||
})
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const startCustomerService = (message: any) => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/chat'
|
||||
})
|
||||
}
|
||||
|
||||
// 查看优惠活动
|
||||
const viewPromoMessage = (message: any) => {
|
||||
message.read = true
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/coupons`
|
||||
})
|
||||
}
|
||||
|
||||
// 联系客服
|
||||
const contactCustomerService = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/chat'
|
||||
})
|
||||
}
|
||||
|
||||
// 清除所有未读
|
||||
const clearAllUnread = () => {
|
||||
uni.showModal({
|
||||
title: '确认操作',
|
||||
content: '确定要标记所有消息为已读吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 标记所有消息为已读
|
||||
systemMessages.forEach(msg => msg.read = true)
|
||||
orderMessages.forEach(msg => msg.read = true)
|
||||
serviceMessages.forEach(msg => msg.read = true)
|
||||
promoMessages.forEach(msg => msg.read = true)
|
||||
|
||||
// 更新标签未读数
|
||||
messageTabs.forEach(tab => tab.unread = 0)
|
||||
|
||||
uni.showToast({
|
||||
title: '已标记所有消息为已读',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const onRefresh = () => {
|
||||
refreshing.value = true
|
||||
setTimeout(() => {
|
||||
loadMessages()
|
||||
refreshing.value = false
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.messages-page {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.messages-header {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header-actions .action-icon {
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 消息分类标签 */
|
||||
.message-tabs {
|
||||
background-color: white;
|
||||
display: flex;
|
||||
padding: 0 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
padding: 15px 5px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #ff5000;
|
||||
border-bottom-color: #ff5000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background-color: #ff5000;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 消息内容区 */
|
||||
.messages-content {
|
||||
flex: 1;
|
||||
padding-bottom: 80px; /* 为底部按钮留出空间 */
|
||||
}
|
||||
|
||||
/* 消息项 */
|
||||
.message-section {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.message-item.unread {
|
||||
background-color: #fff8f6;
|
||||
border-left: 3px solid #ff5000;
|
||||
}
|
||||
|
||||
.message-icon-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message-preview {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
font-size: 12px;
|
||||
color: #ff5000;
|
||||
background-color: #fff0e8;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.coupon-tag {
|
||||
display: inline-block;
|
||||
background-color: #ff5000;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.coupon-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
color: #ddd;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部浮动按钮 */
|
||||
.floating-action {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background: linear-gradient(135deg, #ff5000, #ff9500);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 4px 12px rgba(255, 80, 0, 0.3);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media screen and (max-width: 320px) {
|
||||
.tab-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.message-preview {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 415px) {
|
||||
.message-item {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.message-icon-wrapper {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
909
pages/mall/consumer/profile - 副本.uvue
Normal file
909
pages/mall/consumer/profile - 副本.uvue
Normal file
@@ -0,0 +1,909 @@
|
||||
<!-- 消费者端 - 个人中心 -->
|
||||
<template>
|
||||
<view class="consumer-profile">
|
||||
<!-- 用户信息头部 -->
|
||||
<view class="profile-header">
|
||||
<image :src="userInfo.avatar_url || '/static/default-avatar.png'" class="user-avatar" @click="editProfile" />
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userInfo.nickname || userInfo.phone }}</text>
|
||||
<text class="user-level">{{ getUserLevel() }}</text>
|
||||
<view class="user-stats">
|
||||
<text class="stat-item">积分: {{ userStats.points }}</text>
|
||||
<text class="stat-item">余额: ¥{{ userStats.balance }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="settings-icon" @click="goToSettings">⚙️</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单状态快捷入口 -->
|
||||
<view class="order-shortcuts">
|
||||
<view class="section-title">我的订单</view>
|
||||
<view class="order-tabs">
|
||||
<view class="order-tab" @click="goToOrders('all')">
|
||||
<text class="tab-icon">📋</text>
|
||||
<text class="tab-text">全部订单</text>
|
||||
<text v-if="orderCounts.total > 0" class="tab-badge">{{ orderCounts.total }}</text>
|
||||
</view>
|
||||
<view class="order-tab" @click="goToOrders('pending')">
|
||||
<text class="tab-icon">💰</text>
|
||||
<text class="tab-text">待支付</text>
|
||||
<text v-if="orderCounts.pending > 0" class="tab-badge">{{ orderCounts.pending }}</text>
|
||||
</view>
|
||||
<view class="order-tab" @click="goToOrders('shipped')">
|
||||
<text class="tab-icon">🚚</text>
|
||||
<text class="tab-text">待收货</text>
|
||||
<text v-if="orderCounts.shipped > 0" class="tab-badge">{{ orderCounts.shipped }}</text>
|
||||
</view>
|
||||
<view class="order-tab" @click="goToOrders('completed')">
|
||||
<text class="tab-icon">⭐</text>
|
||||
<text class="tab-text">待评价</text>
|
||||
<text v-if="orderCounts.review > 0" class="tab-badge">{{ orderCounts.review }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近订单 -->
|
||||
<view class="recent-orders">
|
||||
<view class="section-header">
|
||||
<text class="section-title">最近订单</text>
|
||||
<text class="view-all" @click="goToOrders('all')">查看全部 ></text>
|
||||
</view>
|
||||
|
||||
<view v-if="recentOrders.length === 0" class="empty-orders">
|
||||
<text class="empty-text">暂无订单记录</text>
|
||||
<button class="start-shopping" @click="goShopping">去逛逛</button>
|
||||
</view>
|
||||
|
||||
<view v-for="order in recentOrders" :key="order.id" class="order-item" @click="viewOrderDetail(order)">
|
||||
<view class="order-header">
|
||||
<text class="order-no">订单号: {{ order.order_no }}</text>
|
||||
<text class="order-status" :class="getOrderStatusClass(order.status)">{{ getOrderStatusText(order.status) }}</text>
|
||||
</view>
|
||||
<view class="order-content">
|
||||
<image :src="getOrderMainImage(order)" class="order-image" />
|
||||
<view class="order-info">
|
||||
<text class="order-title">{{ getOrderTitle(order) }}</text>
|
||||
<text class="order-amount">¥{{ order.actual_amount }}</text>
|
||||
<text class="order-time">{{ formatTime(order.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-actions">
|
||||
<button v-if="order.status === 1" class="action-btn pay" @click.stop="payOrder(order)">立即支付</button>
|
||||
<button v-if="order.status === 3" class="action-btn confirm" @click.stop="confirmReceive(order)">确认收货</button>
|
||||
<button v-if="order.status === 4" class="action-btn review" @click.stop="reviewOrder(order)">评价</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的服务 -->
|
||||
<view class="my-services">
|
||||
<view class="section-title">我的服务</view>
|
||||
<view class="service-grid">
|
||||
<view class="service-item" @click="goToCoupons">
|
||||
<text class="service-icon">🎫</text>
|
||||
<text class="service-text">优惠券</text>
|
||||
<text v-if="serviceCounts.coupons > 0" class="service-badge">{{ serviceCounts.coupons }}</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToAddress">
|
||||
<text class="service-icon">📍</text>
|
||||
<text class="service-text">收货地址</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToFavorites">
|
||||
<text class="service-icon">❤️</text>
|
||||
<text class="service-text">我的收藏</text>
|
||||
<text v-if="serviceCounts.favorites > 0" class="service-badge">{{ serviceCounts.favorites }}</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToFootprint">
|
||||
<text class="service-icon">👣</text>
|
||||
<text class="service-text">浏览足迹</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToRefund">
|
||||
<text class="service-icon">🔄</text>
|
||||
<text class="service-text">退款/售后</text>
|
||||
</view>
|
||||
<view class="service-item" @click="contactService">
|
||||
<text class="service-icon">💬</text>
|
||||
<text class="service-text">在线客服</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToMySubscriptions">
|
||||
<text class="service-icon">🧩</text>
|
||||
<text class="service-text">我的订阅</text>
|
||||
</view>
|
||||
<view class="service-item" @click="goToSubscriptions">
|
||||
<text class="service-icon">🧩</text>
|
||||
<text class="service-text">软件订阅</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 消费统计 -->
|
||||
<view class="consumption-stats">
|
||||
<view class="section-title">消费统计</view>
|
||||
<view class="stats-period">
|
||||
<text v-for="period in statsPeriods" :key="period.key"
|
||||
class="period-tab"
|
||||
:class="{ active: activeStatsPeriod === period.key }"
|
||||
@click="switchStatsPeriod(period.key)">{{ period.label }}</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-content">
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">¥{{ currentStats.total_amount }}</text>
|
||||
<text class="stat-label">总消费</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{ currentStats.order_count }}</text>
|
||||
<text class="stat-label">订单数</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">¥{{ currentStats.avg_amount }}</text>
|
||||
<text class="stat-label">平均消费</text>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<text class="stat-value">{{ currentStats.save_amount }}</text>
|
||||
<text class="stat-label">节省金额</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账户安全 -->
|
||||
<view class="account-security">
|
||||
<view class="section-title">账户安全</view>
|
||||
<view class="security-items">
|
||||
<view class="security-item" @click="changePassword">
|
||||
<text class="security-icon">🔒</text>
|
||||
<text class="security-text">修改密码</text>
|
||||
<text class="security-arrow">></text>
|
||||
</view>
|
||||
<view class="security-item" @click="bindPhone">
|
||||
<text class="security-icon">📱</text>
|
||||
<text class="security-text">手机绑定</text>
|
||||
<view class="security-status">
|
||||
<text class="status-text" :class="{ bound: userInfo.phone }">{{ userInfo.phone ? '已绑定' : '未绑定' }}</text>
|
||||
<text class="security-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="security-item" @click="bindEmail">
|
||||
<text class="security-icon">📧</text>
|
||||
<text class="security-text">邮箱绑定</text>
|
||||
<view class="security-status">
|
||||
<text class="status-text" :class="{ bound: userInfo.email }">{{ userInfo.email ? '已绑定' : '未绑定' }}</text>
|
||||
<text class="security-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UserType, OrderType } from '@/types/mall-types.uts'
|
||||
|
||||
type UserStatsType = {
|
||||
points: number
|
||||
balance: number
|
||||
level: number
|
||||
}
|
||||
|
||||
type OrderCountsType = {
|
||||
total: number
|
||||
pending: number
|
||||
shipped: number
|
||||
review: number
|
||||
}
|
||||
|
||||
type ServiceCountsType = {
|
||||
coupons: number
|
||||
favorites: number
|
||||
}
|
||||
|
||||
type ConsumptionStatsType = {
|
||||
total_amount: number
|
||||
order_count: number
|
||||
avg_amount: number
|
||||
save_amount: number
|
||||
}
|
||||
|
||||
type StatsPeriodType = {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
id: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
nickname: '',
|
||||
avatar_url: '',
|
||||
gender: 0,
|
||||
user_type: 0,
|
||||
status: 0,
|
||||
created_at: ''
|
||||
} as UserType,
|
||||
userStats: {
|
||||
points: 0,
|
||||
balance: 0,
|
||||
level: 1
|
||||
} as UserStatsType,
|
||||
orderCounts: {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
shipped: 0,
|
||||
review: 0
|
||||
} as OrderCountsType,
|
||||
serviceCounts: {
|
||||
coupons: 0,
|
||||
favorites: 0
|
||||
} as ServiceCountsType,
|
||||
recentOrders: [] as Array<OrderType>,
|
||||
statsPeriods: [
|
||||
{ key: 'month', label: '本月' },
|
||||
{ key: 'quarter', label: '本季度' },
|
||||
{ key: 'year', label: '本年' },
|
||||
{ key: 'all', label: '全部' }
|
||||
] as Array<StatsPeriodType>,
|
||||
activeStatsPeriod: 'month',
|
||||
currentStats: {
|
||||
total_amount: 0,
|
||||
order_count: 0,
|
||||
avg_amount: 0,
|
||||
save_amount: 0
|
||||
} as ConsumptionStatsType
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadUserProfile()
|
||||
},
|
||||
onShow() {
|
||||
this.refreshData()
|
||||
},
|
||||
methods: {
|
||||
loadUserProfile() {
|
||||
// 模拟加载用户信息
|
||||
this.userInfo = {
|
||||
id: 'user_001',
|
||||
phone: '13800138000',
|
||||
email: 'user@example.com',
|
||||
nickname: '张三',
|
||||
avatar_url: '/static/avatar1.jpg',
|
||||
gender: 1,
|
||||
user_type: 1,
|
||||
status: 1,
|
||||
created_at: '2023-06-15T10:30:00'
|
||||
}
|
||||
|
||||
this.userStats = {
|
||||
points: 1580,
|
||||
balance: 268.50,
|
||||
level: 3
|
||||
}
|
||||
|
||||
this.orderCounts = {
|
||||
total: 23,
|
||||
pending: 2,
|
||||
shipped: 1,
|
||||
review: 3
|
||||
}
|
||||
|
||||
this.serviceCounts = {
|
||||
coupons: 5,
|
||||
favorites: 12
|
||||
}
|
||||
|
||||
this.recentOrders = [
|
||||
{
|
||||
id: 'order_001',
|
||||
order_no: 'ORD202401150001',
|
||||
user_id: 'user_001',
|
||||
merchant_id: 'merchant_001',
|
||||
status: 3,
|
||||
total_amount: 299.98,
|
||||
discount_amount: 30.00,
|
||||
delivery_fee: 8.00,
|
||||
actual_amount: 277.98,
|
||||
payment_method: 1,
|
||||
payment_status: 1,
|
||||
delivery_address: {},
|
||||
created_at: '2024-01-15T14:30:00'
|
||||
},
|
||||
{
|
||||
id: 'order_002',
|
||||
order_no: 'ORD202401140002',
|
||||
user_id: 'user_001',
|
||||
merchant_id: 'merchant_002',
|
||||
status: 4,
|
||||
total_amount: 158.00,
|
||||
discount_amount: 0,
|
||||
delivery_fee: 6.00,
|
||||
actual_amount: 164.00,
|
||||
payment_method: 1,
|
||||
payment_status: 1,
|
||||
delivery_address: {},
|
||||
created_at: '2024-01-14T09:20:00'
|
||||
}
|
||||
]
|
||||
|
||||
this.loadConsumptionStats()
|
||||
},
|
||||
|
||||
loadConsumptionStats() {
|
||||
// 模拟加载消费统计数据
|
||||
const statsData: Record<string, ConsumptionStatsType> = {
|
||||
month: {
|
||||
total_amount: 1280.50,
|
||||
order_count: 8,
|
||||
avg_amount: 160.06,
|
||||
save_amount: 85.20
|
||||
},
|
||||
quarter: {
|
||||
total_amount: 3680.80,
|
||||
order_count: 18,
|
||||
avg_amount: 204.49,
|
||||
save_amount: 256.30
|
||||
},
|
||||
year: {
|
||||
total_amount: 15680.90,
|
||||
order_count: 56,
|
||||
avg_amount: 280.02,
|
||||
save_amount: 986.50
|
||||
},
|
||||
all: {
|
||||
total_amount: 25680.50,
|
||||
order_count: 89,
|
||||
avg_amount: 288.55,
|
||||
save_amount: 1580.20
|
||||
}
|
||||
}
|
||||
|
||||
this.currentStats = statsData[this.activeStatsPeriod]
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
// 刷新页面数据
|
||||
this.loadUserProfile()
|
||||
},
|
||||
|
||||
getUserLevel(): string {
|
||||
const levels = ['新手', '铜牌会员', '银牌会员', '金牌会员', '钻石会员']
|
||||
return levels[this.userStats.level] || '新手'
|
||||
},
|
||||
|
||||
getOrderStatusText(status: number): string {
|
||||
const statusTexts = ['异常', '待支付', '待发货', '待收货', '已完成', '已取消']
|
||||
return statusTexts[status] || '未知'
|
||||
},
|
||||
|
||||
getOrderStatusClass(status: number): string {
|
||||
const statusClasses = ['error', 'pending', 'processing', 'shipping', 'completed', 'cancelled']
|
||||
return statusClasses[status] || 'error'
|
||||
},
|
||||
|
||||
getOrderMainImage(order: OrderType): string {
|
||||
// 模拟获取订单主图
|
||||
return '/static/product1.jpg'
|
||||
},
|
||||
|
||||
getOrderTitle(order: OrderType): string {
|
||||
// 模拟获取订单标题
|
||||
return '精选商品等多件商品'
|
||||
},
|
||||
|
||||
formatTime(timeStr: string): string {
|
||||
const date = new Date(timeStr)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (days === 0) {
|
||||
return '今天'
|
||||
} else if (days === 1) {
|
||||
return '昨天'
|
||||
} else {
|
||||
return `${days}天前`
|
||||
}
|
||||
},
|
||||
|
||||
switchStatsPeriod(period: string) {
|
||||
this.activeStatsPeriod = period
|
||||
this.loadConsumptionStats()
|
||||
},
|
||||
|
||||
editProfile() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/edit-profile'
|
||||
})
|
||||
},
|
||||
|
||||
goToSettings() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/settings'
|
||||
})
|
||||
},
|
||||
|
||||
goToOrders(type: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/orders?type=${type}`
|
||||
})
|
||||
},
|
||||
|
||||
goShopping() {
|
||||
uni.switchTab({
|
||||
url: '/pages/mall/consumer/index'
|
||||
})
|
||||
},
|
||||
|
||||
viewOrderDetail(order: OrderType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/order-detail?orderId=${order.id}`
|
||||
})
|
||||
},
|
||||
|
||||
payOrder(order: OrderType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/payment?orderId=${order.id}`
|
||||
})
|
||||
},
|
||||
|
||||
confirmReceive(order: OrderType) {
|
||||
uni.showModal({
|
||||
title: '确认收货',
|
||||
content: '确认已收到商品吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '确认收货成功',
|
||||
icon: 'success'
|
||||
})
|
||||
this.refreshData()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
reviewOrder(order: OrderType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/review?orderId=${order.id}`
|
||||
})
|
||||
},
|
||||
|
||||
goToCoupons() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/coupons'
|
||||
})
|
||||
},
|
||||
|
||||
goToAddress() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/address'
|
||||
})
|
||||
},
|
||||
|
||||
goToFavorites() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/favorites'
|
||||
})
|
||||
},
|
||||
|
||||
goToFootprint() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/footprint'
|
||||
})
|
||||
},
|
||||
|
||||
goToRefund() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/refund'
|
||||
})
|
||||
},
|
||||
|
||||
contactService() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/service/chat'
|
||||
})
|
||||
},
|
||||
goToMySubscriptions() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/subscription/my-subscriptions'
|
||||
})
|
||||
},
|
||||
goToSubscriptions() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/subscription/plan-list'
|
||||
})
|
||||
},
|
||||
|
||||
changePassword() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/change-password'
|
||||
})
|
||||
},
|
||||
|
||||
bindPhone() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/bind-phone'
|
||||
})
|
||||
},
|
||||
|
||||
bindEmail() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/bind-email'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.consumer-profile {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 60rpx 30rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
margin-right: 30rpx;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-level {
|
||||
font-size: 24rpx;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user-stats {
|
||||
display: flex;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
font-size: 32rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.order-shortcuts, .recent-orders, .my-services, .consumption-stats, .account-security {
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
font-size: 24rpx;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.order-tabs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.order-tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: 20rpx;
|
||||
background-color: #ff4444;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 10rpx;
|
||||
min-width: 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-orders {
|
||||
text-align: center;
|
||||
padding: 80rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.start-shopping {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
padding: 20rpx 40rpx;
|
||||
border-radius: 25rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
padding: 25rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.order-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 24rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 10rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.order-status.pending {
|
||||
background-color: #ffa726;
|
||||
}
|
||||
|
||||
.order-status.processing {
|
||||
background-color: #2196f3;
|
||||
}
|
||||
|
||||
.order-status.shipping {
|
||||
background-color: #9c27b0;
|
||||
}
|
||||
|
||||
.order-status.completed {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
|
||||
.order-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.order-image {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.order-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.order-title {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.order-amount {
|
||||
font-size: 28rpx;
|
||||
color: #ff4444;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5rpx;
|
||||
}
|
||||
|
||||
.order-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 12rpx 25rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-btn.pay {
|
||||
background-color: #ff4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.action-btn.confirm {
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.action-btn.review {
|
||||
background-color: #ffa726;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.service-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 48rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.service-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.service-badge {
|
||||
position: absolute;
|
||||
top: -5rpx;
|
||||
right: 10rpx;
|
||||
background-color: #ff4444;
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
padding: 4rpx 6rpx;
|
||||
border-radius: 8rpx;
|
||||
min-width: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-period {
|
||||
display: flex;
|
||||
gap: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.period-tab {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.period-tab.active {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.stats-content {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.security-items {
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
|
||||
.security-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 25rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.security-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.security-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.security-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.security-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.status-text.bound {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.security-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,513 @@
|
||||
<template>
|
||||
<view>
|
||||
|
||||
<view class="search-page">
|
||||
<!-- 搜索头部 -->
|
||||
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="search-bar-container">
|
||||
<!-- 返回按钮 -->
|
||||
<view class="back-btn" @click="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-input-container">
|
||||
<view class="search-icon">🔍</view>
|
||||
<input
|
||||
class="search-input"
|
||||
type="text"
|
||||
:value="searchKeyword"
|
||||
@input="onInput"
|
||||
@confirm="onSearch"
|
||||
placeholder="请输入药品名称、症状或品牌"
|
||||
placeholder-class="placeholder"
|
||||
focus
|
||||
/>
|
||||
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
|
||||
<text class="clear-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索按钮 -->
|
||||
<view class="search-btn" @click="onSearch">
|
||||
<text class="search-btn-text">搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<scroll-view scroll-y class="main-content" :style="{ height: scrollHeight + 'px' }">
|
||||
<!-- 搜索历史 -->
|
||||
<view v-if="showHistory && searchHistory.length > 0" class="search-history">
|
||||
<view class="section-header">
|
||||
<text class="section-title">搜索历史</text>
|
||||
<text class="clear-history" @click="clearHistory">清空</text>
|
||||
</view>
|
||||
<view class="history-tags">
|
||||
<view
|
||||
v-for="(item, index) in searchHistory"
|
||||
:key="index"
|
||||
class="history-tag"
|
||||
@click="searchFromHistory(item)"
|
||||
>
|
||||
<text class="history-text">{{ item }}</text>
|
||||
<text class="delete-icon" @click.stop="deleteHistoryItem(index)">×</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门搜索 -->
|
||||
<view class="hot-search">
|
||||
<view class="section-header">
|
||||
<text class="section-title">热门搜索</text>
|
||||
</view>
|
||||
<view class="hot-tags">
|
||||
<view
|
||||
v-for="(item, index) in hotSearchList"
|
||||
:key="index"
|
||||
class="hot-tag"
|
||||
:class="{ 'hot': index < 3 }"
|
||||
@click="searchFromHot(item.keyword)"
|
||||
>
|
||||
<text class="hot-rank" v-if="index < 3">{{ index + 1 }}</text>
|
||||
<text class="hot-text">{{ item.keyword }}</text>
|
||||
<text v-if="item.hot" class="hot-icon">🔥</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索分类 -->
|
||||
<view class="search-categories">
|
||||
<view class="section-header">
|
||||
<text class="section-title">按分类搜索</text>
|
||||
</view>
|
||||
<view class="category-grid">
|
||||
<view
|
||||
v-for="category in searchCategories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
@click="searchByCategory(category)"
|
||||
>
|
||||
<view class="category-icon" :style="{ backgroundColor: category.color }">
|
||||
<text>{{ category.icon }}</text>
|
||||
</view>
|
||||
<text class="category-name">{{ category.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索建议 -->
|
||||
<view v-if="searchKeyword && searchSuggestions.length > 0" class="search-suggestions">
|
||||
<view class="suggestions-list">
|
||||
<view
|
||||
v-for="(suggestion, index) in searchSuggestions"
|
||||
:key="index"
|
||||
class="suggestion-item"
|
||||
@click="selectSuggestion(suggestion)"
|
||||
>
|
||||
<view class="suggestion-icon">🔍</view>
|
||||
<text class="suggestion-text">{{ suggestion }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view v-if="showResults" class="search-results">
|
||||
<view class="results-header">
|
||||
<text class="results-title">搜索结果</text>
|
||||
<text class="results-count">共{{ searchResults.length }}个结果</text>
|
||||
</view>
|
||||
|
||||
<view class="results-list">
|
||||
<view
|
||||
v-for="product in searchResults"
|
||||
:key="product.id"
|
||||
class="result-item"
|
||||
@click="viewProductDetail(product)"
|
||||
>
|
||||
<image class="product-image" :src="product.image" mode="aspectFill" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-spec">{{ product.specification }}</text>
|
||||
|
||||
<view class="product-meta">
|
||||
<text class="manufacturer">{{ product.manufacturer }}</text>
|
||||
<text class="sales">已售{{ product.sales }}</text>
|
||||
</view>
|
||||
|
||||
<view class="price-section">
|
||||
<view class="current-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">{{ product.price }}</text>
|
||||
</view>
|
||||
<text v-if="product.originalPrice > product.price" class="original-price">
|
||||
¥{{ product.originalPrice }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="product-tags">
|
||||
<text v-if="product.tag" class="product-tag">{{ product.tag }}</text>
|
||||
<text v-if="product.featured" class="featured-tag">{{ product.featured }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="loading" class="loading-more">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-if="!hasMore && searchResults.length > 0" class="no-more">
|
||||
<text class="no-more-text">--- 没有更多了 ---</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="showEmpty" class="empty-state">
|
||||
<view class="empty-icon">🔍</view>
|
||||
<text class="empty-title">暂无搜索结果</text>
|
||||
<text class="empty-desc">换个关键词试试吧</text>
|
||||
</view>
|
||||
|
||||
<!-- 安全区域 -->
|
||||
<view class="safe-area"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="uts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const statusBarHeight = ref(0)
|
||||
const scrollHeight = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
const showHistory = ref(true)
|
||||
const showResults = ref(false)
|
||||
const showEmpty = ref(false)
|
||||
const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
|
||||
// 搜索历史
|
||||
const searchHistory = ref<string[]>([
|
||||
'布洛芬',
|
||||
'感冒药',
|
||||
'维生素C',
|
||||
'口罩',
|
||||
'创可贴'
|
||||
])
|
||||
|
||||
// 热门搜索
|
||||
const hotSearchList = ref([
|
||||
{ keyword: '布洛芬缓释胶囊', hot: true },
|
||||
{ keyword: '连花清瘟胶囊', hot: true },
|
||||
{ keyword: '维生素C片', hot: true },
|
||||
{ keyword: '板蓝根颗粒', hot: false },
|
||||
{ keyword: '阿莫西林胶囊', hot: false },
|
||||
{ keyword: '口罩', hot: false },
|
||||
{ keyword: '体温计', hot: false },
|
||||
{ keyword: '创可贴', hot: false }
|
||||
])
|
||||
|
||||
// 搜索分类
|
||||
const searchCategories = ref([
|
||||
{ id: 'cold', name: '感冒发烧', icon: '🤧', color: '#2196F3' },
|
||||
{ id: 'stomach', name: '肠胃用药', icon: '🤢', color: '#4CAF50' },
|
||||
{ id: 'pain', name: '止痛消炎', icon: '💊', color: '#F44336' },
|
||||
{ id: 'skin', name: '皮肤用药', icon: '🤕', color: '#9C27B0' },
|
||||
{ id: 'vitamin', name: '维生素', icon: '🍊', color: '#FF9800' },
|
||||
{ id: 'chronic', name: '慢性病', icon: '🫀', color: '#795548' },
|
||||
{ id: 'child', name: '儿童用药', icon: '👶', color: '#00BCD4' },
|
||||
{ id: 'external', name: '外用药品', icon: '🧴', color: '#8BC34A' }
|
||||
])
|
||||
|
||||
// 搜索建议
|
||||
const searchSuggestions = computed(() => {
|
||||
if (!searchKeyword.value) return []
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
const suggestions = [
|
||||
'布洛芬缓释胶囊',
|
||||
'布洛芬颗粒',
|
||||
'布洛芬混悬液',
|
||||
'感冒灵颗粒',
|
||||
'感冒清热颗粒',
|
||||
'维生素C咀嚼片',
|
||||
'维生素C泡腾片',
|
||||
'阿莫西林胶囊',
|
||||
'连花清瘟胶囊',
|
||||
'板蓝根颗粒'
|
||||
]
|
||||
|
||||
return suggestions.filter(item =>
|
||||
item.toLowerCase().includes(keyword)
|
||||
).slice(0, 5)
|
||||
})
|
||||
|
||||
// 搜索结果
|
||||
const searchResults = ref<any[]>([])
|
||||
|
||||
// 模拟搜索结果数据
|
||||
const mockSearchResults = [
|
||||
{
|
||||
id: 'result1',
|
||||
name: '布洛芬缓释胶囊',
|
||||
specification: '0.3g*24粒',
|
||||
price: 18.5,
|
||||
originalPrice: 25.8,
|
||||
image: 'https://picsum.photos/300/300?random=search1',
|
||||
manufacturer: '修正药业',
|
||||
sales: 2560,
|
||||
tag: '热销',
|
||||
featured: '家庭常备'
|
||||
},
|
||||
{
|
||||
id: 'result2',
|
||||
name: '布洛芬颗粒',
|
||||
specification: '0.2g*12袋',
|
||||
price: 15.8,
|
||||
originalPrice: 20.0,
|
||||
image: 'https://picsum.photos/300/300?random=search2',
|
||||
manufacturer: '白云山',
|
||||
sales: 1890,
|
||||
tag: '推荐',
|
||||
featured: '儿童适用'
|
||||
},
|
||||
{
|
||||
id: 'result3',
|
||||
name: '感冒灵颗粒',
|
||||
specification: '10g*9袋',
|
||||
price: 22.5,
|
||||
originalPrice: 28.0,
|
||||
image: 'https://picsum.photos/300/300?random=search3',
|
||||
manufacturer: '999药业',
|
||||
sales: 3420,
|
||||
tag: '热销',
|
||||
featured: '快速缓解'
|
||||
},
|
||||
{
|
||||
id: 'result4',
|
||||
name: '维生素C咀嚼片',
|
||||
specification: '100mg*60片',
|
||||
price: 28.9,
|
||||
originalPrice: 35.0,
|
||||
image: 'https://picsum.photos/300/300?random=search4',
|
||||
manufacturer: '养生堂',
|
||||
sales: 1250,
|
||||
tag: '特价',
|
||||
featured: '增强免疫'
|
||||
}
|
||||
]
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
initPage()
|
||||
})
|
||||
|
||||
// 初始化页面
|
||||
const initPage = () => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = systemInfo.statusBarHeight || 0
|
||||
|
||||
// 计算滚动区域高度
|
||||
const windowHeight = systemInfo.windowHeight
|
||||
const headerHeight = 100 // 搜索头部高度
|
||||
scrollHeight.value = windowHeight - headerHeight
|
||||
|
||||
// 从本地存储加载搜索历史
|
||||
loadSearchHistory()
|
||||
}
|
||||
|
||||
// 加载搜索历史
|
||||
const loadSearchHistory = () => {
|
||||
try {
|
||||
const history = uni.getStorageSync('searchHistory')
|
||||
if (history) {
|
||||
searchHistory.value = JSON.parse(history)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存搜索历史
|
||||
const saveSearchHistory = () => {
|
||||
try {
|
||||
uni.setStorageSync('searchHistory', JSON.stringify(searchHistory.value))
|
||||
} catch (error) {
|
||||
console.error('保存搜索历史失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 输入处理
|
||||
const onInput = (event: any) => {
|
||||
searchKeyword.value = event.detail.value
|
||||
showHistory.value = !searchKeyword.value
|
||||
showResults.value = false
|
||||
showEmpty.value = false
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
showHistory.value = true
|
||||
showResults.value = false
|
||||
showEmpty.value = false
|
||||
}
|
||||
|
||||
// 执行搜索
|
||||
const onSearch = () => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
uni.showToast({
|
||||
title: '请输入搜索关键词',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到搜索历史
|
||||
addToSearchHistory(searchKeyword.value.trim())
|
||||
|
||||
// 显示搜索结果
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 添加到搜索历史
|
||||
const addToSearchHistory = (keyword: string) => {
|
||||
// 移除重复项
|
||||
const index = searchHistory.value.indexOf(keyword)
|
||||
if (index !== -1) {
|
||||
searchHistory.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 添加到开头
|
||||
searchHistory.value.unshift(keyword)
|
||||
|
||||
// 限制历史记录数量
|
||||
if (searchHistory.value.length > 10) {
|
||||
searchHistory.value = searchHistory.value.slice(0, 10)
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
// 从历史记录搜索
|
||||
const searchFromHistory = (keyword: string) => {
|
||||
searchKeyword.value = keyword
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 从热门搜索搜索
|
||||
const searchFromHot = (keyword: string) => {
|
||||
searchKeyword.value = keyword
|
||||
addToSearchHistory(keyword)
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 按分类搜索
|
||||
const searchByCategory = (category: any) => {
|
||||
searchKeyword.value = category.name
|
||||
addToSearchHistory(category.name)
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 选择搜索建议
|
||||
const selectSuggestion = (suggestion: string) => {
|
||||
searchKeyword.value = suggestion
|
||||
addToSearchHistory(suggestion)
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 执行搜索逻辑
|
||||
const performSearch = () => {
|
||||
showHistory.value = false
|
||||
showEmpty.value = false
|
||||
loading.value = true
|
||||
|
||||
// 模拟搜索延迟
|
||||
setTimeout(() => {
|
||||
if (searchKeyword.value.toLowerCase().includes('布洛芬') ||
|
||||
searchKeyword.value.toLowerCase().includes('感冒') ||
|
||||
searchKeyword.value.toLowerCase().includes('维生素')) {
|
||||
// 模拟搜索结果
|
||||
searchResults.value = [...mockSearchResults]
|
||||
showResults.value = true
|
||||
showEmpty.value = false
|
||||
} else {
|
||||
// 无结果
|
||||
searchResults.value = []
|
||||
showResults.value = false
|
||||
showEmpty.value = true
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
hasMore.value = true
|
||||
|
||||
// 滚动到顶部
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 查看商品详情
|
||||
const viewProductDetail = (product: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/medicine/detail?id=${product.id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 清空搜索历史
|
||||
const clearHistory = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要清空搜索历史吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
searchHistory.value = []
|
||||
saveSearchHistory()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除单个历史记录
|
||||
const deleteHistoryItem = (index: number) => {
|
||||
searchHistory.value.splice(index, 1)
|
||||
saveSearchHistory()
|
||||
}
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (loading.value || !hasMore.value) return
|
||||
|
||||
loading.value = true
|
||||
|
||||
// 模拟加载更多数据
|
||||
setTimeout(() => {
|
||||
const newResults = [...mockSearchResults].map((item, idx) => ({
|
||||
...item,
|
||||
id: `more${idx}`,
|
||||
price: Math.floor(item.price * 0.9 + Math.random() * 10)
|
||||
}))
|
||||
|
||||
searchResults.value = [...searchResults.value, ...newResults]
|
||||
loading.value = false
|
||||
hasMore.value = searchResults.value.length < 20
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
.search-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f8fafc;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe
|
||||
|
||||
271
utils/mock-category-data.uts
Normal file
271
utils/mock-category-data.uts
Normal file
@@ -0,0 +1,271 @@
|
||||
// 分类页面mock数据模块
|
||||
// 导出医药分类数据
|
||||
export const medicineCategories = [
|
||||
{ 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' },
|
||||
{ id: 'chronic', name: '慢性病', icon: '🫀', desc: '长期管理', color: '#795548' },
|
||||
{ id: 'child', name: '儿童用药', icon: '👶', desc: '儿童专用', color: '#00BCD4' },
|
||||
{ id: 'external', name: '外用药品', icon: '🧴', desc: '外用制剂', color: '#8BC34A' },
|
||||
{ id: 'device', name: '医疗器械', icon: '🩺', desc: '医疗设备', color: '#607D8B' },
|
||||
{ id: 'health', name: '健康食品', icon: '🥗', desc: '保健食品', color: '#FFC107' }
|
||||
]
|
||||
|
||||
// 导出mock商品数据
|
||||
export const mockProducts = {
|
||||
cold: [
|
||||
{
|
||||
id: 'cold1',
|
||||
name: '布洛芬缓释胶囊',
|
||||
specification: '0.3g*24粒',
|
||||
price: 18.5,
|
||||
originalPrice: 25.8,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '修正药业',
|
||||
sales: 2560,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'cold2',
|
||||
name: '板蓝根颗粒',
|
||||
specification: '10g*20袋',
|
||||
price: 22.8,
|
||||
originalPrice: 29.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '白云山',
|
||||
sales: 1890,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'cold3',
|
||||
name: '连花清瘟胶囊',
|
||||
specification: '0.35g*36粒',
|
||||
price: 42.8,
|
||||
originalPrice: 48.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '以岭药业',
|
||||
sales: 3200,
|
||||
badge: '爆款'
|
||||
},
|
||||
{
|
||||
id: 'cold4',
|
||||
name: '对乙酰氨基酚片',
|
||||
specification: '0.5g*12片',
|
||||
price: 8.9,
|
||||
originalPrice: 12.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '强生制药',
|
||||
sales: 1420,
|
||||
badge: '特价'
|
||||
},
|
||||
{
|
||||
id: 'cold5',
|
||||
name: '感冒清热颗粒',
|
||||
specification: '3g*10袋',
|
||||
price: 16.5,
|
||||
originalPrice: 19.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '同仁堂',
|
||||
sales: 980,
|
||||
badge: '新品'
|
||||
},
|
||||
{
|
||||
id: 'cold6',
|
||||
name: '复方氨酚烷胺片',
|
||||
specification: '10片/盒',
|
||||
price: 12.8,
|
||||
originalPrice: 15.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '三九医药',
|
||||
sales: 1650,
|
||||
badge: '家庭装'
|
||||
}
|
||||
],
|
||||
|
||||
stomach: [
|
||||
{
|
||||
id: 'stomach1',
|
||||
name: '胃康灵胶囊',
|
||||
specification: '0.4g*24粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.5,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '三九医药',
|
||||
sales: 890,
|
||||
badge: '热销'
|
||||
},
|
||||
{
|
||||
id: 'stomach2',
|
||||
name: '奥美拉唑肠溶胶囊',
|
||||
specification: '20mg*14粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '阿斯利康',
|
||||
sales: 1250,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'stomach3',
|
||||
name: '健胃消食片',
|
||||
specification: '0.8g*32片',
|
||||
price: 15.9,
|
||||
originalPrice: 19.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '江中制药',
|
||||
sales: 2100,
|
||||
badge: '推荐'
|
||||
},
|
||||
{
|
||||
id: 'stomach4',
|
||||
name: '蒙脱石散',
|
||||
specification: '3g*10袋',
|
||||
price: 18.6,
|
||||
originalPrice: 22.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '益普生',
|
||||
sales: 1780,
|
||||
badge: '止泻'
|
||||
},
|
||||
{
|
||||
id: 'stomach5',
|
||||
name: '多潘立酮片',
|
||||
specification: '10mg*30片',
|
||||
price: 22.8,
|
||||
originalPrice: 26.5,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '西安杨森',
|
||||
sales: 950,
|
||||
badge: '促消化'
|
||||
},
|
||||
{
|
||||
id: 'stomach6',
|
||||
name: '铝碳酸镁咀嚼片',
|
||||
specification: '0.5g*20片',
|
||||
price: 25.9,
|
||||
originalPrice: 29.9,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '拜耳',
|
||||
sales: 1320,
|
||||
badge: '护胃'
|
||||
}
|
||||
],
|
||||
|
||||
pain: [
|
||||
{
|
||||
id: 'pain1',
|
||||
name: '阿莫西林胶囊',
|
||||
specification: '0.25g*24粒',
|
||||
price: 28.5,
|
||||
originalPrice: 35.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '华北制药',
|
||||
sales: 1560,
|
||||
badge: '处方药'
|
||||
},
|
||||
{
|
||||
id: 'pain2',
|
||||
name: '双氯芬酸钠缓释片',
|
||||
specification: '75mg*10片',
|
||||
price: 19.8,
|
||||
originalPrice: 24.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '诺华制药',
|
||||
sales: 1280,
|
||||
badge: '止痛'
|
||||
},
|
||||
{
|
||||
id: 'pain3',
|
||||
name: '云南白药胶囊',
|
||||
specification: '0.25g*32粒',
|
||||
price: 35.9,
|
||||
originalPrice: 42.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '云南白药',
|
||||
sales: 2350,
|
||||
badge: '经典'
|
||||
},
|
||||
{
|
||||
id: 'pain4',
|
||||
name: '塞来昔布胶囊',
|
||||
specification: '0.2g*10粒',
|
||||
price: 48.6,
|
||||
originalPrice: 55.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '辉瑞',
|
||||
sales: 890,
|
||||
badge: '抗炎'
|
||||
},
|
||||
{
|
||||
id: 'pain5',
|
||||
name: '布洛芬片',
|
||||
specification: '0.1g*24片',
|
||||
price: 12.5,
|
||||
originalPrice: 15.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '中美史克',
|
||||
sales: 1680,
|
||||
badge: '经济装'
|
||||
},
|
||||
{
|
||||
id: 'pain6',
|
||||
name: '头孢克肟胶囊',
|
||||
specification: '0.1g*6粒',
|
||||
price: 32.8,
|
||||
originalPrice: 38.0,
|
||||
image: '/static/images/default-product.png',
|
||||
manufacturer: '广州白云山',
|
||||
sales: 1120,
|
||||
badge: '抗生素'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 为其他分类生成默认商品数据
|
||||
export const generateDefaultProducts = (categoryId: string) => {
|
||||
const baseProducts = [
|
||||
{ name: '通用药品1', price: 25.8, manufacturer: '知名药企', sales: 1200 },
|
||||
{ name: '通用药品2', price: 18.5, manufacturer: '知名药企', sales: 950 },
|
||||
{ name: '通用药品3', price: 32.0, manufacturer: '知名药企', sales: 1450 },
|
||||
{ name: '通用药品4', price: 22.8, manufacturer: '知名药企', sales: 880 },
|
||||
{ name: '通用药品5', price: 28.9, manufacturer: '知名药企', sales: 1100 },
|
||||
{ name: '通用药品6', price: 19.9, manufacturer: '知名药企', sales: 920 }
|
||||
]
|
||||
|
||||
return baseProducts.map((product, index) => ({
|
||||
id: `${categoryId}${index + 1}`,
|
||||
...product,
|
||||
specification: '规格待定',
|
||||
originalPrice: product.price * 1.2,
|
||||
image: '/static/images/default-product.png',
|
||||
badge: index === 0 ? '热销' : index === 1 ? '推荐' : ''
|
||||
}))
|
||||
}
|
||||
|
||||
// 获取分类商品数据
|
||||
export const getCategoryProducts = (categoryId: string) => {
|
||||
if (mockProducts[categoryId]) {
|
||||
return mockProducts[categoryId]
|
||||
} else {
|
||||
return generateDefaultProducts(categoryId)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分类信息
|
||||
export const getCategoryInfo = (categoryId: string) => {
|
||||
const category = medicineCategories.find(cat => cat.id === categoryId)
|
||||
if (category) {
|
||||
return {
|
||||
name: category.name,
|
||||
desc: category.desc
|
||||
}
|
||||
} else {
|
||||
// 返回默认分类信息
|
||||
return {
|
||||
name: '感冒发烧',
|
||||
desc: '解热镇痛'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user