HarmonyOS ArkTS 实战:从零实现校园学术会议与研讨会报名系统

目录


一、项目背景与效果预览

1.1 痛点场景

校园内学术讲座、研讨会、国际会议频繁,但报名流程混乱、日程通知不及时、论文提交渠道不统一、签到靠手写……本应用打造一个一站式学术会议平台,涵盖会议浏览、在线报名、日程查看、论文提交、签到、资料下载、嘉宾介绍、参会统计,提升学术活动组织效率。

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

  • 主界面:顶部为海报轮播(显示热门会议图片);中部为会议列表(卡片展示会议名称、时间、地点、报名状态、剩余名额);底部Tabs切换“首页”、“我的会议”、“论文管理”、“统计”。
  • 会议详情:点击会议卡片进入详情页,显示完整信息、日程(时间线)、嘉宾列表、报名按钮(已满或已报则禁用)。
  • 论文提交:在论文管理Tab中可提交新论文(选择会议、填写标题、上传附件),查看审稿状态。
  • 签到:在“我的会议”中,已报名的会议显示签到入口(模拟生成签到二维码)。
  • 统计:展示已报名会议数、提交论文数、参会总次数,并用图表展示各会议报名人数。

主题色采用深青色(#155E75),象征学术、严谨、专业,搭配浅灰背景。


二、技术栈与开发环境

技术项 说明
开发语言 ArkTS
UI 框架 ArkUI 声明式开发
状态管理 @State / @Provide / @Consume
布局方式 List + Grid + Tabs + Swiper(轮播)
数据持久化 @ohos.data.preferences
路由管理 @ohos.router
图表组件 @ohos.arkui.advanced.Chart
弹窗/提示 @ohos.prompt / @ohos.dialog
二维码生成 模拟(使用文本展示)
开发工具 DevEco Studio 5.0+
SDK 版本 API 24 及以上

三、需求分析与功能架构

3.1 核心功能清单

  1. 会议浏览:首页展示所有会议(可分类筛选),海报轮播推荐。
  2. 在线报名:点击会议卡片,进入详情页报名,实时更新剩余名额,满员后禁止报名。
  3. 会议日程:详情页中以时间线展示会议日程安排(每个时段的活动)。
  4. 论文提交:用户选择会议、填写论文标题、上传文件(模拟),提交后进入“审稿中”状态。
  5. 签到管理:已报名的会议在“我的会议”中生成签到二维码(模拟),点击“签到”标记已签到。
  6. 资料下载:会议详情页提供资料列表(如会议手册、PPT),可点击下载(模拟)。
  7. 嘉宾介绍:展示会议嘉宾照片(占位图)、姓名、职称、简介。
  8. 参会统计:以图表展示各会议报名人数,以及个人参会次数、论文数。

3.2 业务流程

用户浏览会议 → 查看详情 → 报名(名额-1) → 我的会议中出现
→ 会议前可查看日程、嘉宾 → 会议当天签到(生成二维码) → 会议后下载资料
→ 可提交论文(选择会议)→ 审稿 → 查看结果

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

4.1 数据模型

// model/Conference.ets
export interface Conference {
  id: number;
  name: string;
  theme: string;
  time: string;           // 会议时间区间
  location: string;
  organizer: string;
  deadline: string;       // 报名截止日期
  fee: number;            // 费用(0为免费)
  participants: number;   // 已报名人数
  maxParticipants: number;
  isRegistered: boolean;  // 当前用户是否已报名
  status: '即将开始' | '进行中' | '已结束';
  bannerImage: string;    // 海报图片资源名
  schedule: ScheduleItem[]; // 日程
  speakers: Speaker[];    // 嘉宾
  materials: Material[];  // 资料
}

export interface ScheduleItem {
  time: string;           // 如 "09:00-10:00"
  title: string;
  speaker: string;
  location: string;       // 会场
}

export interface Speaker {
  name: string;
  title: string;          // 职称/职务
  avatar: string;         // 头像资源名
  bio: string;
}

export interface Material {
  name: string;
  url: string;            // 模拟下载链接
  size: string;           // 文件大小
}

// model/Paper.ets
export interface Paper {
  id: number;
  conferenceId: number;
  conferenceName: string;
  title: string;
  author: string;
  coAuthors?: string[];
  submitTime: string;
  status: '审稿中' | '已录用' | '已拒绝' | '需修改';
  reviewComment: string;
  file: string;           // 附件文件名
}

4.2 服务层(Service)

沿用 BaseService 模式,实现 ConferenceServicePaperService

// service/ConferenceService.ets
import { BaseService } from './BaseService';
import { Conference, ScheduleItem, Speaker, Material } from '../model/Conference';

class ConferenceService extends BaseService<Conference> {
  constructor() { super('ConfPrefs', 'conferences'); }

  async fetch(): Promise<Conference[]> {
    const data = await this.loadData();
    if (data.length === 0) {
      const mock = this.getMockData();
      await this.saveData(mock);
      return mock;
    }
    return data;
  }

  async add(item: Conference): Promise<Conference[]> { /* ... */ }
  async update(id: number, newItem: Conference): Promise<Conference[]> { /* ... */ }
  async delete(id: number): Promise<Conference[]> { /* ... */ }

  private getMockData(): Conference[] {
    const now = Date.now();
    return [
      {
        id: 1,
        name: '2026年人工智能与教育国际研讨会',
        theme: 'AI赋能未来教育',
        time: '2026-08-15 09:00 ~ 2026-08-17 17:00',
        location: '学术报告厅',
        organizer: '计算机学院',
        deadline: '2026-08-01',
        fee: 200,
        participants: 45,
        maxParticipants: 100,
        isRegistered: false,
        status: '即将开始',
        bannerImage: 'banner_ai',
        schedule: [
          { time: '08-15 09:00-10:00', title: '开幕式及主题报告', speaker: '张院士', location: '主会场' },
          { time: '08-15 10:30-12:00', title: 'AI教育应用分论坛', speaker: '李教授', location: '分会场A' },
          { time: '08-16 14:00-16:00', title: '圆桌讨论', speaker: '多位嘉宾', location: '主会场' }
        ],
        speakers: [
          { name: '张院士', title: '中国科学院院士', avatar: 'avatar_zhang', bio: '人工智能专家,教育信息化领军人物' },
          { name: '李教授', title: '清华大学教授', avatar: 'avatar_li', bio: '教育技术学权威' }
        ],
        materials: [
          { name: '会议手册.pdf', url: '/manual.pdf', size: '2.3MB' },
          { name: '主题报告PPT.pptx', url: '/ppt.pptx', size: '5.1MB' }
        ]
      },
      {
        id: 2,
        name: '大数据与智慧校园研讨会',
        theme: '数据驱动校园治理',
        time: '2026-09-10 08:30 ~ 2026-09-11 12:00',
        location: '图书馆报告厅',
        organizer: '信息中心',
        deadline: '2026-09-01',
        fee: 0,
        participants: 80,
        maxParticipants: 80,
        isRegistered: true,
        status: '即将开始',
        bannerImage: 'banner_bigdata',
        schedule: [ /* 略 */ ],
        speakers: [ /* 略 */ ],
        materials: [ /* 略 */ ]
      }
    ];
  }
}
export const conferenceService = new ConferenceService();

// service/PaperService.ets(略)

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

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

使用 Tabs 切换首页、我的会议、论文管理、统计。

// pages/Index.ets
import { Conference } from '../model/Conference';
import { Paper } from '../model/Paper';
import { conferenceService } from '../service/ConferenceService';
import { paperService } from '../service/PaperService';
import prompt from '@ohos.prompt';
import router from '@ohos.router';
import { Chart, ChartType } from '@ohos.arkui.advanced';

@Entry
@Component
struct Index {
  @State conferences: Conference[] = [];
  @State papers: Paper[] = [];
  @State currentTabIndex: number = 0;
  @State isLoading: boolean = true;
  @State bannerIndex: number = 0;

  // 论文提交弹窗
  @State isPaperDialogVisible: boolean = false;
  @State paperConferenceId: number = -1;
  @State paperTitle: string = '';
  @State paperFile: string = '';

  // 签到状态
  @State signedConferences: Set<number> = new Set();

  aboutToAppear() {
    this.loadData();
  }

  async loadData() {
    this.isLoading = true;
    try {
      this.conferences = await conferenceService.fetch();
      this.papers = await paperService.fetch();
    } catch (e) {
      prompt.showToast({ message: '数据加载失败' });
    } finally {
      this.isLoading = false;
    }
  }

  // 报名
  private async register(conferenceId: number) {
    const conf = this.conferences.find(c => c.id === conferenceId);
    if (!conf) return;
    if (conf.participants >= conf.maxParticipants) {
      prompt.showToast({ message: '会议已满员' });
      return;
    }
    if (conf.isRegistered) {
      prompt.showToast({ message: '您已报名' });
      return;
    }
    const updated = { ...conf, isRegistered: true, participants: conf.participants + 1 };
    try {
      this.conferences = await conferenceService.update(conferenceId, updated);
      prompt.showToast({ message: '报名成功!' });
    } catch (e) {
      prompt.showToast({ message: '报名失败' });
    }
  }

  // 签到
  private async signIn(conferenceId: number) {
    if (this.signedConferences.has(conferenceId)) {
      prompt.showToast({ message: '已签到' });
      return;
    }
    // 模拟签到逻辑:可记录签到时间等
    this.signedConferences.add(conferenceId);
    prompt.showToast({ message: '签到成功!' });
  }

  // ... 其他方法
}

5.2 会议信息展示与报名(含剩余名额)

在首页展示会议列表,点击卡片进入详情页(或直接在首页报名,为了简洁我们放详情页)。这里我们在首页展示简要信息,点击进入详情页。

@Builder ConferenceCard(conf: Conference) {
  Column() {
    Row() {
      // 海报缩略图(纯色占位)
      Row().width(80).height(80).backgroundColor('#E5E7EB').borderRadius(8)
        .overlay(Text('📷').fontSize(20).fontColor('#999'));
      Column() {
        Text(conf.name).fontSize(16).fontWeight(FontWeight.Medium).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis });
        Text(`${conf.time.slice(0,16)}`).fontSize(12).fontColor('#666').margin({ top: 2 });
        Text(`${conf.location}`).fontSize(12).fontColor('#666');
        Row() {
          Text(`报名 ${conf.participants}/${conf.maxParticipants}`).fontSize(12).fontColor('#155E75');
          Blank();
          if (conf.isRegistered) {
            Text('已报名').fontSize(12).fontColor('#10B981');
          } else if (conf.participants >= conf.maxParticipants) {
            Text('已满').fontSize(12).fontColor('#EF4444');
          } else {
            Text('可报名').fontSize(12).fontColor('#F59E0B');
          }
        }
        .width('100%')
        .margin({ top: 4 });
      }
      .layoutWeight(1)
      .margin({ left: 10 })
      .alignItems(HorizontalAlign.Start);
    }
    .width('100%')
    .padding(10)
    .backgroundColor('#FFF')
    .borderRadius(8)
    .shadow({ radius: 2, color: '#00000010' })
    .onClick(() => {
      router.pushUrl({ url: 'pages/Detail', params: { conferenceId: conf.id } });
    });
  }
  .margin({ bottom: 10 });
}

