HarmonyOS ArkTS 实战:从零实现校园评奖评优管理系统

目录


一、项目背景与效果预览

1.1 痛点场景

每年评奖评优季,纸质材料堆积、投票统计繁琐、进度不透明、证书发放延迟……学生和辅导员都备受困扰。本应用将 三好学生、优秀干部、奖学金 等评选全流程数字化,包括通知发布、在线申请、材料上传、投票、进度跟踪、结果公示、电子证书生成,提升评选效率和公正性。

1.2 运行效果(模拟器预览)

  • 主界面:顶部为滚动通知横幅(显示最新评选动态);中部为奖项列表(卡片式,含奖项名称、金额、截止日期,点击“申请”按钮);下方为候选人列表(当前活跃奖项的申请者,带投票按钮,投票时有“+1”动画)。
  • 底部Tabs:“首页”、“我的申请”、“结果公示”、“历年获奖”。
  • 我的申请:显示本人所有申请记录及状态(审核中、初选通过、公示中、已获奖),可查看进度条。
  • 结果公示:按奖项展示最终获奖名单(带等级颜色:金奖/银奖/铜奖)。
  • 历年获奖:展示往年获奖情况,含证书缩略图(可点击查看大图)。

主题色采用深靛蓝(#1E3A8A),象征公正、严谨、信任,搭配金色(#F59E0B)点缀突出荣誉感。


二、技术栈与开发环境

技术项 说明
开发语言 ArkTS
UI 框架 ArkUI 声明式开发
状态管理 @State / @Provide / @Consume
布局方式 List + Grid + Tabs + Stack
数据持久化 @ohos.data.preferences
路由管理 @ohos.router
图表组件 @ohos.arkui.advanced.Chart
弹窗/提示 @ohos.prompt / @ohos.dialog
动画 animateTo / transition
开发工具 DevEco Studio 5.0+
SDK 版本 API 24 及以上

三、需求分析与功能架构

3.1 核心功能清单

  1. 评选通知:顶部轮播/滚动通知,展示最新公告。
  2. 奖项浏览:卡片展示所有奖项(名称、奖金、要求、截止日期),支持申请。
  3. 在线申请:填写个人信息、绩点、申请理由,上传附件(模拟)。
  4. 材料上传:支持多文件(图片/文档)模拟选择,展示文件列表。
  5. 投票系统:对候选人投票,每人每奖项限投1票(防刷),投票动画。
  6. 状态流转:申请 → 审核中 → 初选通过 → 公示中 → 获奖/未获奖,进度条展示。
  7. 结果公示:按奖项分组,展示获奖者名单及等级。
  8. 获奖证书:动态生成证书卡片(含姓名、奖项、等级、日期)。
  9. 数据统计:各奖项申请人数、投票分布饼图/柱状图。
  10. 历年获奖:历史记录查询。

3.2 业务流程图(申请到获奖)

用户浏览奖项 → 点击申请 → 填写表单 + 上传材料 → 提交(状态:审核中)
→ 管理员后台初审(模拟自动通过)→ 状态:初选通过 → 进入投票环节
→ 投票截止 → 计算票数 → 公示(状态:公示中)→ 最终获奖(状态:已获奖)或未获奖

四、数据结构与服务层设计

4.1 数据模型

// model/Award.ets
export interface Award {
  id: number;
  name: string;               // 如“三好学生”
  description: string;
  requirement: string;        // 申请要求
  bonus: number;              // 奖金
  applyDeadline: string;      // 申请截止日期
  isActive: boolean;          // 是否当前可申请
}

// model/Application.ets
export interface Application {
  id: number;
  awardId: number;            // 关联奖项
  awardName: string;
  applicantId: string;        // 学号
  applicantName: string;
  college: string;
  major: string;
  gpa: number;
  description: string;        // 申请理由
  attachments: string[];      // 附件文件名列表
  applyTime: string;
  votes: number;
  status: '审核中' | '初选通过' | '公示中' | '已获奖' | '未获奖';
  awardLevel?: '金奖' | '银奖' | '铜奖'; // 最终等级
  voteLimit: number;          // 每人可投几票(默认1)
}

// model/VoteRecord.ets
export interface VoteRecord {
  id: number;
  voterId: string;            // 投票人学号
  applicationId: number;
  awardId: number;
  voteTime: string;
}

4.2 服务层(Service)

沿用 BaseService 模式,实现 AwardServiceApplicationServiceVoteService

// service/BaseService.ets(同前)
export abstract class BaseService<T> {
  protected prefName: string;
  protected key: string;
  constructor(prefName: string, key: string) { this.prefName = prefName; this.key = key; }
  protected async getPreferences(): Promise<preferences.Preferences> { /* ... */ }
  protected async loadData(): Promise<T[]> { /* ... */ }
  protected async saveData(data: T[]): Promise<void> { /* ... */ }
  abstract fetch(): Promise<T[]>;
  abstract add(item: T): Promise<T[]>;
  abstract update(id: number, newItem: T): Promise<T[]>;
  abstract delete(id: number): Promise<T[]>;
}

// service/AwardService.ets
import { BaseService } from './BaseService';
import { Award } from '../model/Award';

class AwardService extends BaseService<Award> {
  constructor() { super('AwardPrefs', 'awards'); }
  async fetch(): Promise<Award[]> {
    const data = await this.loadData();
    if (data.length === 0) {
      const mock: Award[] = [
        { id: 1, name: '三好学生', description: '品学兼优,全面发展', requirement: 'GPA≥3.5,无违纪', bonus: 2000, applyDeadline: '2026-12-31', isActive: true },
        { id: 2, name: '优秀学生干部', description: '工作突出,服务同学', requirement: '担任班干部或社团负责人', bonus: 1500, applyDeadline: '2026-11-30', isActive: true },
        { id: 3, name: '一等奖学金', description: '学业成绩优异', requirement: 'GPA≥3.8', bonus: 3000, applyDeadline: '2027-01-15', isActive: false },
      ];
      await this.saveData(mock);
      return mock;
    }
    return data;
  }
  async add(item: Award): Promise<Award[]> { /* ... */ }
  async update(id: number, newItem: Award): Promise<Award[]> { /* ... */ }
  async delete(id: number): Promise<Award[]> { /* ... */ }
}
export const awardService = new AwardService();

// service/ApplicationService.ets(略,类似)
// service/VoteService.ets(略)

五、核心功能实现(完整代码)

5.1 页面状态与数据加载(主页面 Index.ets)

使用 Tabs 切换首页、我的申请、结果公示、历年获奖。以首页为主。

// pages/Index.ets
import { Award } from '../model/Award';
import { Application } from '../model/Application';
import { VoteRecord } from '../model/VoteRecord';
import { awardService } from '../service/AwardService';
import { applicationService } from '../service/ApplicationService';
import { voteService } from '../service/VoteService';
import prompt from '@ohos.prompt';
import router from '@ohos.router';
import { Chart, ChartType } from '@ohos.arkui.advanced';

@Entry
@Component
struct Index {
  @State awards: Award[] = [];
  @State applications: Application[] = [];
  @State voteRecords: VoteRecord[] = [];
  @State currentTabIndex: number = 0;
  @State isLoading: boolean = true;

  // 申请弹窗
  @State isApplyDialogVisible: boolean = false;
  @State selectedAwardId: number = -1;
  @State applyGpa: string = '';
  @State applyDesc: string = '';
  @State applyAttachments: string[] = [];

  // 通知横幅
  @State notice: string = '📢 2026年度评奖评优已启动,请及时申请!';

  // 投票防刷:记录当前用户已投的申请ID(假设用户ID为'me')
  private votedIds: Set<number> = new Set();

  aboutToAppear() {
    this.loadData();
  }

  async loadData() {
    this.isLoading = true;
    try {
      this.awards = await awardService.fetch();
      this.applications = await applicationService.fetch();
      this.voteRecords = await voteService.fetch();
      // 加载已投票记录
      this.voteRecords.filter(v => v.voterId === 'me').forEach(v => this.votedIds.add(v.applicationId));
    } catch (e) {
      prompt.showToast({ message: '数据加载失败' });
    } finally {
      this.isLoading = false;
    }
  }

  // 获取某个奖项的申请列表(用于投票)
  private getApplicationsByAward(awardId: number): Application[] {
    return this.applications.filter(app => app.awardId === awardId && (app.status === '初选通过' || app.status === '公示中'));
  }

  // 获取当前活跃奖项
  private getActiveAwards(): Award[] {
    return this.awards.filter(a => a.isActive);
  }

  // ... 其他方法
}

5.2 奖项展示与申请(含表单弹窗)

奖项卡片展示,点击“申请”打开弹窗。

@Builder AwardCard(award: Award) {
  Column() {
    Row() {
      Text(award.name).fontSize(18).fontWeight(FontWeight.Bold);
      Blank();
      Text(`¥${award.bonus}`).fontSize(16).fontColor('#F59E0B');
    }.width('100%');
    Text(award.description).fontSize(14).fontColor('#666').margin({ top: 4 });
    Text(`截止:${award.applyDeadline}`).fontSize(12).fontColor('#999').margin({ top: 4 });
    Row() {
      if (award.isActive) {
        Button('立即申请')
          .backgroundColor('#1E3A8A')
          .fontSize(12)
          .height(30)
          .onClick(() => this.openApplyDialog(award.id));
      } else {
        Text('已结束').fontSize(12).fontColor('#999');
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.End)
    .margin({ top: 8 });
  }
  .width('100%')
  .padding(12)
  .backgroundColor('#FFF')
  .borderRadius(8)
  .shadow({ radius: 4, color: '#00000020' })
  .margin({ bottom: 10 });
}

private openApplyDialog(awardId: number) {
  this.selectedAwardId = awardId;
  this.applyGpa = '';
  this.applyDesc = '';
  this.applyAttachments = [];
  this.isApplyDialogVisible = true;
}

private async submitApplication() {
  const award = this.awards.find(a => a.id === this.selectedAwardId);
  if (!award) return;
  if (!this.applyDesc.trim()) {
    prompt.showToast({ message: '请填写申请理由' });
    return;
  }
  const gpa = parseFloat(this.applyGpa);
  if (isNaN(gpa) || gpa < 0 || gpa > 4.0) {
    prompt.showToast({ message: '请输入有效绩点(0~4.0)' });
    return;
  }
  const app: Application = {
    id: Date.now(),
    awardId: award.id,
    awardName: award.name,
    applicantId: 'me',
    applicantName: '我',
    college: '计算机学院',
    major: '计算机科学与技术',
    gpa: gpa,
    description: this.applyDesc.trim(),
    attachments: this.applyAttachments,
    applyTime: new Date().toISOString(),
    votes: 0,
    status: '审核中',
    voteLimit: 1
  };
  try {
    this.applications = await applicationService.add(app);
    prompt.showToast({ message: '申请提交成功!' });
    this.isApplyDialogVisible = false;
  } catch (e) {
    prompt.showToast({ message: '提交失败' });
  }
}

申请弹窗内容(含附件上传模拟):

@Builder ApplyDialogContent() {
  Column() {
    Text('申请').fontSize(18).fontWeight(FontWeight.Bold).margin(12);
    Text(`奖项:${this.awards.find(a => a.id === this.selectedAwardId)?.name || ''}`).margin(4);
    TextInput({ placeholder: '绩点(如3.8)', text: this.applyGpa }).type(InputType.Number).onChange(v => this.applyGpa = v).margin(6);
    TextArea({ placeholder: '申请理由(简要说明)', text: this.applyDesc }).onChange(v => this.applyDesc = v).height(80).margin(6);
    // 附件上传(模拟)
    Row() {
      Text('附件:').width(60);
      Button('添加文件(模拟)').onClick(() => {
        // 模拟选择文件
        prompt.showDialog({
          title: '选择文件',
          message: '请选择材料文件(图片/文档)',
          buttons: [{ text: '选择图片' }, { text: '选择文档' }, { text: '取消' }]
        }).then(result => {
          if (result.index !== 2) {
            const file = result.index === 0 ? '成绩单.jpg' : '推荐信.docx';
            this.applyAttachments = [...this.applyAttachments, file];
            prompt.showToast({ message: `已添加:${file}` });
          }
        });
      }).height(30).fontSize(12);
    }.margin(6);
    if (this.applyAttachments.length > 0) {
      Column() {
        ForEach(this.applyAttachments, (file: string) => {
          Row() {
            Text('📎 ' + file).fontSize(12).layoutWeight(1);
            Button('×').onClick(() => {
              this.applyAttachments = this.applyAttachments.filter(f => f !== file);
            }).height(20).fontSize(12).backgroundColor('#DC2626');
          }.width('100%');
        });
      }.width('100%').margin({ top: 4 });
    }
    Row() {
      Button('取消').onClick(() => this.isApplyDialogVisible = false).backgroundColor('#999');
      Button('提交申请').onClick(() => this.submitApplication()).backgroundColor('#1E3A8A').margin({ left: 20 });
    }.margin(16);
  }
  .padding(20)
  .width('90%')
  .backgroundColor('#FFF')
  .borderRadius(16);
}

5.3 在线投票(含动画与防刷)

在首页展示候选列表,每个候选人带投票按钮,投票后按钮禁用且显示“已投”。

@Builder CandidateList() {
  Column() {
    Text('📊 候选人投票').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 12, bottom: 8 }).alignSelf(ItemAlign.Start);
    // 只展示当前有申请且状态为“初选通过”的奖项,简化:取第一个活跃奖项
    const activeAward = this.getActiveAwards()[0];
    if (!activeAward) {
      Text('暂无投票环节').fontSize(14).fontColor('#999');
      return;
    }
    const candidates = this.getApplicationsByAward(activeAward.id);
    if (candidates.length === 0) {
      Text('暂无候选人').fontSize(14).fontColor('#999');
      return;
    }
    List() {
      ForEach(candidates, (app: Application) => {
        ListItem() {
          Row() {
            Column({ space: 4 }) {
              Text(app.applicantName).fontSize(16).fontWeight(FontWeight.Medium);
              Text(`绩点${app.gpa} | 票数:${app.votes}`).fontSize(12).fontColor('#666');
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1);
            if (this.votedIds.has(app.id)) {
              Text('已投').fontSize(14).fontColor('#999');
            } else {
              Button('投票')
                .backgroundColor('#F59E0B')
                .fontSize(14)
                .height(32)
                .onClick(() => this.doVote(app.id));
            }
          }
          .width('100%')
          .padding(10)
          .border({ width: { bottom: 1 }, color: '#F0F0F0' });
        }
      });
    }
    .width('100%')
    .height(200)
  }
  .width('100%')
  .padding(10)
}

