项目背景

每年毕业季,校园里都会掀起一股“闲置流转”热潮:即将离校的学长学姐们有大量教材、电子产品、生活用品需要处理,而学弟学妹们则希望能以实惠的价格淘到好东西。然而,传统的方式——贴吧发帖、微信群刷屏、打印传单张贴——不仅效率低下,还存在信息杂乱、交易无保障等问题。

本文就带大家使用 HarmonyOS 和 ArkTS 框架,从零搭建一个「毕业季跳蚤市场」应用。该应用涵盖商品发布、分类浏览、在线沟通、下单交易等核心功能,同时提供分类筛选、搜索、收藏、我的发布、求购专区、毕业专区等完整模块。项目使用 DevEco Studio 开发,适配 API 23 及以上版本。

读完本文,你将掌握 Grid 网格布局、组件状态管理、状态流转控制、卡片阴影等实用技巧,并能在此基础上扩展出完整的校园二手交易平台。

项目效果

启动应用后,首页以清新的深青色调呈现,顶部展示毕业季主题标语,下方是横向滚动的分类标签和双列商品网格。每张商品卡片包含缩略图区域、毕业清仓标签、价格信息和收藏按钮,点击可直接下单购买。

运行效果

毕业季跳蚤市场应用

应用整体风格简洁明快,商品信息一目了然,操作路径短、反馈及时,非常适合校园场景下的快速交易。

功能介绍

本应用围绕二手交易的核心流程,设计了以下功能模块:

  • 商品发布:用户可发布闲置物品,填写标题、价格、分类、成色、描述等信息。
  • 分类浏览:按教材书籍、电子数码、生活用品、服饰鞋包、运动器材等类别筛选商品。
  • 商品搜索:通过关键词快速定位目标商品。
  • 价格筛选:按照价格区间过滤,方便比价。
  • 商品收藏:收藏感兴趣的商品,便于后续集中查看和对比。
  • 在线沟通:买卖双方可通过内置聊天功能沟通细节(后续扩展)。
  • 下单购买:一键下单,商品状态自动从“在售”变更为“已预定”。
  • 我的发布:查看自己发布的商品列表,管理在售/已售状态。
  • 我的订单:查看自己作为买家或卖家的订单记录。
  • 求购专区:发布求购信息,反向匹配闲置商品。
  • 毕业专区:筛选带有“毕业清仓”标签的商品,集中浏览。
  • 当面交易:交易确认后面对面交付,无需物流。

定义数据结构

良好的数据结构是应用稳定运行的基石。我们首先定义 GoodsOrder 两个核心接口:

interface Goods {
  id: number;
  title: string;
  price: number;
  originalPrice: number;
  category: string;
  condition: string; // 全新/几乎全新/轻微使用/有使用痕迹
  seller: string;
  location: string;
  images: string[];
  description: string;
  status: string; // 在售/已预定/已售出
  publishTime: string;
  isGraduation: boolean;
}

interface Order {
  id: number;
  goodsId: number;
  goodsTitle: string;
  price: number;
  buyer: string;
  seller: string;
  status: string; // 待见面/已完成/已取消
  createTime: string;
}

Goods 接口涵盖了商品的完整信息,其中 conditionisGraduation 字段分别用于标识新旧程度和是否为毕业清仓商品,status 字段贯穿整个交易流程的三种状态。Order 接口则记录了每次交易的买卖双方、金额、状态和时间。

初始化页面状态

利用 ArkTS 的 @State 装饰器,我们可以轻松管理页面的响应式数据:

@State private tabIndex: number = 0;
@State private currentCategory: string = '全部';
@State private searchKeyword: string = '';

@State private categories: string[] = ['全部', '教材书籍', '电子数码', '生活用品', '服饰鞋包', '运动器材', '其他'];

@State private goods: Goods[] = [
  { id: 1, title: '九成新高等数学教材', price: 10, originalPrice: 45, category: '教材书籍', condition: '几乎全新', seller: '张同学', location: '1号楼', images: [], description: '高数第七版上册,几乎全新,有少量笔记', status: '在售', publishTime: '2026-07-20', isGraduation: true },
  { id: 2, title: '自用iPad 2021 64G', price: 1800, originalPrice: 2999, category: '电子数码', condition: '轻微使用', seller: '李同学', location: '3号楼', images: [], description: '毕业出,无拆无修,带充电器', status: '在售', publishTime: '2026-07-21', isGraduation: true },
  { id: 3, title: '床上小桌子', price: 15, originalPrice: 39, category: '生活用品', condition: '有使用痕迹', seller: '王同学', location: '5号楼', images: [], description: '毕业清仓,自提', status: '在售', publishTime: '2026-07-22', isGraduation: true },
  { id: 4, title: '羽毛球拍尤尼克斯', price: 80, originalPrice: 200, category: '运动器材', condition: '轻微使用', seller: '赵同学', location: '体育馆', images: [], description: '入门级球拍,送球', status: '已预定', publishTime: '2026-07-19', isGraduation: false },
];