详情页 Detail.ets(摘要):

// pages/Detail.ets
import router from '@ohos.router';
import { Conference } from '../model/Conference';
import { conferenceService } from '../service/ConferenceService';
import prompt from '@ohos.prompt';

@Entry
@Component
struct Detail {
  @State conf: Conference | null = null;
  private confId: number = -1;

  aboutToAppear() {
    const params = router.getParams() as { conferenceId: number };
    if (params) {
      this.confId = params.conferenceId;
      this.loadConf();
    }
  }

  async loadConf() {
    const list = await conferenceService.fetch();
    this.conf = list.find(c => c.id === this.confId) || null;
  }

  build() {
    Column() {
      if (this.conf) {
        Scroll() {
          Column({ space: 12 }) {
            Text(this.conf.name).fontSize(20).fontWeight(FontWeight.Bold).width('100%');
            Text(`主题:${this.conf.theme}`).fontSize(14).width('100%');
            Text(`时间:${this.conf.time}`).fontSize(14);
            Text(`地点:${this.conf.location}`).fontSize(14);
            Text(`主办:${this.conf.organizer}`).fontSize(14);
            Text(`报名截止:${this.conf.deadline}`).fontSize(14);
            Text(`费用:${this.conf.fee === 0 ? '免费' : '¥'+this.conf.fee}`).fontSize(14);
            Text(`已报名:${this.conf.participants}/${this.conf.maxParticipants}`).fontSize(14);
            // 报名按钮
            if (this.conf.isRegistered) {
              Button('已报名').enabled(false).backgroundColor('#10B981');
            } else if (this.conf.participants >= this.conf.maxParticipants) {
              Button('已满员').enabled(false).backgroundColor('#EF4444');
            } else {
              Button('立即报名').backgroundColor('#155E75').onClick(() => {
                // 调用父页面的报名?可以通过事件或直接调用服务,但为解耦,可在本页调用服务
                // 简化:通过路由传参,让首页刷新?或直接在本页更新
                this.doRegister();
              });
            }
            // 日程时间线
            this.ScheduleTimeline();
            // 嘉宾列表
            this.SpeakerList();
            // 资料下载
            this.MaterialList();
          }
          .padding(16)
          .alignItems(HorizontalAlign.Start)
        }
        .layoutWeight(1)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F9FAFB')
  }

  private async doRegister() {
    if (!this.conf) return;
    if (this.conf.isRegistered || this.conf.participants >= this.conf.maxParticipants) return;
    const updated = { ...this.conf, isRegistered: true, participants: this.conf.participants + 1 };
    try {
      const list = await conferenceService.update(this.conf.id, updated);
      // 从列表中取最新
      this.conf = list.find(c => c.id === this.conf.id) || null;
      prompt.showToast({ message: '报名成功' });
    } catch (e) { prompt.showToast({ message: '报名失败' }); }
  }

  @Builder ScheduleTimeline() {
    Column() {
      Text('📅 会议日程').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 12 });
      ForEach(this.conf?.schedule || [], (item: any) => {
        Row() {
          Column() {
            Circle({ width: 10, height: 10 }).fill('#155E75');
            if (item !== this.conf?.schedule[this.conf.schedule.length-1]) {
              Rect({ width: 2, height: 40 }).fill('#D1D5DB');
            }
          }.width(20).alignItems(HorizontalAlign.Center);
          Column() {
            Text(item.time).fontSize(13).fontWeight(FontWeight.Medium);
            Text(item.title).fontSize(14);
            Text(`主讲:${item.speaker} | 地点:${item.location}`).fontSize(12).fontColor('#666');
          }
          .alignItems(HorizontalAlign.Start)
          .margin({ left: 8 })
          .layoutWeight(1);
        }
        .width('100%')
        .padding({ top: 4, bottom: 4 });
      });
    }
    .width('100%')
  }