private async doVote(applicationId: number) {
  if (this.votedIds.has(applicationId)) {
    prompt.showToast({ message: '您已投过票' });
    return;
  }
  // 每人每奖项限投1票,已通过voteRecords检查,这里再查一次
  const app = this.applications.find(a => a.id === applicationId);
  if (!app) return;
  // 检查是否已经投过该奖项
  const alreadyVoted = this.voteRecords.some(v => v.voterId === 'me' && v.awardId === app.awardId);
  if (alreadyVoted) {
    prompt.showToast({ message: '您已对此奖项投过票' });
    return;
  }
  // 执行投票
  const updatedApp = { ...app, votes: app.votes + 1 };
  try {
    this.applications = await applicationService.update(applicationId, updatedApp);
    // 记录投票
    const vote: VoteRecord = {
      id: Date.now(),
      voterId: 'me',
      applicationId: applicationId,
      awardId: app.awardId,
      voteTime: new Date().toISOString()
    };
    this.voteRecords = await voteService.add(vote);
    this.votedIds.add(applicationId);
    prompt.showToast({ message: '投票成功!' });
    // 动画效果:票数+1的漂浮动画可后续实现
  } catch (e) {
    prompt.showToast({ message: '投票失败' });
  }
}

5.4 评选进度与状态流转