@State private orders: Order[] = [];
@State private favorites: number[] = [];
@State private nextId: number = 10;

这里初始化了分类列表、示例商品数据、订单数组、收藏数组以及自增 ID 计数器。示例数据涵盖了教材、数码、生活用品和运动器材等典型品类,既有毕业清仓商品也有普通闲置,方便演示不同状态的展示效果。

收藏商品

收藏功能使用数组存储已收藏的商品 ID,点击爱心图标时切换收藏状态。利用展开运算符创建新数组引用,触发 UI 响应式更新:

private toggleFavorite(goodsId: number): void {
  if (this.favorites.includes(goodsId)) {
    this.favorites = this.favorites.filter(id => id !== goodsId);
  } else {
    this.favorites = [...this.favorites, goodsId];
  }
}

购买商品

下单逻辑包含以下几个步骤:校验商品是否存在且为“在售”状态、创建订单、更新商品状态为“已预定”、递增订单 ID。整个过程通过不可变数据更新确保 UI 正确刷新:

private buyGoods(goodsId: number): void {
  const goods = this.goods.find(g => g.id === goodsId);
  if (!goods || goods.status !== '在售') return;
  
  const order: Order = {
    id: this.nextId, goodsId, goodsTitle: goods.title, price: goods.price,
    buyer: '我', seller: goods.seller, status: '待见面',
    createTime: new Date().toLocaleDateString()
  };
  this.orders = [order, ...this.orders];
  this.goods = this.goods.map(g => g.id === goodsId ? { ...g, status: '已预定' } : g);
  this.nextId += 1;
}

确认交易

当买卖双方面对面完成交付后,调用 confirmDeal 将订单状态更新为“已完成”,同时将对应商品标记为“已售出”:

private confirmDeal(orderId: number): void {
  const order = this.orders.find(o => o.id === orderId);
  if (!order) return;
  
  this.orders = this.orders.map(o => o.id === orderId ? { ...o, status: '已完成' } : o);
  this.goods = this.goods.map(g => g.id === order.goodsId ? { ...g, status: '已售出' } : g);
}

至此,商品完整的状态流转路径为:在售 → 已预定 → 已售出,每一步对应的订单状态也同步变化,逻辑清晰、易于维护。

商品卡片组件

GoodsCard 是使用 @Builder 装饰器定义的自定义构建函数,负责渲染单张商品卡片。卡片采用白色圆角容器,顶部区域展示默认背景和状态标签,下方展示标题、价格和收藏按钮:

@Builder
GoodsCard(goods: Goods) {
  Column({ space: 8 }) {
    Stack() {
      Column()
        .width('100%')
        .height(120)
        .backgroundColor('#F0FDFA')
        .borderRadius({ topLeft: 12, topRight: 12 })
      
      if (goods.isGraduation) {
        Text('毕业清仓')
          .fontSize(10)
          .fontColor(Color.White)
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .backgroundColor('#134E4A')
          .borderRadius(8)
          .position({ x: 8, y: 8 })
      }
      
      Text(goods.status === '在售' ? '' : goods.status)
        .fontSize(11)
        .fontColor(Color.White)
        .padding({ left: 8, right: 8, top: 3, bottom: 3 })
        .backgroundColor(goods.status === '已售出' ? '#94A3B8' : '#F59E0B')
        .borderRadius(10)
        .position({ x: '70%', y: 8 })
    }
    .width('100%')
    
    Text(goods.title)
      .fontSize(13)
      .fontColor('#134E4A')
      .width('100%')
      .maxLines(2)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .padding({ left: 8, right: 8 })
    
    Row() {
      Text(`¥${goods.price}`)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#0F766E')
      Text(`¥${goods.originalPrice}`)
        .fontSize(11)
        .fontColor('#94A3B8')
        .decoration({ type: TextDecorationType.LineThrough })
      Blank()
      Text('❤️')
        .fontSize(16)
        .fontColor(this.favorites.includes(goods.id) ? '#EF4444' : '#CBD5E1')
        .onClick(() => this.toggleFavorite(goods.id))
    }
    .width('100%')
    .padding({ left: 8, right: 8, bottom: 8 })
  }
  .width('100%')
  .backgroundColor(Color.White)
  .borderRadius(12)
  .shadow({ radius: 4, color: '#134E4A10', offsetY: 2 })
  .onClick(() => {
    if (goods.status === '在售') this.buyGoods(goods.id);
  })
}