  @Builder SpeakerList() {
    Column() {
      Text('👤 演讲嘉宾').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 12 });
      ForEach(this.conf?.speakers || [], (speaker: any) => {
        Row() {
          Circle({ width: 40, height: 40 }).fill('#D1D5DB')
            .overlay(Text(speaker.avatar).fontSize(20).fontColor('#999'));
          Column() {
            Text(speaker.name).fontSize(15).fontWeight(FontWeight.Medium);
            Text(speaker.title).fontSize(12).fontColor('#666');
            Text(speaker.bio).fontSize(12).fontColor('#999').maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis });
          }
          .margin({ left: 10 })
          .layoutWeight(1)
          .alignItems(HorizontalAlign.Start);
        }
        .width('100%')
        .padding({ top: 6, bottom: 6 });
      });
    }
    .width('100%')
  }

  @Builder MaterialList() {
    Column() {
      Text('📁 会议资料').fontSize(16).fontWeight(FontWeight.Medium).margin({ top: 12 });
      ForEach(this.conf?.materials || [], (material: any) => {
        Row() {
          Text(`📄 ${material.name}`).layoutWeight(1);
          Text(material.size).fontSize(12).fontColor('#999');
          Button('下载')
            .onClick(() => prompt.showToast({ message: `开始下载 ${material.name}` }))
            .height(28).fontSize(12).backgroundColor('#155E75');
        }
        .width('100%')
        .padding({ top: 6, bottom: 6 })
        .border({ width: { bottom: 1 }, color: '#F0F0F0' });
      });
    }
    .width('100%')
  }
}

