当前位置: 首页 > news >正文

鸿蒙OSUniApp智能商品展示实战:打造高性能的动态排序系统#三方框架 #Uniapp

UniApp智能商品展示实战:打造高性能的动态排序系统

引言

在电商应用开发中,商品展示和智能排序是提升用户体验的关键因素。随着HarmonyOS生态的发展,用户对应用的性能和交互体验要求越来越高。本文将深入探讨如何在UniApp中实现一个性能优异、体验流畅的智能商品排序展示系统。

技术方案设计

1. 核心功能规划

  1. 多维度排序支持

    • 价格排序(升序/降序)
    • 销量排序
    • 好评率排序
    • 综合排序算法
    • 智能推荐排序
  2. 性能优化重点

    • 虚拟列表实现
    • 数据分页加载
    • 排序状态缓存
    • 图片懒加载

2. 技术选型

  • 前端框架:UniApp + Vue3 + TypeScript
  • 状态管理:Pinia
  • 数据处理:Lodash-es
  • 虚拟列表:vue-virtual-scroller
  • 图片优化:自定义懒加载指令

核心代码实现

1. 商品列表组件

<!-- components/ProductList.vue -->
<template><view class="product-list"><!-- 排序工具栏 --><view class="sort-toolbar"><view v-for="(item, index) in sortOptions" :key="index"class="sort-item":class="{ active: currentSort === item.value }"@tap="handleSort(item.value)"><text>{{ item.label }}</text><view class="sort-icon" v-if="item.sortable"><text class="iconfont icon-arrow-up":class="{ active: currentSort === item.value && sortOrder === 'asc' }"></text><text class="iconfont icon-arrow-down":class="{ active: currentSort === item.value && sortOrder === 'desc' }"></text></view></view></view><!-- 商品列表 --><recycle-listclass="product-container":items="sortedProducts":item-size="220"key-field="id"@scroll="handleScroll"><template #default="{ item }"><view class="product-item"><view class="product-image"><lazy-image:src="item.imageUrl":aspect-ratio="1"loading="lazy"@load="handleImageLoad"/><view class="product-tags" v-if="item.tags?.length"><text v-for="tag in item.tags" :key="tag.id"class="tag":class="tag.type">{{ tag.text }}</text></view></view><view class="product-info"><text class="product-name">{{ item.name }}</text><text class="product-desc">{{ item.description }}</text><view class="product-meta"><view class="price"><text class="currency">¥</text><text class="amount">{{ formatPrice(item.price) }}</text></view><view class="sales"><text class="count">{{ formatNumber(item.salesCount) }}</text><text class="unit">已售</text></view></view><view class="product-rating" v-if="item.rating"><rate-display :value="item.rating" :size="24" /><text class="rating-count">({{ formatNumber(item.ratingCount) }})</text></view></view></view></template></recycle-list><!-- 加载状态 --><view class="loading-status"><template v-if="isLoading"><loading-spinner size="24" /><text>加载中...</text></template><template v-else-if="hasMore"><text>上拉加载更多</text></template><template v-else><text>没有更多数据了</text></template></view></view>
</template><script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { useProductStore } from '@/stores/product'
import { usePreferenceStore } from '@/stores/preference'
import { debounce } from 'lodash-es'
import type { SortOption, Product, SortOrder } from '@/types'// 排序选项配置
const sortOptions: SortOption[] = [{ label: '综合', value: 'comprehensive', sortable: false },{ label: '销量', value: 'sales', sortable: true },{ label: '价格', value: 'price', sortable: true },{ label: '好评', value: 'rating', sortable: true },{ label: '智能', value: 'smart', sortable: false }
]// 状态管理
const productStore = useProductStore()
const preferenceStore = usePreferenceStore()// 响应式数据
const currentSort = ref('comprehensive')
const sortOrder = ref<SortOrder>('desc')
const isLoading = ref(false)
const hasMore = ref(true)
const pageSize = 20
const currentPage = ref(1)// 计算属性:排序后的商品列表
const sortedProducts = computed(() => {const products = [...productStore.products]switch (currentSort.value) {case 'price':return products.sort((a, b) => {return sortOrder.value === 'asc' ? a.price - b.price : b.price - a.price})case 'sales':return products.sort((a, b) => {return sortOrder.value === 'asc'? a.salesCount - b.salesCount: b.salesCount - a.salesCount})case 'rating':return products.sort((a, b) => {return sortOrder.value === 'asc'? a.rating - b.rating: b.rating - a.rating})case 'smart':return applySortingAlgorithm(products)default:return applyComprehensiveSorting(products)}
})// 智能排序算法
const applySortingAlgorithm = (products: Product[]) => {const userPreferences = preferenceStore.preferencesreturn products.sort((a, b) => {// 计算商品得分const scoreA = calculateProductScore(a, userPreferences)const scoreB = calculateProductScore(b, userPreferences)return scoreB - scoreA})
}// 商品得分计算
const calculateProductScore = (product: Product, preferences: any) => {let score = 0// 基础分数:销量、评分、价格等维度score += product.salesCount * 0.4score += product.rating * 0.3score += (1 / product.price) * 0.2// 用户偏好加权if (preferences.categories?.includes(product.category)) {score *= 1.2}if (preferences.brands?.includes(product.brand)) {score *= 1.1}// 时间衰减因子const daysSincePublish = calculateDaysDiff(product.publishTime, new Date())score *= Math.exp(-0.01 * daysSincePublish)return score
}// 综合排序实现
const applyComprehensiveSorting = (products: Product[]) => {return products.sort((a, b) => {// 多维度权重计算const weightA = calculateWeight(a)const weightB = calculateWeight(b)return weightB - weightA})
}// 权重计算函数
const calculateWeight = (product: Product) => {const salesWeight = 0.35const ratingWeight = 0.25const priceWeight = 0.2const timeWeight = 0.2const normalizedSales = normalize(product.salesCount, 0, 10000)const normalizedRating = product.rating / 5const normalizedPrice = 1 - normalize(product.price, 0, 10000)const normalizedTime = normalize(new Date(product.publishTime).getTime(),new Date().getTime() - 30 * 24 * 60 * 60 * 1000,new Date().getTime())return (salesWeight * normalizedSales +ratingWeight * normalizedRating +priceWeight * normalizedPrice +timeWeight * normalizedTime)
}// 数值归一化
const normalize = (value: number, min: number, max: number) => {return (value - min) / (max - min)
}// 排序处理
const handleSort = (sortType: string) => {if (currentSort.value === sortType && sortOptions.find(opt => opt.value === sortType)?.sortable) {// 切换排序方向sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'} else {currentSort.value = sortTypesortOrder.value = 'desc'}// 重置分页currentPage.value = 1hasMore.value = true// 重新加载数据loadProducts()
}// 加载商品数据
const loadProducts = async () => {if (isLoading.value || !hasMore.value) returntry {isLoading.value = trueconst params = {page: currentPage.value,pageSize,sortBy: currentSort.value,sortOrder: sortOrder.value}const { items, total } = await productStore.fetchProducts(params)hasMore.value = items.length === pageSizecurrentPage.value++} catch (error) {uni.showToast({title: '加载失败,请重试',icon: 'none'})} finally {isLoading.value = false}
}// 滚动加载
const handleScroll = debounce((e: any) => {const { scrollHeight, scrollTop, clientHeight } = e.detailif (scrollHeight - scrollTop - clientHeight < 50) {loadProducts()}
}, 100)// 图片加载优化
const handleImageLoad = (e: any) => {// 图片加载完成后的处理逻辑
}// 工具函数
const formatPrice = (price: number) => {return price.toFixed(2)
}const formatNumber = (num: number) => {return num >= 10000 ? (num / 10000).toFixed(1) + '万': num.toString()
}const calculateDaysDiff = (date1: Date, date2: Date) => {return Math.floor((date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24))
}// 生命周期
onMounted(() => {loadProducts()
})// 监听排序变化
watch([currentSort, sortOrder], () => {// 保存用户排序偏好preferenceStore.saveSortPreference({type: currentSort.value,order: sortOrder.value})
})
</script><style lang="scss">
.product-list {height: 100%;background: #f5f5f5;.sort-toolbar {display: flex;align-items: center;height: 88rpx;background: #fff;padding: 0 20rpx;position: sticky;top: 0;z-index: 100;.sort-item {flex: 1;display: flex;align-items: center;justify-content: center;font-size: 28rpx;color: #333;position: relative;&.active {color: var(--primary-color);font-weight: 500;}.sort-icon {display: flex;flex-direction: column;margin-left: 4rpx;.iconfont {font-size: 20rpx;color: #999;line-height: 1;&.active {color: var(--primary-color);}}}}}.product-container {height: calc(100% - 88rpx);padding: 20rpx;.product-item {background: #fff;border-radius: 12rpx;margin-bottom: 20rpx;overflow: hidden;.product-image {position: relative;width: 100%;.product-tags {position: absolute;top: 12rpx;left: 12rpx;display: flex;flex-wrap: wrap;gap: 8rpx;.tag {padding: 4rpx 12rpx;font-size: 20rpx;color: #fff;border-radius: 4rpx;&.hot {background: #ff4d4f;}&.new {background: #52c41a;}&.promotion {background: #1890ff;}}}}.product-info {padding: 20rpx;.product-name {font-size: 28rpx;color: #333;font-weight: 500;line-height: 1.4;margin-bottom: 8rpx;}.product-desc {font-size: 24rpx;color: #666;line-height: 1.4;margin-bottom: 16rpx;}.product-meta {display: flex;align-items: center;justify-content: space-between;margin-bottom: 12rpx;.price {color: #ff4d4f;.currency {font-size: 24rpx;}.amount {font-size: 32rpx;font-weight: 500;}}.sales {font-size: 24rpx;color: #999;.count {margin-right: 4rpx;}}}.product-rating {display: flex;align-items: center;.rating-count {font-size: 24rpx;color: #999;margin-left: 8rpx;}}}}}.loading-status {display: flex;align-items: center;justify-content: center;height: 80rpx;color: #999;font-size: 24rpx;.loading-spinner {margin-right: 8rpx;}}
}// 深色模式适配
@media (prefers-color-scheme: dark) {.product-list {background: #1a1a1a;.sort-toolbar {background: #2c2c2c;.sort-item {color: #fff;}}.product-container {.product-item {background: #2c2c2c;.product-info {.product-name {color: #fff;}.product-desc {color: rgba(255, 255, 255, 0.65);}}}}}
}
</style>

2. 状态管理

// stores/product.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Product, ProductQueryParams } from '@/types'export const useProductStore = defineStore('product', () => {// 状态定义const products = ref<Product[]>([])const total = ref(0)// 获取商品列表const fetchProducts = async (params: ProductQueryParams) => {try {const response = await uni.request({url: '/api/products',method: 'GET',data: params})const { items, total: totalCount } = response.dataif (params.page === 1) {products.value = items} else {products.value.push(...items)}total.value = totalCountreturn {items,total: totalCount}} catch (error) {console.error('Failed to fetch products:', error)throw error}}// 更新商品数据const updateProduct = (productId: string, data: Partial<Product>) => {const index = products.value.findIndex(p => p.id === productId)if (index > -1) {products.value[index] = { ...products.value[index], ...data }}}return {products,total,fetchProducts,updateProduct}
})

3. 自定义图片懒加载指令

// directives/lazy-image.ts
import { DirectiveBinding } from 'vue'interface LazyImageState {loaded: booleanerror: booleanloading: booleanattempt: number
}export const lazyImage = {mounted(el: HTMLImageElement, binding: DirectiveBinding) {const state: LazyImageState = {loaded: false,error: false,loading: false,attempt: 0}const observer = new IntersectionObserver(entries => {entries.forEach(entry => {if (entry.isIntersecting) {loadImage(el, binding.value, state)observer.unobserve(el)}})}, {rootMargin: '50px'})observer.observe(el)}
}function loadImage(el: HTMLImageElement, src: string, state: LazyImageState) {if (state.loading || state.loaded || state.error) returnstate.loading = trueconst img = new Image()img.src = srcimg.onload = () => {el.src = srcstate.loading = falsestate.loaded = true}img.onerror = () => {state.loading = falsestate.error = truestate.attempt++if (state.attempt <= 3) {setTimeout(() => {loadImage(el, src, state)}, 1000 * state.attempt)}}
}

HarmonyOS平台优化

1. 性能优化

  1. 列表渲染优化

    • 使用虚拟列表
    • 图片懒加载
    • 数据分页
  2. 动画性能

    • 使用transform代替位置属性
    • 开启硬件加速
    • 避免重排重绘
  3. 内存管理

    • 及时释放不需要的资源
    • 控制图片缓存大小
    • 优化大列表数据结构

2. 交互优化

  1. 手势操作

    • 支持下拉刷新
    • 流畅的滚动体验
    • 顺滑的动画效果
  2. 视觉反馈

    • 加载状态提示
    • 错误处理展示
    • 操作结果反馈

最佳实践建议

  1. 数据处理

    • 合理的数据结构设计
    • 高效的排序算法
    • 本地数据缓存策略
  2. 用户体验

    • 智能的排序推荐
    • 流畅的滚动体验
    • 清晰的视觉反馈
  3. 代码质量

    • TypeScript类型约束
    • 组件化开发
    • 统一的错误处理

总结

通过本文的实践,我们实现了一个功能完备、性能优异的商品展示系统。该方案具有以下特点:

  • 智能的排序算法
  • 高效的性能表现
  • 流畅的用户体验
  • 完善的平台适配
  • 可扩展的架构设计

希望本文的内容能够帮助开发者更好地实现商品展示相关功能,同时为HarmonyOS平台的应用开发提供参考。

参考资源

  • UniApp官方文档
  • HarmonyOS设计规范
  • 前端性能优化指南
  • 移动端交互设计指南

相关文章:

  • grep/awk/sed笔记
  • 黑马k8s(十七)
  • 开发词云的Python程序
  • Linux入门(十一)进程管理
  • 【Day40】
  • 2024PLM系统实施案例:天水天轲零部件
  • 2025030给荣品PRO-RK3566开发板单独升级Android13的boot.img
  • vue+threeJs 绘制3D圆形
  • SpringBoot关于文件上传超出大小限制--设置了全局异常但是没有正常捕获的情况+捕获后没有正常响应返给前端
  • Pytorch Geometric官方例程pytorch_geometric/examples/link_pred.py环境安装教程及图数据集制作
  • OramaCore 是您 AI 项目、答案引擎、副驾驶和搜索所需的 AI 运行时。它包括一个成熟的全文搜索引擎、矢量数据库、LLM界面和更多实用程序
  • K8s工作流程与YAML实用指南
  • 编程之巅:语言的较量
  • 清华大学发Nature!光学工程+神经网络创新结合
  • vue2 + webpack 老项目升级 node v22 + vite + vue2 实战全记录
  • BaseTypeHandler用法-笔记
  • 【Unity】模型渐变技术 BlendShapes变形
  • UE5蓝图暴露变量,类似Unity中public一个变量,在游戏运行时修改变量实时变化和看向目标跟随目标Find Look at Rotation
  • 当 Redis 作为缓存使用时,如何保证缓存数据与数据库(或其他服务的数据源)之间的一致性?
  • BKP(备份寄存器)和 RTC(实时时钟)
  • 绍兴做网站的/最近一周新闻大事件
  • wordpress页面百度不收录/seo博客大全
  • c2c模式流程图/aso优化平台
  • 厦门软件园网站建设/网络安全培训机构哪家好
  • 省建设厅执业资格注册中心网站/地推团队联系方式
  • 微信生活门户网站源码/新东方小吃培训价格表