在“我的申请”Tab中展示每个申请的状态和进度条。

// 在Tabs的第二个TabContent中
TabContent() {
  Column() {
    Text('我的申请').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    List() {
      ForEach(this.applications.filter(a => a.applicantId === 'me'), (app: Application) => {
        ListItem() {
          Column({ space: 6 }) {
            Row() {
              Text(app.awardName).fontSize(16).fontWeight(FontWeight.Medium);
              Blank();
              Text(this.getStatusText(app.status)).fontSize(12).fontColor(this.getStatusColor(app.status));
            }.width('100%');
            // 进度条
            Progress({ value: this.getStatusProgress(app.status), total: 100 })
              .width('100%')
              .height(6)
              .color(this.getStatusColor(app.status));
            Text(`申请时间:${app.applyTime.slice(0,10)}`).fontSize(11).fontColor('#999').width('100%');
          }
          .width('100%')
          .padding(12)
          .backgroundColor('#FFF')
          .borderRadius(8)
          .margin({ bottom: 8 });
        }
      });
    }
    .width('100%')
    .layoutWeight(1)
  }
  .padding(10)
}
.tabBar('📝 我的申请')

辅助方法:

private getStatusText(status: string): string {
  const map: Record<string, string> = {
    '审核中': '⏳ 审核中',
    '初选通过': '✅ 初选通过',
    '公示中': '📢 公示中',
    '已获奖': '🏆 已获奖',
    '未获奖': '❌ 未获奖'
  };
  return map[status] || status;
}