5.3 会议日程时间线(可展开)

已在详情页中实现,见上。

5.4 论文提交(含文件上传模拟)

在“论文管理”Tab中,可提交新论文。

// 在 Index.ets 的论文管理 Tab 中
TabContent() {
  Column() {
    Text('📝 我的论文').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    Button('提交新论文')
      .backgroundColor('#155E75')
      .margin(12)
      .onClick(() => {
        // 弹出选择会议对话框
        const confNames = this.conferences.map(c => c.name);
        prompt.showDialog({
          title: '选择会议',
          items: confNames,
          buttons: [{ text: '取消' }, { text: '确定' }]
        }).then(result => {
          if (result.index === 1 && result.value !== undefined) {
            const selectedName = confNames[result.value];
            const conf = this.conferences.find(c => c.name === selectedName);
            if (conf) {
              this.paperConferenceId = conf.id;
              this.paperTitle = '';
              this.paperFile = '';
              this.isPaperDialogVisible = true;
            }
          }
        });
      });
    List() {
      ForEach(this.papers, (paper: Paper) => {
        ListItem() {
          Row() {
            Column() {
              Text(paper.title).fontSize(15).fontWeight(FontWeight.Medium);
              Text(`会议:${paper.conferenceName} | 提交:${paper.submitTime.slice(0,10)}`).fontSize(12).fontColor('#666');
              Text(`状态:${paper.status}`).fontSize(12).fontColor(
                paper.status === '已录用' ? '#10B981' :
                paper.status === '已拒绝' ? '#EF4444' : '#F59E0B'
              );
            }
            .alignItems(HorizontalAlign.Start)
            .layoutWeight(1);
            Text(paper.file).fontSize(11).fontColor('#999');
          }
          .width('100%')
          .padding(10)
          .border({ width: { bottom: 1 }, color: '#F0F0F0' });
        }
      });
    }
    .width('100%')
    .layoutWeight(1)
  }
}
.tabBar('📝 论文')

