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. 解释

  • ListListItem:用于展示商品列表,每个商品项是一个 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. 图片放大功能

  • StackBlank:使用 StackBlank 组件实现弹窗效果,Blank 组件用于半透明背景。
  • ImageFit.Contain:使图片按比例缩放,完整显示在弹窗中。

4. 视频播放功能

  • Video组件:使用 Video 组件播放商品视频,设置 controllertrue,显示播放控制器。
  • 点击关闭弹窗:点击视频区域,关闭弹窗。

五、商品搜索和筛选功能(可选)

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

1 回复

更多关于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

回到顶部