HarmonyOS ArkTS 实战:实现一个校园志愿者招募与排班应用

项目效果

本文使用 HarmonyOS 和 ArkTS 实现一个校园志愿者招募与排班应用。

应用可以发布志愿活动,在线报名,自动排班,签到签退,时长统计,并提供岗位选择、排班日历、时长证明、评价反馈等完整功能。

项目使用 DevEco Studio 开发,适配 API 23 及以上版本。

运行效果

校园志愿者招募与排班应用

功能介绍

  • 志愿活动发布
  • 在线报名参加
  • 岗位选择
  • 自动排班分配
  • 排班日历查看
  • 签到签退
  • 服务时长统计
  • 时长证明下载
  • 志愿者评价
  • 星级志愿者评定
  • 消息通知
  • 志愿团队管理

定义数据结构

interface VolunteerActivity {
  id: number;
  title: string;
  date: string;
  location: string;
  positions: { name: string; need: number; joined: number }[];
  totalNeed: number;
  totalJoined: number;
  status: string;
  cover: string;
}

interface Schedule {
  id: number;
  activityId: number;
  activityTitle: string;
  date: string;
  timeSlot: string;
  position: string;
  location: string;
  status: string;
}

初始化页面状态

@State private tabIndex: number = 0;
@State private totalHours: number = 156;
@State private starLevel: number = 5;
@State private activityNum: number = 32;

@State private activities: VolunteerActivity[] = [
  { id: 1, title: '进博会志愿服务', date: '2026-11-05', location: '国家会展中心', positions: [{ name: '引导岗', need: 20, joined: 18 }, { name: '翻译岗', need: 10, joined: 7 }], totalNeed: 30, totalJoined: 25, status: '招募中', cover: '' },
  { id: 2, title: '新生迎新志愿', date: '2026-09-01', location: '校门口', positions: [{ name: '引导岗', need: 30, joined: 30 }, { name: '搬运行李', need: 20, joined: 20 }], totalNeed: 50, totalJoined: 50, status: '已截止', cover: '' },
];

@State private schedules: Schedule[] = [
  { id: 1, activityId: 2, activityTitle: '新生迎新志愿', date: '2026-09-01', timeSlot: '08:00-12:00', position: '引导岗', location: '校门口', status: '已完成' },
];

@State private nextId: number = 10;

报名活动

private signUp(activityId: number, positionName: string): void {
  this.activities = this.activities.map(a => {
    if (a.id !== activityId) return a;
    return {
      ...a,
      totalJoined: a.totalJoined + 1,
      positions: a.positions.map(p => p.name === positionName ? { ...p, joined: p.joined + 1 } : p)
    };
  });
}

排班

private assignSchedule(activityId: number, timeSlot: string, position: string): void {
  const activity = this.activities.find(a => a.id === activityId);
  if (!activity) return;
  
  const schedule: Schedule = {
    id: this.nextId, activityId, activityTitle: activity.title,
    date: activity.date, timeSlot, position, location: activity.location, status: '待服务'
  };
  this.schedules = [schedule, ...this.schedules];
  this.nextId += 1;
}

签到完成服务

private finishService(scheduleId: number): void {
  this.schedules = this.schedules.map(s => 
    s.id === scheduleId ? { ...s, status: '已完成' } : s
  );
  this.totalHours += 4;
  this.activityNum += 1;
}

活动卡片组件

@Builder
ActivityCard(activity: VolunteerActivity) {
  Column({ space: 8 }) {
    Text(activity.title)
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .fontColor('#1E293B')
      .width('100%')
    
    Text(`📅 ${activity.date} | 📍 ${activity.location}`)
      .fontSize(12)
      .fontColor('#64748B')
      .width('100%')
    
    Row() {
      Text(`已报名 ${activity.totalJoined}/${activity.totalNeed}`)
        .fontSize(12)
        .fontColor(activity.totalJoined >= activity.totalNeed ? '#EF4444' : '#10B981')
      Blank()
      Text(activity.status)
        .fontSize(12)
        .fontColor(activity.status === '招募中' ? '#2563EB' : '#94A3B8')
    }
    .width('100%')
    .margin({ top: 4 })
    
    if (activity.status === '招募中') {
      Button('立即报名')
        .width('100%')
        .height(36)
        .fontSize(14)
        .backgroundColor('#1E293B')
        .margin({ top: 8 })
        .onClick(() => this.signUp(activity.id, '引导岗'))
    }
  }
  .width('100%')
  .padding(16)
  .backgroundColor('#F8FAFC')
  .borderRadius(12)
}