论文提交弹窗:

@Builder PaperDialogContent() {
  Column() {
    Text('提交论文').fontSize(18).fontWeight(FontWeight.Bold).margin(12);
    Text(`会议:${this.conferences.find(c => c.id === this.paperConferenceId)?.name || ''}`).margin(4);
    TextInput({ placeholder: '论文标题', text: this.paperTitle }).onChange(v => this.paperTitle = v).margin(6);
    Row() {
      Text('附件:').width(60);
      Button('选择文件(模拟)').onClick(() => {
        prompt.showDialog({
          title: '选择文件',
          message: '支持 PDF, Word',
          buttons: [{ text: '选择PDF' }, { text: '选择Word' }, { text: '取消' }]
        }).then(result => {
          if (result.index !== 2) {
            this.paperFile = result.index === 0 ? 'paper.pdf' : 'paper.docx';
            prompt.showToast({ message: `已选择 ${this.paperFile}` });
          }
        });
      }).height(30).fontSize(12);
    }.margin(6);
    if (this.paperFile) Text(`已选:${this.paperFile}`).fontSize(12).fontColor('#155E75');
    Row() {
      Button('取消').onClick(() => this.isPaperDialogVisible = false).backgroundColor('#999');
      Button('提交').onClick(() => this.submitPaper()).backgroundColor('#155E75').margin({ left: 20 });
    }.margin(16);
  }
  .padding(20)
  .width('90%')
  .backgroundColor('#FFF')
  .borderRadius(16);
}