private getStatusColor(status: string): string {
  const map: Record<string, string> = {
    '审核中': '#F59E0B',
    '初选通过': '#3B82F6',
    '公示中': '#8B5CF6',
    '已获奖': '#10B981',
    '未获奖': '#EF4444'
  };
  return map[status] || '#999';
}

private getStatusProgress(status: string): number {
  const map: Record<string, number> = {
    '审核中': 25,
    '初选通过': 50,
    '公示中': 75,
    '已获奖': 100,
    '未获奖': 100
  };
  return map[status] || 0;
}

5.5 结果公示与获奖证书(动态生成)

在“结果公示”Tab中展示获奖名单,并支持点击查看证书。

TabContent() {
  Column() {
    Text('🏅 结果公示').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    // 按奖项分组
    const awardIds = [...new Set(this.applications.filter(a => a.status === '已获奖').map(a => a.awardId))];
    if (awardIds.length === 0) {
      Text('暂无公示结果').fontSize(14).fontColor('#999').margin(20);
    } else {
      ForEach(awardIds, (awardId: number) => {
        const award = this.awards.find(a => a.id === awardId);
        if (!award) return;
        Column() {
          Text(`${award.name} 获奖名单`).fontSize(16).fontWeight(FontWeight.Medium).margin({ bottom: 8 }).alignSelf(ItemAlign.Start);
          ForEach(this.applications.filter(a => a.awardId === awardId && a.status === '已获奖'), (app: Application) => {
            Row() {
              Text(app.applicantName).layoutWeight(1);
              Text(app.awardLevel || '金奖').fontSize(12).fontColor('#F59E0B');
              Button('查看证书')
                .onClick(() => {
                  router.pushUrl({ url: 'pages/Certificate', params: { applicationId: app.id } });
                })
                .height(28).fontSize(12).backgroundColor('#1E3A8A');
            }
            .width('100%')
            .padding(8)
            .border({ width: { bottom: 1 }, color: '#F0F0F0' });
          });
        }
        .width('100%')
        .margin({ bottom: 16 });
      });
    }
  }
  .padding(10)
}
.tabBar('📋 结果公示')