页面布局

build() {
  Column() {
    // 顶部个人统计
    Row({ space: 12 }) {
      Column() {
        Text(`${this.totalHours}`)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
          .fontColor('#1E293B')
        Text('服务时长(h)')
          .fontSize(12)
          .fontColor('#64748B')
      }
      .layoutWeight(1)
      .padding(20)
      .backgroundColor('#F1F5F9')
      .borderRadius(12)
      
      Column() {
        Text(`${'⭐'.repeat(this.starLevel)}`)
          .fontSize(20)
        Text(`${this.starLevel}星志愿者`)
          .fontSize(12)
          .fontColor('#64748B')
      }
      .layoutWeight(1)
      .padding(20)
      .backgroundColor('#F1F5F9')
      .borderRadius(12)
    }
    .width('100%')
    .padding(20)
    
    // Tab
    Row({ space: 20 }) {
      Text('活动招募')
        .fontSize(16)
        .fontWeight(this.tabIndex === 0 ? FontWeight.Bold : FontWeight.Normal)
        .fontColor(this.tabIndex === 0 ? '#1E293B' : '#94A3B8')
        .onClick(() => this.tabIndex = 0)
      Text('我的排班')
        .fontSize(16)
        .fontWeight(this.tabIndex === 1 ? FontWeight.Bold : FontWeight.Normal)
        .fontColor(this.tabIndex === 1 ? '#1E293B' : '#94A3B8')
        .onClick(() => this.tabIndex = 1)
    }
    .width('100%')
    .padding({ left: 20, right: 20 })
    
    if (this.tabIndex === 0) {
      List({ space: 12 }) {
        ForEach(this.activities, (a: VolunteerActivity) => {
          ListItem() { this.ActivityCard(a) }
        })
      }
      .width('100%')
      .padding(20)
      .layoutWeight(1)
    } else {
      List({ space: 12 }) {
        ForEach(this.schedules, (s: Schedule) => {
          ListItem() {
            Row() {
              Column({ space: 4 }) {
                Text(s.activityTitle)
                  .fontSize(14)
                  .fontWeight(FontWeight.Medium)
                Text(`${s.date} ${s.timeSlot} | ${s.position}`)
                  .fontSize(12)
                  .fontColor('#64748B')
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)
              
              if (s.status === '待服务') {
                Button('签到')
                  .fontSize(12)
                  .height(32)
                  .backgroundColor('#10B981')
                  .onClick(() => this.finishService(s.id))
              } else {
                Text('已完成')
                  .fontSize(12)
                  .fontColor('#10B981')
              }
            }
            .width('100%')
            .padding(16)
            .backgroundColor('#F8FAFC')
            .borderRadius(12)
          }
        })
      }
      .width('100%')
      .padding(20)
      .layoutWeight(1)
    }
  }
  .width('100%')
  .height('100%')
  .backgroundColor(Color.White)
}

页面设计说明

主题色采用slate-800#1E293B,体现志愿服务的专业、奉献。顶部展示服务时长和星级,Tab切换活动和排班,卡片式设计清晰展示信息。

SDK配置

API 24,compatibleSdkVersion: “6.1.1(24)”

运行项目

将代码复制到 entry/src/main/ets/pages/Index.ets 即可运行。

项目总结

实现了活动招募、报名、排班、签到、时长统计、星级评定等功能。掌握了列表卡片、Tab切换、状态管理、数据统计等。后续可加入自动排班算法、日历视图、电子证书、评价系统、团队管理、培训考核等功能。

Logo

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

更多推荐