继续补充功能页面,consumer模块完成度70%

This commit is contained in:
2026-01-26 08:16:41 +08:00
parent 0ed62a8258
commit be90f1213b
17 changed files with 4859 additions and 8793 deletions

View File

@@ -3,14 +3,13 @@
<!-- 搜索头部 -->
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="search-bar-container">
<!-- 返回按钮 -->
<!-- 返回按钮:小于号加粗 -->
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
<text class="back-icon">&lt;</text>
</view>
<!-- 搜索框 -->
<view class="search-input-container">
<view class="search-icon">🔍</view>
<input
class="search-input"
type="text"
@@ -21,14 +20,21 @@
placeholder-class="placeholder"
:focus="autoFocus"
/>
<!-- 清除按钮 -->
<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 class="camera-btn" @click="openCamera">
<text class="camera-icon">📷</text>
</view>
<!-- 搜索按钮:移入输入框内部 -->
<view class="inner-search-btn" @click="onSearch">
<text class="inner-search-text">搜索</text>
</view>
</view>
</view>
</view>
@@ -149,9 +155,23 @@
<view class="results-header">
<text class="results-title">搜索结果</text>
<view class="filter-tabs">
<text class="filter-tab active">综合</text>
<text class="filter-tab">销量</text>
<text class="filter-tab">价格</text>
<text
class="filter-tab"
:class="{ active: activeSort === 'default' }"
@click="switchSort('default')"
>综合</text>
<text
class="filter-tab"
:class="{ active: activeSort === 'sales' }"
@click="switchSort('sales')"
>销量</text>
<text
class="filter-tab"
:class="{ active: activeSort === 'price' }"
@click="switchSort('price')"
>
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
</text>
</view>
</view>
@@ -175,7 +195,7 @@
<text class="price-symbol">¥</text>
<text class="price-value">{{ product.price }}</text>
</view>
<view class="add-cart-btn">
<view class="add-cart-btn" @click.stop="addToCart(product)">
<text class="cart-icon">+</text>
</view>
</view>
@@ -183,14 +203,14 @@
</view>
</view>
<!-- 空结果 -->
<view v-if="searchResults.length === 0 && !loading" class="empty-result">
<!-- 空结果 - 仅在非加载状态且无结果时显示 -->
<view v-if="!loading && searchResults.length === 0" class="empty-result">
<text class="empty-icon">🤔</text>
<text class="empty-text">未找到相关商品</text>
<text class="empty-sub">换个关键词试试吧</text>
</view>
<!-- 加载更多 -->
<!-- 加载更多/加载中 - 在加载状态或有更多数据时显示 -->
<view v-if="loading" class="loading-more">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
@@ -219,6 +239,8 @@ const loading = ref(false)
const hasMore = ref(true)
const isError = ref(false) // 错误状态控制
const autoFocus = ref(true)
const activeSort = ref('default') // 当前排序方式: default, sales, price
const priceSortAsc = ref(false) // 价格排序是否为升序
// 数据定义
const searchHistory = ref<string[]>([])
@@ -271,6 +293,31 @@ const initPage = () => {
scrollHeight.value = windowHeight - (60 + statusBarHeight.value)
loadData()
// 检查页面参数
const pages = getCurrentPages()
if (pages.length > 0) {
const currentPage = pages[pages.length - 1]
// @ts-ignore
const options = currentPage.options
if (options && options['keyword']) {
const keyword = decodeURIComponent(options['keyword'])
searchKeyword.value = keyword
if (options['type'] === 'family') {
// 如果是家庭常备药类型,直接添加到历史并搜索
addToHistory(keyword)
// 立即显示结果区域并设置为加载中
showResults.value = true
loading.value = true
// 确保searchResults不为空数组导致闪烁虽然loading=true已经拦截了empty-result但双重保险
// 此时不要置空searchResults或者给一个初始值
// 直接调用搜索移除setTimeout防止中间状态
performSearch()
}
}
}
} catch (e) {
console.error('初始化失败', e)
isError.value = true
@@ -279,7 +326,7 @@ const initPage = () => {
// 加载基础数据
const loadData = () => {
loading.value = true
// loading.value = true // 不使用全局loading避免影响搜索状态
isError.value = false
// 模拟网络请求
@@ -288,10 +335,10 @@ const loadData = () => {
loadSearchHistory()
hotSearchList.value = mockDatabase.hot
guessList.value = mockDatabase.guess
loading.value = false
// loading.value = false // 不使用全局loading
} catch (e) {
isError.value = true
loading.value = false
// loading.value = false
}
}, 500)
}
@@ -391,37 +438,84 @@ const selectSuggestion = (suggestion: string) => {
}
const performSearch = () => {
// 再次强制设置状态,确保万无一失
showResults.value = true
loading.value = true
searchResults.value = [] // 清空旧结果
// 注意:这里不要清空 searchResults.value = [],否则如果 loading 状态切换有微小延迟,可能会短暂满足 "无数据且非加载" 的条件
// 保持旧数据直到新数据回来,或者依靠 loading 状态完全遮罩
// 模拟搜索请求
setTimeout(() => {
loading.value = false
// 生成模拟结果
searchResults.value = Array.from({ length: 6 }, (_, i) => ({
id: `s${i}`,
const newResults = Array.from({ length: 6 }, (_, i) => ({
id: `s${Date.now()}${i}`, // 确保ID唯一
shopId: i % 2 === 0 ? 'shop_self' : `shop_${i}_${Date.now()}`,
shopName: i % 2 === 0 ? '平台自营大药房' : '阿里健康大药房',
name: `${searchKeyword.value}相关药品-${i+1}`,
specification: '10g*12袋',
price: (Math.random() * 50 + 10).toFixed(1),
image: `https://picsum.photos/300/300?random=s${i}`,
image: '/static/images/default-product.png', // 使用本地默认图片
sales: Math.floor(Math.random() * 1000),
tag: i % 2 === 0 ? '自营' : ''
}))
// 数据准备好后再关闭 loading确保无缝衔接
searchResults.value = newResults
// 应用当前排序
sortResults()
loading.value = false
hasMore.value = true
}, 800)
}
// 切换排序
const switchSort = (type: string) => {
if (type === 'price') {
if (activeSort.value === 'price') {
priceSortAsc.value = !priceSortAsc.value
} else {
activeSort.value = 'price'
priceSortAsc.value = true // 默认升序
}
} else {
activeSort.value = type
}
sortResults()
}
// 执行排序逻辑
const sortResults = () => {
const list = [...searchResults.value]
if (activeSort.value === 'sales') {
// 销量降序
list.sort((a, b) => b.sales - a.sales)
} else if (activeSort.value === 'price') {
// 价格排序
list.sort((a, b) => {
const p1 = parseFloat(a.price)
const p2 = parseFloat(b.price)
return priceSortAsc.value ? (p1 - p2) : (p2 - p1)
})
} else {
// 综合排序这里简单按ID倒序模拟
list.sort((a, b) => (a.id > b.id ? -1 : 1))
}
searchResults.value = list
}
const loadMore = () => {
if (loading.value || !hasMore.value) return
loading.value = true
setTimeout(() => {
const newItems = Array.from({ length: 4 }, (_, i) => ({
id: `more${Date.now()}${i}`,
shopId: i % 2 === 0 ? 'shop_self' : `shop_more_${i}_${Date.now()}`,
shopName: i % 2 === 0 ? '平台自营大药房' : '好药师大药房',
name: `${searchKeyword.value}更多药品-${i+1}`,
specification: '盒装',
price: (Math.random() * 50 + 10).toFixed(1),
image: `https://picsum.photos/300/300?random=m${i}`,
image: '/static/images/default-product.png',
sales: Math.floor(Math.random() * 500),
tag: ''
}))
@@ -442,11 +536,77 @@ const refreshGuessList = () => {
const viewProductDetail = (item: any) => {
// 跳转详情页逻辑
console.log('查看商品', item)
uni.showToast({ title: '点击了商品: ' + item.name, icon: 'none' })
uni.navigateTo({
url: `/pages/mall/consumer/product-detail?productId=${item.id}`
})
}
// 添加到购物车
const addToCart = (product: any) => {
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let cartItems: any[] = []
if (cartData) {
try {
cartItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
}
}
// 检查商品是否已存在
const existingItem = cartItems.find((item: any) => item.id === product.id)
if (existingItem) {
existingItem.quantity++
} else {
// 添加新商品
cartItems.push({
id: product.id,
shopId: product.shopId || 'shop_search_default',
shopName: product.shopName || (product.tag === '自营' ? '平台自营大药房' : '优质大药房'),
name: product.name,
price: product.price,
image: product.image,
spec: product.specification || '默认规格',
quantity: 1,
selected: true
})
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(cartItems))
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
}
const openCamera = () => {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: (res) => {
console.log('拍摄图片路径:', res.tempFilePaths[0])
uni.showToast({ title: '已启用相机', icon: 'none' })
},
fail: (err) => {
console.error('启用相机失败', err)
}
})
}
const goBack = () => {
uni.navigateBack()
if (showResults.value) {
// 如果在搜索结果页,先返回到搜索初始页
showResults.value = false
searchKeyword.value = ''
} else {
// 如果在搜索初始页,则返回上一页
uni.navigateBack()
}
}
</script>
@@ -463,41 +623,46 @@ const goBack = () => {
.search-header {
background-color: #ffffff;
padding-bottom: 10px;
position: sticky;
top: 0;
z-index: 100;
/* #ifdef APP-PLUS */
padding-top: 0; /* 在App端由style动态控制 */
/* #endif */
}
.search-bar-container {
display: flex;
flex-direction: row; /* UVUE 必须显式设置 row */
align-items: center;
padding: 10px 16px;
gap: 12px;
width: 100%; /* 确保占满宽度 */
}
.back-btn {
padding: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 32px; /* 固定宽度防止压缩 */
height: 32px;
}
.back-icon {
font-size: 24px;
color: #333;
font-weight: bold;
font-family: monospace;
}
.search-input-container {
flex: 1;
height: 36px;
flex: 1; /* 占据剩余空间 */
height: 40px; /*稍微增高一点以容纳按钮*/
background-color: #f0f0f0;
border-radius: 18px;
border-radius: 20px;
display: flex;
flex-direction: row; /* UVUE 必须显式设置 row */
align-items: center;
padding: 0 12px;
}
.search-icon {
font-size: 16px;
margin-right: 8px;
color: #999;
padding: 0 4px 0 12px;
}
.search-input {
@@ -505,6 +670,7 @@ const goBack = () => {
font-size: 14px;
color: #333;
height: 100%;
background-color: transparent; /* 确保背景透明 */
}
.placeholder {
@@ -513,6 +679,10 @@ const goBack = () => {
.clear-btn {
padding: 4px;
margin-right: 2px;
display: flex;
align-items: center;
justify-content: center;
}
.clear-icon {
@@ -520,13 +690,37 @@ const goBack = () => {
color: #999;
}
.search-btn {
padding: 4px 0;
.camera-btn {
padding: 4px 8px 4px 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-right-width: 1px; /* UVUE 边框写法 */
border-right-style: solid;
border-right-color: #ddd;
margin-right: 8px;
}
.search-btn-text {
font-size: 15px;
color: #4CAF50;
.camera-icon {
font-size: 20px;
}
/* 内部搜索按钮样式 */
.inner-search-btn {
padding: 0 16px;
background-color: #87CEEB;
border-radius: 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 32px;
}
.inner-search-text {
font-size: 13px;
color: #ffffff;
font-weight: 500;
}
@@ -540,22 +734,27 @@ const goBack = () => {
/* 模块通用头部 */
.section-header {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-top: 8px;
width: 100%;
}
.section-title {
font-size: 15px;
font-weight: bold;
color: #333;
flex: 1; /* 占据左侧空间 */
}
.header-right {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
align-items: center;
gap: 4px;
flex-shrink: 0; /* 防止被压缩 */
}
.clear-text {
@@ -570,12 +769,16 @@ const goBack = () => {
/* 搜索历史 */
.search-history {
margin-bottom: 24px;
padding: 0 4px; /* 微调内边距 */
}
.history-tags {
display: flex;
flex-wrap: wrap;
flex-direction: row; /* UVUE 显式设置 row */
gap: 10px;
flex-wrap: wrap; /* 允许换行 */
padding: 0 4px;
align-items: center;
}
.history-tag {
@@ -585,7 +788,7 @@ const goBack = () => {
display: flex;
align-items: center;
gap: 6px;
max-width: 100%;
flex-shrink: 0; /* 防止被压缩 */
}
.history-text {
@@ -619,17 +822,21 @@ const goBack = () => {
.hot-tags {
display: flex;
flex-wrap: wrap;
flex-direction: row; /* UVUE 显式设置 row */
flex-wrap: wrap; /* 允许换行 */
gap: 10px;
padding: 0 4px;
}
.hot-tag {
background-color: #fff;
padding: 6px 12px;
border-radius: 4px;
border-radius: 16px; /* 增加圆角,像胶囊一样 */
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
flex-shrink: 0; /* 防止被压缩 */
}
.hot-tag.hot {
@@ -773,9 +980,12 @@ const goBack = () => {
.results-header {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
flex-wrap: wrap; /* 允许换行以适应小屏 */
gap: 8px;
}
.results-title {
@@ -786,12 +996,16 @@ const goBack = () => {
.filter-tabs {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
gap: 16px;
flex: 1; /* 自适应填充剩余空间 */
justify-content: flex-end; /* 靠右对齐 */
}
.filter-tab {
font-size: 13px;
color: #666;
padding: 4px 8px; /* 增加点击区域 */
}
.filter-tab.active {
@@ -800,22 +1014,56 @@ const goBack = () => {
}
.results-list {
display: flex;
flex-direction: column;
gap: 12px;
display: grid;
grid-template-columns: repeat(2, 1fr); /* 默认移动端双列 */
gap: 10px;
padding: 0 4px;
}
/* 响应式布局 */
/* 平板设备 (768px以上) */
@media screen and (min-width: 768px) {
.results-list {
grid-template-columns: repeat(3, 1fr); /* 平板显示3列 */
gap: 16px;
padding: 0 16px;
}
.guess-grid {
grid-template-columns: repeat(4, 1fr); /* 猜你喜欢在平板上显示4列 */
}
}
/* 桌面设备 (1024px以上) */
@media screen and (min-width: 1024px) {
.results-list {
grid-template-columns: repeat(4, 1fr); /* 桌面显示4列 */
gap: 20px;
padding: 0 24px;
}
.guess-grid {
grid-template-columns: repeat(6, 1fr); /* 猜你喜欢在桌面上显示6列 */
}
/* 桌面端调整图片高度 */
.product-image {
height: 160px;
}
}
.result-item {
background-color: #fff;
border-radius: 8px;
padding: 10px;
padding: 8px;
display: flex;
gap: 12px;
flex-direction: column; /* 垂直排列 */
gap: 8px;
}
.product-image {
width: 100px;
height: 100px;
width: 100%;
height: 120px; /* 调整图片高度 */
border-radius: 4px;
background-color: #f0f0f0;
}
@@ -828,44 +1076,47 @@ const goBack = () => {
}
.product-name {
font-size: 15px;
font-size: 13px; /* 减小字号 */
color: #333;
font-weight: 500;
line-height: 1.4;
line-height: 1.3;
height: 34px; /* 限制高度 */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-tags-row {
margin-top: 4px;
}
.product-tag {
font-size: 10px;
color: #ff5000;
border: 1px solid #ff5000;
padding: 1px 4px;
border-radius: 2px;
margin-top: 2px;
display: none; /* 隐藏标签以保持简洁 */
}
.product-spec {
font-size: 12px;
color: #999;
margin-top: 4px;
display: none; /* 隐藏规格 */
}
.product-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 8px;
align-items: center; /* 垂直居中 */
margin-top: 4px;
}
.price-box {
color: #ff5000;
display: flex;
align-items: baseline;
}
.price-symbol {
font-size: 10px;
}
.price-value {
font-size: 18px;
font-weight: bold;
font-size: 16px; /* 减小价格字号 */
font-weight: 600;
}
.add-cart-btn {
@@ -880,7 +1131,7 @@ const goBack = () => {
.cart-icon {
color: #fff;
font-size: 16px;
font-size: 14px;
font-weight: bold;
}