证书页面 Certificate.ets(简化):

// pages/Certificate.ets
import router from '@ohos.router';
import { Application } from '../model/Application';
import { applicationService } from '../service/ApplicationService';

@Entry
@Component
struct Certificate {
  @State app: Application | null = null;
  private appId: number = -1;

  aboutToAppear() {
    const params = router.getParams() as { applicationId: number };
    if (params) {
      this.appId = params.applicationId;
      this.loadApp();
    }
  }

  async loadApp() {
    const list = await applicationService.fetch();
    this.app = list.find(a => a.id === this.appId) || null;
  }

  build() {
    Column() {
      if (this.app) {
        Stack() {
          Column() {
            Text('🏆 获奖证书').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#1E3A8A').margin(20);
            Text(`兹证明`).fontSize(16).fontColor('#333');
            Text(this.app.applicantName).fontSize(28).fontWeight(FontWeight.Bold).fontColor('#F59E0B').margin(10);
            Text(`荣获 “${this.app.awardName}`).fontSize(20).margin(10);
            Text(`等级:${this.app.awardLevel || '金奖'}`).fontSize(18).fontColor('#1E3A8A').margin(10);
            Text(`颁发日期:${new Date().toLocaleDateString()}`).fontSize(14).fontColor('#666').margin(20);
          }
          .padding(30)
          .width('85%')
          .backgroundColor('#FFF')
          .borderRadius(16)
          .shadow({ radius: 8, color: '#00000030' })
          .border({ width: 4, color: '#F59E0B' });
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('#F3F4F6');
      } else {
        Text('证书不存在').margin(20);
      }
    }
    .width('100%')
    .height('100%')
  }
}

5.6 历年获奖与统计图表

在“历年获奖”Tab中展示往年记录,并附带统计图表。

TabContent() {
  Column() {
    Text('📊 历年获奖统计').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    // 统计各奖项获奖人数
    const awardNames = [...new Set(this.applications.filter(a => a.status === '已获奖').map(a => a.awardName))];
    const counts = awardNames.map(name => 
      this.applications.filter(a => a.awardName === name && a.status === '已获奖').length
    );
    if (awardNames.length > 0) {
      Chart({
        type: ChartType.Bar,
        datasets: [{ data: counts, color: '#1E3A8A' }],
        options: {
          xAxis: { labels: awardNames, color: '#999' },
          yAxis: { min: 0, step: 1, color: '#999' }
        }
      }).width('100%').height(150).margin({ bottom: 16 });
    }

    Text('📜 历年获奖记录').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 8 }).alignSelf(ItemAlign.Start);
    List() {
      ForEach(this.applications.filter(a => a.status === '已获奖'), (app: Application) => {
        ListItem() {
          Row() {
            Text(`${app.awardName} - ${app.applicantName}`).layoutWeight(1);
            Text(app.awardLevel || '金奖').fontSize(12).fontColor('#F59E0B');
          }
          .width('100%')
          .padding(8)
          .border({ width: { bottom: 1 }, color: '#F0F0F0' });
        }
      });
    }
    .width('100%')
    .layoutWeight(1)
  }
  .padding(10)
}
.tabBar('📅 历年获奖')