private async submitPaper() {
  if (!this.paperTitle.trim()) { prompt.showToast({ message: '请输入标题' }); return; }
  if (!this.paperFile) { prompt.showToast({ message: '请选择附件' }); return; }
  const conf = this.conferences.find(c => c.id === this.paperConferenceId);
  if (!conf) return;
  const paper: Paper = {
    id: Date.now(),
    conferenceId: conf.id,
    conferenceName: conf.name,
    title: this.paperTitle.trim(),
    author: '我',
    submitTime: new Date().toISOString(),
    status: '审稿中',
    reviewComment: '',
    file: this.paperFile
  };
  try {
    this.papers = await paperService.add(paper);
    prompt.showToast({ message: '论文提交成功' });
    this.isPaperDialogVisible = false;
  } catch (e) {
    prompt.showToast({ message: '提交失败' });
  }
}

5.5 签到管理(二维码模拟)

在“我的会议”Tab中,展示已报名会议,提供签到按钮,点击后展示二维码(模拟)。

TabContent() {
  Column() {
    Text('📌 我的会议').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    List() {
      ForEach(this.conferences.filter(c => c.isRegistered), (conf: Conference) => {
        ListItem() {
          Column() {
            Row() {
              Text(conf.name).fontSize(16).fontWeight(FontWeight.Medium).layoutWeight(1);
              Text(conf.status).fontSize(12).fontColor(
                conf.status === '即将开始' ? '#F59E0B' :
                conf.status === '进行中' ? '#3B82F6' : '#999'
              );
            }.width('100%');
            Text(`${conf.time.slice(0,16)} ${conf.location}`).fontSize(13).fontColor('#666').width('100%').margin({ top: 4 });
            Row() {
              if (this.signedConferences.has(conf.id)) {
                Text('✅ 已签到').fontColor('#10B981');
              } else {
                Button('签到').onClick(() => this.signIn(conf.id)).backgroundColor('#155E75').height(28).fontSize(12);
                Button('查看签到码').onClick(() => {
                  // 模拟二维码,显示文本
                  prompt.showDialog({
                    title: '签到二维码(模拟)',
                    message: `签到码:CONF-${conf.id}-${conf.name.slice(0,4)}`,
                    buttons: [{ text: '确定' }]
                  });
                }).backgroundColor('#F59E0B').height(28).fontSize(12).margin({ left: 8 });
              }
            }
            .width('100%')
            .justifyContent(FlexAlign.End)
            .margin({ top: 6 });
          }
          .width('100%')
          .padding(12)
          .backgroundColor('#FFF')
          .borderRadius(8)
          .margin({ bottom: 8 });
        }
      });
    }
    .width('100%')
    .layoutWeight(1)
  }
}
.tabBar('📋 我的会议')

5.6 资料下载与嘉宾介绍

已在详情页中实现。

5.7 数据统计图表(参会/论文分布)

在“统计”Tab中展示。

