consumer模块完成度95%,能编译在安卓端运行,在解决数据获取和页面布局问题

This commit is contained in:
cyh666666
2026-02-27 08:20:43 +08:00
parent e606c597ca
commit b9acce6c35
1554 changed files with 23471 additions and 8551 deletions

View File

@@ -51,13 +51,13 @@
<!-- 主内容区域 -->
<scroll-view
v-else
scroll-y
direction="vertical"
class="main-content"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="loadMore"
>
<!-- 初始状态(无搜索词) -->
<view v-if="!searchKeyword && !showResults">
<view v-if="searchKeyword == '' && showResults == false">
<!-- 搜索历史 -->
<view v-if="searchHistory.length > 0" class="search-history">
<view class="section-header">
@@ -92,12 +92,12 @@
v-for="(item, index) in hotSearchList"
:key="index"
class="hot-tag"
:class="{ 'hot': item.hot }"
:class="item.hot == true ? 'hot' : ''"
@click="searchFromHot(item.keyword)"
>
<text class="hot-rank" :class="{ 'top-three': index < 3 }">{{ index + 1 }}</text>
<text class="hot-rank" :class="index < 3 ? 'top-three' : ''">{{ index + 1 }}</text>
<text class="hot-text">{{ item.keyword }}</text>
<text v-if="item.hot" class="hot-icon">🔥</text>
<text v-if="item.hot == true" class="hot-icon">🔥</text>
</view>
</view>
</view>
@@ -136,7 +136,7 @@
</view>
<!-- 搜索建议 -->
<view v-if="searchKeyword && !showResults" class="search-suggestions">
<view v-if="searchKeyword != '' && showResults == false" class="search-suggestions">
<view class="suggestions-list">
<view
v-for="(suggestion, index) in searchSuggestions"
@@ -157,7 +157,7 @@
<view class="section-top">
<text class="result-title-sm">相关店铺</text>
</view>
<scroll-view scroll-x class="shop-list-scroll">
<scroll-view direction="horizontal" class="shop-list-scroll">
<view class="shop-list-row">
<view
v-for="shop in searchShopResults"
@@ -267,110 +267,47 @@ const autoFocus = ref(true)
const activeSort = ref('default') // 当前排序方式: default, sales, price
const priceSortAsc = ref(false) // 价格排序是否为升序
// 数据定义
const searchHistory = ref<string[]>([])
const hotSearchList = ref<any[]>([])
const guessList = ref<any[]>([])
const allGuessItems = ref<any[]>([]) // 缓存所有猜你喜欢商品
const searchResults = ref<any[]>([])
const searchShopResults = ref<any[]>([]) // 搜索到的店铺
onMounted(() => {
initPage()
})
const initPage = () => {
try {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
const windowHeight = systemInfo.windowHeight
// 减去头部高度 (约60px + statusBarHeight)
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' || options['type'] === 'brand') {
// 如果是家庭常备药或品牌类型,直接添加到历史并搜索
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
}
type HotSearchItemType = {
keyword: string
hot: boolean
}
// 加载基础数据
const loadData = async () => {
isError.value = false
try {
loadSearchHistory()
// 获取热门商品作为热门搜索推荐和猜你喜欢
// 获取更多数据以便"换一批"
const hotProducts = await supabaseService.getHotProducts(30)
hotSearchList.value = hotProducts.slice(0, 10).map((p: any) => ({
keyword: p.name,
hot: true
}))
allGuessItems.value = hotProducts.map((p: any) => ({
id: p.id,
name: p.name,
price: p.base_price,
image: p.main_image_url ?? '/static/default.jpg',
sales: typeof p.sale_count === 'number' ? p.sale_count : 0
}))
// 初始显示随机6个
refreshGuessListItems()
} catch (e) {
console.error('Load data failed', e)
isError.value = true
}
type GuessItemType = {
id: string
name: string
price: number
image: string
sales: number
}
// 点击重试
const retryLoad = () => {
uni.showLoading({ title: '重新加载中' })
setTimeout(() => {
uni.hideLoading()
loadData()
}, 1000)
type SearchResultType = {
id: string
name: string
image: string
price: number
specification: string
tag: string
sales: number
}
// 历史记录管理
type ShopResultType = {
id: string
name: string
logo: string
productCount: number
}
const searchHistory = ref<Array<string>>([])
const hotSearchList = ref<Array<HotSearchItemType>>([])
const guessList = ref<Array<GuessItemType>>([])
const allGuessItems = ref<Array<GuessItemType>>([])
const searchResults = ref<Array<SearchResultType>>([])
const searchShopResults = ref<Array<ShopResultType>>([])
const loadSearchHistory = () => {
const history = uni.getStorageSync('searchHistory')
if (history) {
if (history != null) {
try {
// 确保是数组
const parsed = JSON.parse(history as string)
if (Array.isArray(parsed)) {
searchHistory.value = parsed as string[]
@@ -414,13 +351,258 @@ const deleteHistoryItem = (index: number) => {
saveSearchHistory()
}
// 搜索建议 - 改为实时获取
const searchSuggestions = ref<string[]>([])
let suggestTimer = 0
const refreshGuessListItems = () => {
if (allGuessItems.value.length > 0) {
const arr: Array<GuessItemType> = []
for (let i: number = 0; i < allGuessItems.value.length; i++) {
arr.push(allGuessItems.value[i])
}
for (let i: number = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
const result: Array<GuessItemType> = []
const limit = arr.length < 6 ? arr.length : 6
for (let i: number = 0; i < limit; i++) {
result.push(arr[i])
}
guessList.value = result
}
}
const loadData = async (): Promise<void> => {
isError.value = false
try {
loadSearchHistory()
const hotProducts = await supabaseService.getHotProducts(30)
const hotList: Array<HotSearchItemType> = []
const limit1 = hotProducts.length < 10 ? hotProducts.length : 10
for (let i: number = 0; i < limit1; i++) {
const p = hotProducts[i] as UTSJSONObject
const item: HotSearchItemType = {
keyword: p.getString('name') ?? '',
hot: true
}
hotList.push(item)
}
hotSearchList.value = hotList
const allItems: Array<GuessItemType> = []
for (let i: number = 0; i < hotProducts.length; i++) {
const p = hotProducts[i] as UTSJSONObject
const saleCount = p.getNumber('sale_count')
const item: GuessItemType = {
id: p.getString('id') ?? '',
name: p.getString('name') ?? '',
price: p.getNumber('base_price') ?? 0,
image: p.getString('main_image_url') ?? '/static/default.jpg',
sales: saleCount != null ? saleCount : 0
}
allItems.push(item)
}
allGuessItems.value = allItems
refreshGuessListItems()
} catch (e) {
console.error('Load data failed', e)
isError.value = true
}
}
const retryLoad = () => {
uni.showLoading({ title: '重新加载中' })
setTimeout(() => {
uni.hideLoading()
loadData()
}, 1000)
}
const searchSuggestions = ref<Array<string>>([])
let suggestTimer: number = 0
const fetchSuggestions = async (kw: string): Promise<void> => {
if (kw == '' || showResults.value) return
try {
const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
if (res.data != null && res.data.length > 0) {
const names: Array<string> = []
for (let i: number = 0; i < res.data.length; i++) {
const p = res.data[i]
let name = ''
if (p instanceof UTSJSONObject) {
name = p.getString('name') ?? ''
} else {
const pObj = p as UTSJSONObject
name = pObj.getString('name') ?? ''
}
let found = false
for (let j: number = 0; j < names.length; j++) {
if (names[j] === name) {
found = true
break
}
}
if (found === false && name !== '') {
names.push(name)
}
}
searchSuggestions.value = names
} else {
searchSuggestions.value = []
}
} catch(e) {
searchSuggestions.value = []
}
}
const currentPage = ref<number>(1)
const performSearch = async (): Promise<void> => {
showResults.value = true
loading.value = true
currentPage.value = 1
const keyword = searchKeyword.value.trim()
if (keyword == '') {
loading.value = false
return
}
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
sortBy = 'price'
ascending = priceSortAsc.value
} else if (activeSort.value === 'default') {
sortBy = 'default'
}
try {
const prodResp = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
let shopRespData: Array<any> = []
if (currentPage.value === 1 && activeSort.value === 'default') {
const shopResp = await supabaseService.searchShops(keyword)
if (shopResp.data != null) {
const rawData = shopResp.data
for (let i: number = 0; i < rawData.length; i++) {
shopRespData.push(rawData[i])
}
}
}
if (shopRespData.length > 0) {
const shopList: Array<ShopResultType> = []
for (let i: number = 0; i < shopRespData.length; i++) {
const s = shopRespData[i] as UTSJSONObject
const shopItem: ShopResultType = {
id: s.getString('id') ?? '',
name: s.getString('shop_name') ?? '',
logo: s.getString('shop_logo') ?? '/static/shop_logo_default.png',
productCount: s.getNumber('product_count') ?? 0
}
shopList.push(shopItem)
}
searchShopResults.value = shopList
} else {
searchShopResults.value = []
}
const prodData = prodResp.data != null ? prodResp.data : []
const resultList: Array<SearchResultType> = []
for (let i: number = 0; i < prodData.length; i++) {
const p = prodData[i] as UTSJSONObject
let tag = ''
const tagsRaw = p.get('tags')
if (tagsRaw != null) {
try {
const tagsStr = p.getString('tags')
if (tagsStr != null) {
const tags = JSON.parse(tagsStr)
if (Array.isArray(tags) && tags.length > 0) {
const firstTag = tags[0]
tag = firstTag != null ? (firstTag as string) : ''
}
}
} catch(e) {}
}
const searchItem: SearchResultType = {
id: p.getString('id') ?? '',
name: p.getString('name') ?? '',
image: p.getString('main_image_url') ?? '/static/default.jpg',
price: p.getNumber('base_price') ?? 0,
specification: p.getString('specification') ?? '标准规格',
tag: tag,
sales: p.getNumber('sale_count') ?? 0
}
resultList.push(searchItem)
}
searchResults.value = resultList
hasMore.value = prodResp.hasmore
} catch(e) {
console.error('Search failed', e)
} finally {
loading.value = false
}
}
const initPage = () => {
try {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
const windowHeight = systemInfo.windowHeight
scrollHeight.value = windowHeight - (60 + statusBarHeight.value)
loadData()
const pages = getCurrentPages()
if (pages.length > 0) {
const currentPageObj = pages[pages.length - 1]
// @ts-ignore
const options = currentPageObj.options
if (options != null) {
const optObj = options as UTSJSONObject
const kwRaw = optObj.getString('keyword')
if (kwRaw != null && kwRaw !== '') {
const decoded = decodeURIComponent(kwRaw)
const keyword = decoded != null ? decoded : kwRaw
searchKeyword.value = keyword
const typeVal = optObj.getString('type')
if (typeVal === 'family' || typeVal === 'brand') {
if (typeVal === 'family') {
addToHistory(keyword)
}
showResults.value = true
loading.value = true
performSearch()
}
}
}
}
} catch (e) {
console.error('初始化失败', e)
isError.value = true
}
}
onMounted(() => {
initPage()
})
// 搜索逻辑
const onInput = (e: any) => {
const val = e.detail.value
const eObj = e as UTSJSONObject
const detailRaw = eObj.get('detail')
const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject())
const val = detail.getString('value') ?? ''
searchKeyword.value = val
if (val == '') {
showResults.value = false
@@ -428,37 +610,12 @@ const onInput = (e: any) => {
return
}
// Debounce suggestion search
if (suggestTimer > 0) clearTimeout(suggestTimer)
suggestTimer = setTimeout(() => {
fetchSuggestions(val)
}, 300)
}
const fetchSuggestions = async (kw: string) => {
if (kw == '' || showResults.value) return
// 简单搜索前5个相关商品作为建议
try {
const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
if (Array.isArray(res.data) && res.data.length > 0) {
// 去重
const names = res.data.map((p:any) :string => {
if(p instanceof UTSJSONObject){
return p.getString('name') ?? ''
}
return p['name'] as string
})
// @ts-ignore
searchSuggestions.value = Array.from(new Set(names))
} else {
searchSuggestions.value = []
}
} catch(e) {
searchSuggestions.value = []
}
}
const clearSearch = () => {
searchKeyword.value = ''
showResults.value = false
@@ -487,84 +644,6 @@ const selectSuggestion = (suggestion: string) => {
performSearch()
}
const currentPage = ref(1)
const performSearch = async () => {
// 再次强制设置状态,确保万无一失
showResults.value = true
loading.value = true
// 重置页码
currentPage.value = 1
// 使用 Supabase 搜索真实数据
const keyword = searchKeyword.value.trim()
if (keyword == '') {
loading.value = false
return
}
// 确定排序方式
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
sortBy = 'price'
ascending = priceSortAsc.value
} else if (activeSort.value === 'default') {
sortBy = 'default'
}
try {
// 并行请求:商品搜索 + 店铺搜索
const [prodResp, shopResp] = await Promise.all([
supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending),
// 只有第一页搜索且非价格排序时搜索店铺,避免重复和无关搜索
currentPage.value === 1 && activeSort.value === 'default'
? supabaseService.searchShops(keyword)
: Promise.resolve({ data: [], total: 0, page: 1, limit: 0, hasmore: false })
])
// 处理店铺结果
if (shopResp.data.length > 0) {
searchShopResults.value = shopResp.data.map((s: any) => ({
id: s.id,
name: s.shop_name,
logo: s.shop_logo ?? '/static/shop_logo_default.png',
productCount: s.product_count ?? 0
}))
} else {
searchShopResults.value = []
}
// 处理商品结果
searchResults.value = prodResp.data.map((p: any) => {
let tag = ''
if (p.tags) {
try {
const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
} catch(e) {}
}
return {
id: p.id,
name: p.name,
image: p.main_image_url ?? '/static/default.jpg',
price: p.base_price,
specification: p.specification ?? '标准规格',
tag: tag,
sales: p.sale_count ?? 0
}
})
hasMore.value = prodResp.hasmore
} catch(e) {
console.error('Search failed', e)
} finally {
loading.value = false
}
}
// 切换排序
const switchSort = (type: string) => {
if (type === 'price') {
if (activeSort.value === 'price') {
@@ -580,15 +659,13 @@ const switchSort = (type: string) => {
performSearch()
}
const loadMore = async () => {
if (loading.value || !hasMore.value || searchKeyword.value.trim() == '') return
const loadMore = async (): Promise<void> => {
if (loading.value || hasMore.value == false || searchKeyword.value.trim() == '') return
loading.value = true
// 增加页码
currentPage.value++
const keyword = searchKeyword.value.trim()
// 确定排序方式
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
@@ -600,26 +677,35 @@ const loadMore = async () => {
try {
const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
const newItems = response.data.map((p: any) => {
const respData = response.data != null ? response.data : []
for (let i: number = 0; i < respData.length; i++) {
const p = respData[i] as UTSJSONObject
let tag = ''
if (p.tags) {
const tagsRaw = p.get('tags')
if (tagsRaw != null) {
try {
const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
const tagsStr = p.getString('tags')
if (tagsStr != null) {
const tags = JSON.parse(tagsStr)
if (Array.isArray(tags) && tags.length > 0) {
const firstTag = tags[0]
tag = firstTag != null ? (firstTag as string) : ''
}
}
} catch(e) {}
}
return {
id: p.id,
name: p.name,
image: p.main_image_url ?? '/static/default.jpg',
price: p.base_price,
specification: p.specification ?? '标准规格',
const searchItem: SearchResultType = {
id: p.getString('id') ?? '',
name: p.getString('name') ?? '',
image: p.getString('main_image_url') ?? '/static/default.jpg',
price: p.getNumber('base_price') ?? 0,
specification: p.getString('specification') ?? '标准规格',
tag: tag,
sales: p.sale_count ?? 0
sales: p.getNumber('sale_count') ?? 0
}
})
searchResults.value.push(...newItems)
searchResults.value.push(searchItem)
}
hasMore.value = response.hasmore
} catch(e) {
console.error('Load more failed', e)
@@ -637,29 +723,22 @@ const refreshGuessList = () => {
}, 500)
}
const refreshGuessListItems = () => {
if (allGuessItems.value.length > 0) {
// 简单的随机乱序并取前6个
const shuffled = [...allGuessItems.value].sort(() => Math.random() - 0.5)
guessList.value = shuffled.slice(0, 6)
}
}
const viewProductDetail = (item: any) => {
// 跳转详情页逻辑 - 传递必要的参数作为预加载/fallback
const viewProductDetail = (item: SearchResultType | GuessItemType) => {
const id = (item as GuessItemType).id
const price = (item as GuessItemType).price
const name = (item as GuessItemType).name
uni.navigateTo({
url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&name=${encodeURIComponent(item.name)}`
url: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}`
})
}
const viewShopDetail = (shop: any) => {
const viewShopDetail = (shop: ShopResultType) => {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
})
}
// 添加到购物车 - 搜索列表无法选择规格,跳转详情页
const addToCart = (product: any) => {
const addToCart = (product: SearchResultType | GuessItemType) => {
uni.showToast({ title: '请选择规格', icon: 'none' })
setTimeout(() => {
viewProductDetail(product)