5.7 数据持久化(Preferences)

已在服务层实现,每次修改自动保存。


六、UI 界面设计与实现(完整组件)

6.1 顶部通知横幅

使用 Marquee 或滚动文字展示通知。

@Builder NoticeBanner() {
  Row() {
    Text(this.notice)
      .fontSize(14)
      .fontColor('#FFF')
      .padding(8)
      .width('100%')
      .textOverflow({ overflow: TextOverflow.Marquee })
      .marquee({ loop: -1, start: true })
  }
  .width('100%')
  .backgroundColor('#1E3A8A')
  .padding({ left: 16, right: 16 })
}

6.2 奖项列表卡片

AwardCard 构建器。

6.3 候选人列表(可投票)

CandidateList 构建器。

6.4 我的申请状态跟踪

见“我的申请”Tab内容。

6.5 获奖证书展示(卡片翻转)

在证书页面使用 Stack 和动画实现翻转效果(简化)。


七、完整主页面代码(Index.ets)及子页面

由于篇幅,此处提供 Index.ets 完整骨架,所有Builder和逻辑已在前文给出,只需组合。项目结构如下:

entry/src/main/ets/
├── model/
│   ├── Award.ets
│   ├── Application.ets
│   └── VoteRecord.ets
├── service/
│   ├── BaseService.ets
│   ├── AwardService.ets
│   ├── ApplicationService.ets
│   └── VoteService.ets
└── pages/
    ├── Index.ets
    └── Certificate.ets

Index.ets 最终 build 方法:

build() {
  Column() {
    // 顶部通知(仅首页显示)
    if (this.currentTabIndex === 0) {
      this.NoticeBanner();
    }

    Tabs({ barPosition: BarPosition.End }) {
      TabContent() {
        Column() {
          if (this.isLoading) LoadingProgress().color('#1E3A8A');
          else {
            Scroll() {
              Column() {
                // 奖项列表
                ForEach(this.getActiveAwards(), (award: Award) => {
                  this.AwardCard(award);
                });
                // 候选人投票
                this.CandidateList();
              }
              .padding(10)
            }
            .layoutWeight(1)
          }
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#F3F4F6')
      }
      .tabBar('🏠 首页')

      TabContent() { /* 我的申请内容 */ }
      .tabBar('📝 我的申请')

      TabContent() { /* 结果公示内容 */ }
      .tabBar('📋 结果公示')

      TabContent() { /* 历年获奖内容 */ }
      .tabBar('📅 历年获奖')
    }
    .width('100%')
    .layoutWeight(1)
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#FFF')
  .dialog($$this.isApplyDialogVisible, this.ApplyDialogContent())
}

八、运行与调试

8.1 环境

  • DevEco Studio 5.0+,API 24。
  • 无需特殊权限。

8.2 运行

  1. 导入完整项目,确保所有服务层和模型文件存在。
  2. 同步并运行模拟器。
  3. 测试申请、投票、查看状态、结果公示、证书等全部流程。

8.3 调试

  • 使用 HiLog 查看数据变更。
  • 模拟投票防刷逻辑,可尝试重复投票验证。

九、项目总结与扩展思路

9.1 项目总结

  • 全流程覆盖:从申请到公示、证书,完整闭环。
  • 状态管理清晰:使用进度条直观展示申请进程。
  • 交互丰富:弹窗、动画、图表、卡片,提升用户体验。
  • 数据持久化:所有状态保存,重启不丢失。
  • 工程化:分层服务,便于扩展。

9.2 扩展方向

  1. 管理员后台:添加审核、发布奖项、设置投票时间等功能。
  2. 导师推荐:增加推荐人字段,需导师确认。
  3. 综测加分:自动计算综测分数并关联。
  4. 答辩环节:线上答辩预约与评分。
  5. 异议申诉:对结果提出申诉,流程跟踪。
  6. 推送通知:状态变更时推送消息。

运行效果

校园三好学生优秀干部评选应用

Logo

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

更多推荐