HarmonyOS 鸿蒙Next 商品列表与详情展示——玩具店购物应用开发实战(三)
HarmonyOS 鸿蒙Next 商品列表与详情展示——玩具店购物应用开发实战(三)
商品列表与详情展示——玩具店购物应用开发实战(三)
在前两篇文章中,我们完成了玩具店购物应用的项目规划、环境搭建,以及用户注册和登录功能的实现。本篇文章将详细介绍商品列表和商品详情展示的实现,包括从数据库或网络加载商品数据、商品的搜索和筛选等功能。
一、功能概述
1. 功能需求
商品列表展示:
- 从数据库或网络加载商品数据,展示所有玩具商品。
- 支持下拉刷新,实时更新商品信息。
- 支持分页加载,防止一次加载过多数据导致卡顿。
- 商品项包括商品图片、名称、价格等基本信息。
商品搜索和筛选(可选):
- 根据关键词搜索商品,支持模糊匹配。
- 根据类别、价格范围等条件筛选商品。
商品详情展示:
- 展示商品的详细信息,包括多张高清图片、商品描述、价格、库存等。
- 支持图片的放大查看。
- 支持播放商品的演示视频。
2. 技术难点
- 数据加载与展示:如何高效地从数据库或网络加载商品数据,并在界面上进行展示。
- 分页与刷新:如何实现分页加载和下拉刷新,提升用户体验。
- 多媒体展示:如何在商品详情页实现图片的放大和视频的播放。
二、商品数据准备
1. 商品数据的获取方式
- 本地数据:将商品数据存储在本地数据库中,适合演示和测试。
- 网络数据:从服务器获取商品数据,适合实际生产环境。
为了简化开发过程,我们将在本篇中使用本地数据库来存储和加载商品数据。
2. 在数据库中创建商品表
在 DatabaseHelper.ets
中,完善商品表的创建。
// common/utils/DatabaseHelper.ets
// 添加在onCreate方法中
db.executeSql(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
description TEXT,
price REAL,
stock INTEGER,
images TEXT, -- 存储JSON格式的图片URL数组
video_url TEXT
)
`);
3. 插入商品数据
由于我们使用的是本地数据库,需要在应用初始化时插入一些商品数据。
(1)创建商品数据插入方法
// common/utils/DatabaseHelper.ets
export class DatabaseHelper {
// 前面的代码
static async initializeProducts() {
const store = await this.getRdbStore();
// 检查是否已有商品数据
const resultSet = await store.query('SELECT COUNT(*) as count FROM products');
if (resultSet.goToFirstRow()) {
const count = resultSet.getInt(resultSet.getColumnIndex('count'));
resultSet.close();
if (count > 0) {
// 已有商品数据,不需要重复插入
return;
}
}
// 插入商品数据
const products = [
{
name: '积木玩具',
description: '高品质的积木玩具,培养孩子的动手能力和创造力。',
price: 99.0,
stock: 50,
images: JSON.stringify(['resources/images/toy1.jpg', 'resources/images/toy1_2.jpg']),
video_url: 'resources/videos/toy1.mp4',
},
{
name: '遥控赛车',
description: '高速遥控赛车,带给孩子无尽的乐趣。',
price: 199.0,
stock: 30,
images: JSON.stringify(['resources/images/toy2.jpg']),
video_url: 'resources/videos/toy2.mp4',
},
// 更多商品...
];
for (const product of products) {
await store.insert('products', product);
}
}
}
(2)在应用启动时初始化商品数据
在 app.ets
中,应用启动时调用 initializeProducts
方法。
// app.ets
import { DatabaseHelper } from './common/utils/DatabaseHelper';
@Entry
@Component
struct App {
build() {
// 初始化商品数据
DatabaseHelper.initializeProducts().then(() => {
// 检查登录状态
if (AppStorage.Get('isLoggedIn')) {
Router.replace({ uri: 'ProductListPage' });
} else {
Router.replace({ uri: 'LoginPage' });
}
});
}
}
三、商品列表页面开发
1. 页面布局
在 pages/ProductListPage.ets
中,创建商品列表页面的UI布局。
// pages/ProductListPage.ets
import { Product } from '../model/ProductModel';
import { DatabaseHelper } from '../common/utils/DatabaseHelper';
@Entry
@Component
struct ProductListPage {
@State products: Product[] = [];
@State isLoading: boolean = false;
build() {
Column() {
// 标题和搜索框(可选)
Text('玩具商店').fontSize(24).margin({ top: 16, bottom: 16 }).textAlign(TextAlign.Center);
// 搜索框
// ...
// 商品列表
if (this.isLoading) {
// 加载中
Progress().margin({ top: 50 });
} else {
List() {
ForEach(this.products, (product) => {
ListItem() {
Row({ alignItems: ItemAlign.Center }) {
Image(product.images[0])
.width(80)
.height(80)
.objectFit(ImageFit.Cover)
.margin(8);
Column() {
Text(product.name).fontSize(18);
Text(`价格:¥${product.price}`).fontSize(16).fontColor(Color.Red);
}
Spacer();
Button('查看')
.onClick(() => this.viewProductDetail(product))
.margin(8);
}
}
.backgroundColor(Color.White);
});
}
.listDirection(Axis.Vertical)
.edgeEffect(EdgeEffect.None)
.refresh({
refreshing: false,
onRefresh: () => this.refreshProducts(),
});
}
}
.padding(16)
.backgroundColor(Color.Background);
}
aboutToAppear() {
this.loadProducts();
}
async loadProducts() {
this.isLoading = true;
const store = await DatabaseHelper.getRdbStore();
const resultSet = await store.query('SELECT * FROM products');
const products: Product[] = [];
while (resultSet.goToNextRow()) {
const product = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
description: resultSet.getString(resultSet.getColumnIndex('description')),
price: resultSet.getDouble(resultSet.getColumnIndex('price')),
stock: resultSet.getLong(resultSet.getColumnIndex('stock')),
images: JSON.parse(resultSet.getString(resultSet.getColumnIndex('images'))),
video_url: resultSet.getString(resultSet.getColumnIndex('video_url')),
};
products.push(product);
}
resultSet.close();
this.products = products;
this.isLoading = false;
}
async refreshProducts() {
await this.loadProducts();
// 模拟刷新完成
setTimeout(() => {
prompt.showToast({ message: '刷新完成' });
}, 500);
}
viewProductDetail(product: Product) {
Router.push({
uri: 'ProductDetailPage',
params: {
productId: product.id,
},
});
}
}
2. 解释
List
和ListItem
:用于展示商品列表,每个商品项是一个ListItem
。Image
:展示商品的缩略图。Text
:展示商品名称和价格。Button
:点击“查看”按钮,跳转到商品详情页。loadProducts()
:从数据库加载商品数据,存储到products
状态中。refreshProducts()
:实现下拉刷新功能,重新加载商品数据。
3. 添加搜索功能(可选)
在商品列表页添加搜索框,支持根据关键词搜索商品。
// pages/ProductListPage.ets
@Entry
@Component
struct ProductListPage {
@State products: Product[] = [];
@State isLoading: boolean = false;
@State searchText: string = '';
build() {
Column() {
Text('玩具商店').fontSize(24).margin({ top: 16 }).textAlign(TextAlign.Center);
// 搜索框
TextField({ placeholder: '搜索商品', text: this.searchText })
.onChange((value) => {
this.searchText = value;
this.searchProducts();
})
.margin({ top: 16, bottom: 16 });
// 商品列表
// 与之前相同
}
.padding(16)
.backgroundColor(Color.Background);
}
// 前面的代码
async searchProducts() {
this.isLoading = true;
const store = await DatabaseHelper.getRdbStore();
let query = 'SELECT * FROM products';
let params = [];
if (this.searchText) {
query += ' WHERE name LIKE ?';
params.push(`%${this.searchText}%`);
}
const resultSet = await store.query(query, params);
// 与loadProducts()方法中处理resultSet的代码相同
// ...
}
}
四、商品详情页面开发
1. 页面布局
在 pages/ProductDetailPage.ets
中,创建商品详情页面的UI布局。
// pages/ProductDetailPage.ets
import { Product } from '../model/ProductModel';
import { DatabaseHelper } from '../common/utils/DatabaseHelper';
@Entry
@Component
struct ProductDetailPage {
@Prop productId: number;
@State product: Product | null = null;
@State selectedImage: string = '';
@State isImageModalVisible: boolean = false;
@State isVideoModalVisible: boolean = false;
build() {
if (!this.product) {
Progress().margin({ top: 50 });
return;
}
Scroll() {
Column() {
// 商品图片轮播
if (this.product.images.length > 0) {
Swiper({ index: 0, autoPlay: false }) {
ForEach(this.product.images, (image) => {
Image(image)
.width('100%')
.height(300)
.objectFit(ImageFit.Cover)
.onClick(() => this.showImageModal(image));
});
}
.margin({ bottom: 16 });
}
// 商品名称和价格
Text(this.product.name).fontSize(24).margin({ top: 16 });
Text(`价格:¥${this.product.price}`).fontSize(20).fontColor(Color.Red).margin({ top: 8 });
// 商品库存
Text(`库存:${this.product.stock}`).fontSize(16).margin({ top: 8 });
// 商品描述
Text('商品描述').fontSize(18).margin({ top: 16 });
Text(this.product.description).margin({ top: 8 });
// 视频演示
if (this.product.video_url) {
Button('播放视频')
.onClick(() => this.showVideoModal())
.margin({ top: 16 });
}
// 添加到购物车
Button('加入购物车')
.onClick(() => this.addToCart())
.margin({ top: 16, bottom: 32 });
}
}
.padding(16);
}
// 图片放大弹窗
if (this.isImageModalVisible) {
this.imageModal();
}
// 视频播放弹窗
if (this.isVideoModalVisible) {
this.videoModal();
}
}
async loadProductDetail() {
const store = await DatabaseHelper.getRdbStore();
const query = 'SELECT * FROM products WHERE id = ?';
const resultSet = await store.query(query, [this.productId]);
if (resultSet.goToFirstRow()) {
const product = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
description: resultSet.getString(resultSet.getColumnIndex('description')),
price: resultSet.getDouble(resultSet.getColumnIndex('price')),
stock: resultSet.getLong(resultSet.getColumnIndex('stock')),
images: JSON.parse(resultSet.getString(resultSet.getColumnIndex('images'))),
video_url: resultSet.getString(resultSet.getColumnIndex('video_url')),
};
this.product = product;
}
resultSet.close();
}
showImageModal(image: string) {
this.selectedImage = image;
this.isImageModalVisible = true;
}
imageModal() {
Stack() {
Blank().backgroundColor(Color.Black).opacity(0.8);
Image(this.selectedImage)
.width('100%')
.height('100%')
.objectFit(ImageFit.Contain)
.onClick(() => this.isImageModalVisible = false);
}
.position({ type: PositionType.Absolute }).width('100%').height('100%');
}
showVideoModal() {
this.isVideoModalVisible = true;
}
videoModal() {
Stack() {
Blank().backgroundColor(Color.Black).opacity(0.8);
Video({ src: this.product.video_url, controller: true })
.width('100%')
.height('100%')
.onClick(() => this.isVideoModalVisible = false);
}
.position({ type: PositionType.Absolute }).width('100%').height('100%');
}
addToCart() {
// 将商品添加到购物车
}
2. 解释
Swiper
:用于展示商品的多张图片,支持左右滑动切换。Image点击事件
:点击图片,调用showImageModal()
,显示放大图片。Video组件
:用于播放商品的演示视频。addToCart()
:点击“加入购物车”按钮,将商品添加到购物车(将在下一篇中详细实现)。
3. 图片放大功能
Stack
和Blank
:使用Stack
和Blank
组件实现弹窗效果,Blank
组件用于半透明背景。ImageFit.Contain
:使图片按比例缩放,完整显示在弹窗中。
4. 视频播放功能
Video组件
:使用Video
组件播放商品视频,设置controller
为true
,显示播放控制器。点击关闭弹窗
:点击视频区域,关闭弹窗。
五、商品搜索和筛选功能(可选)
1. 搜索功能
已在商品列表页的代码中添加搜索框,通过 LIKE
关键字进行模糊查询。
2. 筛选功能
可以在商品列表页添加筛选条件,例如根据价格范围、商品类别等进行筛选。
// 添加筛选条件状态
@State selectedCategory: string = '';
@State priceRange: [number, number] = [0, 500];
// 在UI中添加筛选组件
// ...
// 修改searchProducts()方法,根据筛选条件查询
async searchProducts() {
this.isLoading = true;
const store = await DatabaseHelper.getRdbStore();
let query = 'SELECT * FROM products WHERE 1=1';
let params = [];
if (this.searchText) {
query += ' AND name LIKE ?';
params.push(`%${this.searchText}%`);
}
if (this.selectedCategory) {
query += ' AND category = ?';
params.push(this.selectedCategory);
}
query += ' AND price BETWEEN ? AND ?';
params.push(this.priceRange[0], this.priceRange[1]);
// 查询数据库,处理结果集
// ...
}
六、完善用户体验
1. 界面美化
- 布局优化:合理使用间距、对齐方式,使界面更加整洁美观。
- 颜色搭配:使用协调的配色方案,提高视觉效果。
- 动画效果:在图片切换、页面跳转等操作中添加过渡动画,提升用户体验。
2. 错误处理
- 异常捕获:在数据库操作和多媒体加载中,添加异常处理,防止应用崩溃。
- 友好提示:在数据加载失败、网络异常等情况下,给出友好的提示信息。
3. 性能优化
- 懒加载:对于大图片或视频,考虑使用懒加载技术,避免一次性加载过多资源。
- 分页加载:实现商品列表的分页加载,防止一次性加载过多商品导致卡顿。
七、测试与调试
1. 测试用例
商品列表展示:
- 应用启动后,能正常加载并显示商品列表。
- 下拉刷新后,商品列表能正确更新。
- 搜索框输入关键词后,能正确过滤商品。
商品详情展示:
- 点击“查看”按钮,能跳转到商品详情页,并正确显示商品信息。
- 点击商品图片,能放大查看,并能正常关闭。
- 点击“播放视频”按钮,能正常播放商品视频。
2. 调试方法
- 日志输出:使用
console.log()
输出关键数据,检查数据是否正确加载和传递。 - 断点调试:在 DevEco Studio 中设置断点,逐步检查代码执行流程。
- 查看数据库:使用数据库浏览工具,查看商品数据是否正确插入和读取。
目前我们实现了玩具店购物应用的商品列表和商品详情展示功能。通过从本地数据库加载商品数据,并在界面上进行展示,实现了商品的浏览和查看。我们还添加了商品的搜索功能,以及商品详情页的多媒体展示。在下一篇文章中,我们将实现多媒体功能与购物车的实现,包括将商品添加到购物车、购物车页面的开发、商品的数量修改等。
更多关于HarmonyOS 鸿蒙Next 商品列表与详情展示——玩具店购物应用开发实战(三)的实战教程也可以访问 https://www.itying.com/category-93-b0.html
更多关于HarmonyOS 鸿蒙Next 商品列表与详情展示——玩具店购物应用开发实战(三)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next系统中开发玩具店购物应用时,实现商品列表与详情展示功能,主要涉及UI布局和数据绑定。
首先,你需要利用ArkUI框架进行界面设计。ArkUI提供了丰富的组件库,如List组件可用于展示商品列表,而Swiper等组件则可用于实现商品详情页面的滑动查看。
在数据展示方面,你可以通过绑定数据模型到UI组件上,实现数据的动态更新。例如,将商品数据列表绑定到List组件,当用户滚动列表时,组件会自动根据数据更新显示内容。
对于商品详情页,你可以设计包含商品图片、名称、价格、描述等信息的布局,并通过点击列表项触发页面跳转,将选中的商品数据传递给详情页进行展示。
此外,还需注意应用的性能优化,如合理管理内存、避免界面卡顿等,以确保用户获得流畅的购物体验。
如果在实现商品列表与详情展示功能过程中遇到问题,如布局错乱、数据绑定失败等,可检查组件属性设置、数据模型定义及传递是否正确。若问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html