卡片设计注重信息层级:毕业清仓标签固定在左上角,状态标签固定在右上角,标题支持最多两行显示并自动省略,原价使用删除线效果形成价格对比,收藏按钮根据状态切换红色/灰色。点击卡片时,在售商品可直接触发购买。

页面布局

build() 方法将上述组件组装为完整页面,从上到下依次为:顶部标题、搜索框、横向分类导航、商品网格和底部发布按钮:

build() {
  Column() {
    // 顶部标题
    Text('🎓 毕业季跳蚤市场')
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .fontColor('#134E4A')
      .width('100%')
      .padding(20)
    
    // 搜索
    TextInput({ placeholder: '搜索闲置物品' })
      .width('90%')
      .height(40)
      .backgroundColor('#F0FDFA')
      .borderRadius(20)
      .margin({ bottom: 12 })
    
    // 分类
    Scroll(Axis.Horizontal) {
      Row({ space: 8 }) {
        ForEach(this.categories, (c: string) => {
          Text(c)
            .fontSize(13)
            .fontColor(this.currentCategory === c ? Color.White : '#134E4A')
            .padding({ left: 12, right: 12, top: 6, bottom: 6 })
            .backgroundColor(this.currentCategory === c ? '#134E4A' : '#CCFBF1')
            .borderRadius(16)
            .onClick(() => this.currentCategory = c)
        })
      }
    }
    .scrollBar(BarState.Off)
    .width('100%')
    .padding({ left: 20, right: 20 })
    
    // 商品网格
    Grid() {
      ForEach(this.goods.filter(g => 
        (this.currentCategory === '全部' || g.category === this.currentCategory) &&
        g.status !== '已售出'
      ), (g: Goods) => {
        GridItem() { this.GoodsCard(g) }
      })
    }
    .columnsTemplate('1fr 1fr')
    .columnsGap(12)
    .rowsGap(12)
    .width('100%')
    .padding(20)
    .layoutWeight(1)
    
    // 发布按钮
    Button('+ 发布闲置')
      .width('90%')
      .height(48)
      .fontSize(16)
      .backgroundColor('#134E4A')
      .borderRadius(24)
      .margin({ bottom: 20 })
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#F8FAFC')
}

页面使用 layoutWeight(1) 让商品网格区域自动填充剩余空间,分类标签通过 Scroll 组件支持横向滚动,适应不同数量的分类项。暗纹浅灰背景 #F8FAFC 衬托白色卡片,整体视觉效果清爽、层次分明。

页面设计说明

主题色采用 teal-900 #134E4A,深青色既传递了校园的清新与活力,又保持了足够的可读性。毕业清仓标签使用同色系小圆角徽章,与主题呼应;价格使用加粗深绿,原价添加删除线形成视觉对比;收藏爱心在收藏/未收藏之间切换红色和浅灰,交互反馈直观。两列网格配合 12px 间距和圆角阴影卡片,在保持信息密度的同时让页面呼吸感十足。

SDK 配置

本项目基于 API 24 开发,兼容版本配置如下:

compatibleSdkVersion: "6.1.1(24)"

如需适配低版本设备,可将 API 版本下调至 23,代码无需额外修改。

运行项目

将完整代码复制到 entry/src/main/ets/pages/Index.ets 文件中,在 DevEco Studio 中连接真机或启动模拟器,点击运行即可体验。首次运行时会展示预设的示例商品数据,你可以通过修改 goods 数组来添加自己的测试数据。

项目总结

本文从数据结构设计入手,逐步实现了商品浏览、分类筛选、收藏切换、下单购买、交易确认等二手交易核心功能。在实现过程中,我们掌握了以下技术要点:

  • Grid 网格布局:通过 columnsTemplate('1fr 1fr') 实现双列自适应网格。
  • 状态管理:利用 @State 装饰器配合不可变数据更新,确保 UI 响应及时。
  • 商品状态流转:设计“在售 → 已预定 → 已售出”的状态机,逻辑清晰可扩展。
  • 自定义组件:使用 @Builder 封装可复用的商品卡片,提升代码组织性。
  • 卡片阴影与圆角:营造轻量化的视觉层次。

后续可以考虑扩展的功能方向包括:接入实时聊天服务实现买卖双方在线沟通、集成图片上传让商品展示更直观、添加求购发布与智能匹配、引入地址选择与校内定位、完善评价体系建立信用机制等。掌握了本文的基础架构后,这些扩展都可以在现有代码上平滑叠加,推荐读者动手尝试,打造属于自己的校园交易平台!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