TabContent() {
  Column() {
    Text('📊 参会统计').fontSize(18).fontWeight(FontWeight.Bold).margin(12).alignSelf(ItemAlign.Start);
    // 个人统计
    Row() {
      Column() { Text('已报名').fontSize(12).fontColor('#666'); Text(`${this.conferences.filter(c => c.isRegistered).length}`).fontSize(20).fontWeight(FontWeight.Bold); }
      .layoutWeight(1).alignItems(HorizontalAlign.Center);
      Column() { Text('提交论文').fontSize(12).fontColor('#666'); Text(`${this.papers.length}`).fontSize(20).fontWeight(FontWeight.Bold); }
      .layoutWeight(1).alignItems(HorizontalAlign.Center);
      Column() { Text('已签到').fontSize(12).fontColor('#666'); Text(`${this.signedConferences.size}`).fontSize(20).fontWeight(FontWeight.Bold); }
      .layoutWeight(1).alignItems(HorizontalAlign.Center);
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#F3F4F6')
    .borderRadius(8)
    .margin({ bottom: 16 });

    // 各会议报名人数柱状图
    const confNames = this.conferences.map(c => c.name.slice(0,6));
    const participants = this.conferences.map(c => c.participants);
    if (confNames.length > 0) {
      Chart({
        type: ChartType.Bar,
        datasets: [{ data: participants, color: '#155E75' }],
        options: {
          xAxis: { labels: confNames, color: '#999' },
          yAxis: { min: 0, step: 10, color: '#999' }
        }
      }).width('100%').height(150);
    }
  }
  .padding(10)
}
.tabBar('📊 统计')

5.8 数据持久化(Preferences)

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


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

6.1 顶部海报轮播(Banner)

在首页顶部添加 Swiper 轮播。

@Builder BannerSwiper() {
  Swiper() {
    ForEach(this.conferences.filter(c => c.bannerImage), (conf: Conference) => {
      Row()
        .width('100%')
        .height(150)
        .backgroundColor('#155E75')
        .borderRadius(8)
        .overlay(Text(conf.name).fontSize(16).fontColor('#FFF').shadow({ radius: 4 }))
    })
  }
  .autoPlay(true)
  .interval(3000)
  .indicator(true)
  .width('100%')
  .height(150)
  .margin({ bottom: 12 })
}

6.2 会议列表卡片

ConferenceCard

6.3 日程时间线

见详情页中的 ScheduleTimeline

6.4 论文提交入口与状态

见“论文管理”Tab。

6.5 我的会议与签到入口

见“我的会议”Tab。


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

Index.ets 完整结构如下(组合所有片段):

// Index.ets
// ... 导入
@Entry
@Component
struct Index {
  // 所有状态
  // 所有方法
  // Builders
  build() {
    Column() {
      Tabs({ barPosition: BarPosition.End }) {
        TabContent() { // 首页
          if (this.isLoading) LoadingProgress().color('#155E75');
          else {
            Scroll() {
              Column() {
                this.BannerSwiper();
                ForEach(this.conferences, (conf: Conference) => {
                  this.ConferenceCard(conf);
                });
              }
              .padding(10)
            }
            .layoutWeight(1)
          }
        }.tabBar('🏠 首页')

        TabContent() { /* 我的会议 */ }.tabBar('📋 我的会议')
        TabContent() { /* 论文管理 */ }.tabBar('📝 论文')
        TabContent() { /* 统计 */ }.tabBar('📊 统计')
      }
      .width('100%')
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F9FAFB')
    .dialog($$this.isPaperDialogVisible, this.PaperDialogContent())
  }
}

子页面 Detail.etsCertificate.ets(可省略)已在前文给出。


八、运行与调试

8.1 环境

  • DevEco Studio 5.0+,API 24。
  • 无需额外权限。

8.2 运行

  1. 导入完整项目,包含服务层和模型。
  2. 运行模拟器,测试会议浏览、报名、日程、论文提交、签到、统计等功能。

8.3 调试

  • 使用 HiLog 打印操作日志。
  • 测试满员报名、重复报名等边界情况。

九、项目总结与扩展思路

9.1 项目总结

  • 功能完整:覆盖会议管理全生命周期(浏览→报名→参会→签到→论文提交)。
  • 交互丰富:轮播、时间线、弹窗、图表、二维码模拟。
  • 工程化:分层服务、数据持久化、路由分离。
  • 可扩展性:易于增加线上直播、互动问答等功能。

9.2 扩展方向

  1. 线上直播:接入直播组件,支持远程参会。
  2. 会议笔记:参会时记录笔记,云端同步。
  3. 提问互动:会中提问、弹幕。
  4. 论文录用通知:状态变化时推送消息。
  5. 学分认定:参会可获学分,自动关联教务系统。
  6. 多语言支持:国际化。

运行效果

校园学术会议与研讨会报名应用

Logo

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

更多推荐