【共创季稿事节】毕业季·鸿蒙同行:我用 HarmonyOS 做了一个「鸿蒙求职罗盘」,陪自己走完校招这一程
【共创季稿事节】毕业季·鸿蒙同行:我用 HarmonyOS 做了一个「鸿蒙求职罗盘」,陪自己走完校招这一程
投稿方向:毕业季·鸿蒙同行。
一、写在前面:毕设做完了,求职才刚刚开始
几个月前,我完成了自己的毕业设计——一款叫「鸿蒙毕设伴侣」的应用,用它管理毕设进度、润色论文、演练答辩。写完那篇复盘文章时,我以为自己已经把"毕业季"这件事讲完整了。
但很快我意识到:毕设只是毕业季的上半场,求职才是下半场,而且往往更漫长、更煎熬。 投了简历石沉大海、面试被问住哑口无言、offer 迟迟不来的焦虑,比赶论文的焦虑更磨人——因为论文的截止日期是确定的,而求职的"什么时候能上岸"是不确定的。
我投了不少公司,投递记录散落在各个招聘 App 里,笔试通知在邮箱,面试安排在微信,复盘的心得只存在脑子里,过几天就忘了当时具体是怎么答的、哪里答得不好。我又想起了做毕设伴侣时的那个念头:我是一个正在学鸿蒙的学生,为什么不用鸿蒙做点什么,来解决我正在经历的问题?
于是就有了这款「鸿蒙求职罗盘」——一个专门陪应届生走完校招这一程的鸿蒙应用。它管理我的每一次投递,帮我把简历里模糊的项目经历改得更专业,陪我模拟面试官可能问的每一个刁钻问题,也记录下每一次面试后我自己的复盘和反思。
这款应用和「鸿蒙毕设伴侣」是姊妹项目——一个负责"毕业设计"这条线,一个负责"求职、实习与成长"这条线,两个应用合在一起,正好覆盖了「毕业季·鸿蒙同行」这个方向里我最想讲的故事。
先看一眼成品。这是应用在 DevEco Studio 里,运行在 Mate 80 Pro(HarmonyOS 6.1.0) 模拟器上的样子——投递看板页:

三张统计卡片一目了然:总投递、进行中、Offer。下面是具体的投递记录——华为(内推,二面)、某互联网大厂(笔试)、某创业公司(已投递)。这是我自己求职季最真实的状态截面:几家在谈、几家在等、一家还没消息。
二、需求梳理:求职季最真实的四个痛点
在写代码之前,我先诚实地梳理了自己求职这段时间遇到的四个具体痛点:
- 投递记录到处乱记,进度记不清。 投了十几家公司,每家进展到哪一步(笔试?一面?二面?)全靠脑子记,经常搞混。
- 简历写不出彩,项目经历流水账。 我的项目经历描述总是像"做了一个xx功能",缺乏量化和亮点,不知道怎么改。
- 面试没底气,不知道会被问什么。 尤其是技术面,总担心被问到自己没准备的知识点,一慌就语无伦次。
- 面完就忘,同样的问题下次还是答不好。 每次面试完只有一个模糊的"感觉还行/感觉不太好",没有具体记录,下次遇到类似问题还是老样子。
这四个痛点,对应了应用的四个 Tab:看板、简历、面试、我的。和做毕设伴侣时一样,我坚持"先想清楚自己真实需要什么,再决定做什么功能"——这是我做这两个项目学到的最重要的一课。
三、架构设计:复用经验,快速搭建
3.1 和毕设伴侣同构的技术路线
做完毕设伴侣之后,我发现"AI 出题→作答→点评"这套模式几乎可以套用在任何"需要陪练"的场景——答辩演练是这样,模拟面试也是这样。所以这次开发,我大量复用了已验证过的架构模式:
entry/src/main/ets/
├── entryability/EntryAbility.ets 入口:初始化、沉浸式全屏、安全区
├── common/
│ ├── Theme.ets 深空蓝罗盘风主题
│ ├── AIConfig.ets 模型配置 + 求职顾问系统提示词
│ └── TimeUtil.ets 相对时间格式化
├── model/
│ ├── JobStore.ets 数据模型 + 持久化 + 状态机配色
│ └── AIService.ets AI 服务:多轮对话 + 单轮任务
└── pages/
├── Index.ets 四 Tab 主壳
├── BoardTab.ets 投递看板
├── ResumeTab.ets AI 简历顾问
├── InterviewTab.ets 模拟面试
└── MyTab.ets 复盘日记 + 我的
这次开发速度明显比第一次做毕设伴侣快很多——因为架构分层的思路已经跑通过一次,AIService 的多轮对话封装、任务模板法、JSON/文本解析容错,这些方法论都可以直接照搬。这让我真切体会到"复用架构经验"这件事的价值:第一个项目投入的心血,会在第二个项目里加速兑现,这也是成长的一种具体体现。
3.2 四个 Tab 对应四个痛点
- 🧭 看板:投递状态机管理,解决"记不清进度"
- 📄 简历:AI 简历顾问,解决"写不出彩"
- 🎤 面试:模拟面试陪练,解决"没底气"
- 👤 我的:复盘日记,解决"面完就忘"
四、投递看板:把"记不清"变成"看得见"
看板要解决的核心问题是:十几家公司的投递进度,要在一个地方一眼看清。
4.1 投递状态机
我把求职投递抽象成一个七态状态机:
export const STAGES: string[] = ['已投递', '笔试', '一面', '二面', '三面', 'Offer', '已挂'];
export interface JobApp {
id: string;
company: string; // 公司名
position: string; // 岗位
stage: string; // 当前状态(STAGES 之一)
note: string; // 备注(渠道/内推人等)
updateTime: number;
}
每条投递记录点击状态标签,会弹出底部选择框,选一个新状态就完成流转:
static async updateStage(id: string, stage: string): Promise<JobApp[]> {
const cur = await JobStore.listApps();
for (const a of cur) { if (a.id === id) { a.stage = stage; a.updateTime = Date.now(); } }
const sorted = cur.sort((a: JobApp, b: JobApp) => b.updateTime - a.updateTime); // 更新后重新按时间排序
await JobStore.saveApps(sorted);
return sorted;
}
状态更新的同时刷新 updateTime 并重新排序,这样最近有动态的投递会自动排到列表最前面——这是个小细节,但对求职者来说很实用:你最关心的永远是"最近有进展的那几家",而不是按添加时间死板排列的列表。
4.2 状态配色,一眼分辨紧急程度
每个状态对应一个颜色,从"已投递"的中性灰,到"二面/三面"的warning色,再到"Offer"的成功绿、"已挂"的警示红:
export function stageColor(stage: string): string {
switch (stage) {
case '已投递': return '#647291';
case '笔试': return '#3E8EF7';
case '一面': return '#9E7BFF';
case '二面': return '#F5B93E';
case '三面': return '#FF8A5B';
case 'Offer': return '#2FD9A8';
case '已挂': return '#F76A6A';
default: return '#647291';
}
}
配合概览区的三张统计卡(总投递、进行中、Offer 数),打开应用第一眼就能看到自己求职的"战况"——这种"数字化自己进度"的感觉,比脑子里模糊记着"投了好多家,进展不知道"要踏实得多。
五、AI 简历顾问:把流水账改成有亮点的表达
这是我认为整个应用里,对求职最直接有用的一个功能。
5.1 四类任务模板
简历问题千差万别,但常见的求助场景可以归纳成四类:润色经历、写自我介绍、STAR 法则改写、岗位匹配分析。我依然沿用"任务模板 + 用户输入"的套路:
private tasks: QuickTask[] = [
{ label: '润色经历', tpl: '请帮我润色下面这段项目经历描述,使其更专业、有量化亮点,并指出改进点:\n{X}' },
{ label: '自我介绍', tpl: '我的背景是:{X}。请帮我写一段 1 分钟左右的求职自我介绍(技术岗)。' },
{ label: 'STAR改写', tpl: '请用 STAR 法则(情境-任务-行动-结果)改写下面这段经历,突出个人贡献与量化成果:\n{X}' },
{ label: '岗位匹配', tpl: '这是招聘岗位要求:{X}。请分析我应该在简历中突出哪些技能与项目,并给出建议。' }
];
5.2 真实效果:AI 一针见血地指出问题
下图是我把自己简历里一段"项目经历"贴给 AI 润色的真实效果——我原本写的是一段更像"个人博客简介"的描述(“关注 HarmonyOS、Flutter、人工智能及跨端开发技术,喜欢分享技术文章”),AI 直接指出了三个问题:

AI 反馈非常直接:定位模糊(像博客简介而非简历内容)、缺乏量化成果与技术深度、未体现工程能力与业务价值。这三条反馈,说实话,比我自己反复读十遍都更能一针见血地戳中问题。这也是为什么我把系统提示词里 AI 的人设定得很明确:
static readonly systemPrompt: string =
'你是一位资深的校招/实习求职顾问兼技术面试官,熟悉简历优化、自我介绍、' +
'技术面试与 HR 面试的常见套路。请用专业、具体、鼓励性的中文帮助应届生求职,' +
'回答要可操作、有针对性,避免空泛的套话。';
“专业、具体、鼓励性”、“可操作、有针对性、避免空泛套话”——这几个关键词是我反复调试后确定的,第一版提示词让 AI 给出的建议比较笼统("建议增加更多细节"这类),加上"避免空泛的套话"这句限制后,AI 的反馈明显更犀利、更具体了。提示词里明确禁止什么,有时候比说明白要求什么更有效。
5.3 多轮追问:把改到满意为止
首轮任务模板发送后,后续的对话就自动进入自由追问模式:
const prompt = this.messages.length === 0
? this.tasks[this.activeTask].tpl.replace('{X}', raw) // 首轮:套用任务模板
: raw; // 后续:自由追问
拿到 AI 指出问题后,我可以接着问"那你帮我改一版",或者"我这个项目具体是做了xx功能,怎么量化",一路把简历改到自己满意为止。这种多轮打磨的过程,本身就是一次很好的自我梳理。
六、模拟面试:技术面 + HR 面双模式陪练
面试焦虑的核心是"未知"——不知道会被问什么。这个功能就是要把未知变成已知,提前练。
6.1 技术面 / HR 面切换
我把面试拆成两种模式,因为技术面和 HR 面的问题画像完全不同:
private modes: string[] = ['技术面', 'HR面'];
const modeDesc = this.mode === 0 ? '技术面试官' : 'HR 面试官';
const prompt = `我的求职背景是:「${this.topic.trim()}」。` +
`请站在${modeDesc}角度,预测 5 个最可能被问到的面试问题。` +
`只输出问题列表,每行一个问题,用数字编号,不要额外说明。`;
6.2 真实效果:Java 技术栈的面试题预测
我输入"java"作为技术方向,切到"技术面"模式,AI 生成的问题相当扎实:

三个问题分别是——HashMap 的底层实现与 JDK 8 优化点、synchronized 与 ReentrantLock 的区别、Java 内存模型与 volatile 的可见性/有序性/原子性。这几乎就是 Java 后端面试的高频三连问,专业度完全不输真实的面试官。
切到"HR面"模式,同样输入"java",AI 生成的问题风格完全不同:

自我介绍、为什么选 Java(技术选型动机)、遇到过最棘手的问题及如何解决——明显能感受到 HR 面更关注表达能力、职业动机和解决问题的思路,而不是纯技术细节。同一个技术方向,两种模式下 AI 给出的问题画像截然不同,这背后靠的就是提示词里"站在技术面试官角度"和"站在 HR 面试官角度"这一句话的差异。这让我意识到,在提示词里明确身份视角,是让大模型输出符合场景预期的一个简单却极其有效的手段。
6.3 出题解析与容错
和毕设伴侣的答辩演练一样,我要求 AI"只输出编号问题列表",然后用正则清洗:
private parseQuestions(text: string): string[] {
const lines = text.split('\n');
const out: string[] = [];
for (const raw of lines) {
const line = raw.trim();
if (line === '') { continue; }
const cleaned = line.replace(/^[Qq]?\s*\d+\s*[\.\、\:\))\.]?\s*/, '').trim(); // 去除各种编号格式
if (cleaned !== '') { out.push(cleaned); }
}
return out.slice(0, 8);
}
这套解析逻辑我在两个项目里几乎原封不动地复用了——这也印证了前面说的,方法论层面的经验,会随着项目积累越来越好用。
6.4 作答与点评闭环
每道题下面有"练习作答",写完提交后 AI 会点评:
const prompt = `${modeDesc}面试问题:${this.questions[i]}\n` +
`候选人的回答:${this.answer.trim()}\n` +
`请作为面试官,简要点评这个回答(优点、不足、改进建议),控制在 150 字内。`;
"出题→作答→点评"形成完整闭环,相当于把面试现场提前搬到自己房间里反复练习。我练完 Java 并发那道题之后,发现自己对 synchronized 底层锁升级机制讲得含糊,这正是复盘价值所在——练习中暴露的漏洞,比面试现场暴露的漏洞成本低得多。
七、复盘日记:把"面完就忘"变成持续的成长记录
这是我认为整个应用里,最能体现"成长"这个主题的功能。
7.1 为什么复盘比刷题更重要
求职期间我发现一个现象:很多人(包括曾经的我)面试完只会有一个模糊的印象——“感觉还行"或"感觉挂了”,但具体问了什么、哪里答得不好、下次该怎么改进,都随着时间被遗忘。没有记录的经验,等于没有发生过的经验。
所以我设计了「复盘日记」这个模块,鼓励自己每场面试后花 5 分钟,把具体问题和反思记下来:
export interface ReviewLog {
id: string;
company: string;
title: string; // 简述,如"一面复盘"
content: string;
time: number;
}
7.2 一条真实的复盘记录
下图是「我的」页面,展示了一条我自己写的真实复盘——华为一面之后的记录:

复盘内容是这样写的:“问了 ArkTS 与 TypeScript 的区别、@State/@Prop 状态管理原理,还问了项目里怎么处理大模型输出不稳定的问题。答得还算流畅,但对分布式能力细节回答不够深入,需要补一下。”
这条记录很朴素,但价值巨大——它精确指出了我在"分布式能力"这个知识点上的薄弱环节。如果没有这条记录,我可能过几天就忘了这个具体的薄弱点,下一场面试遇到类似问题还是会卡壳。 有了它,我在准备下一场面试前,会专门去补一下鸿蒙分布式能力的细节,这就是"记录"带来的实际改进。
页面顶部我特意起了个标题——“求职同行者”,配文"每一次面试,都是一次成长"。这不是一句空洞的口号,而是我做这个功能时最真实的心态:求职路上每一次挫折,只要认真复盘,都能转化成下一次的底气。
7.3 时间线可视化
复盘记录用竖向时间线展示,每条记录带公司名、标题、内容摘要和相对时间:
Row({ space: 10 }) {
Column() {
Circle({ width: 10, height: 10 }).fill(C.primary)
if (i < this.reviews.length - 1) {
Divider().vertical(true).color(C.stroke).strokeWidth(2).height(40)
}
}.width(14).alignItems(HorizontalAlign.Center)
Column({ space: 4 }) {
Row() {
Text(r.company + ' · ' + r.title).fontWeight(FontWeight.Bold).layoutWeight(1)
Text('✕').onClick(() => { this.onDelete(r.id); })
}.width('100%')
Text(r.content).fontSize(13).fontColor(C.textSub).width('100%')
Text(fmtRelative(r.time)).fontSize(11).fontColor(C.textDim)
}
}
随着求职季推进,这条时间线会越来越长,回头看时能清楚感受到自己"面试能力在一次次复盘中不断被打磨"的过程——这和毕设伴侣里的"成长里程碑时间线"是同一套设计思路,都是在用最朴素的方式,让"成长"这件抽象的事情变得可视化、可回顾。
八、开发环境:在 HarmonyOS 6 全系设备上验证
这次开发依然是在最新的 HarmonyOS 6(API 23) 环境下完成。打开 DevEco Studio 的设备选择器,能看到从 Mate 80 Pro、Mate X7、Pura 90 到 MatePad Pro 13、MateBook Pro 的全系列 HarmonyOS 6.1.0 设备:

我最终选择 Mate 80 Pro(HarmonyOS 6.1.0) 作为主力调试设备。能在这么完整的一套设备矩阵下验证应用的运行效果,对一个还在学习阶段的开发者来说是很难得的资源——这也从侧面印证了鸿蒙生态这几年发展的速度。
九、AI 服务复用:架构经验的复利效应
这一节我想单独展开讲讲"复用"这件事,因为它是这次开发速度明显加快的核心原因。
9.1 几乎照搬的 AIService
对比「毕设伴侣」和「求职罗盘」的 AIService.ets,两者的多轮对话封装几乎是一模一样的结构:
export class AIService {
static async chat(history: ChatMessage[]): Promise<string> {
const messages: ChatMessage[] = [{ role: 'system', content: AIConfig.systemPrompt }];
for (const m of history) { messages.push(m); }
// ... 发起请求、超时控制、异常兜底、finally 释放资源
}
static async task(prompt: string): Promise<string> {
return AIService.chat([{ role: 'user', content: prompt }]);
}
}
唯一的差异只是 AIConfig.systemPrompt 里的人设描述——一个是"毕业设计指导老师",一个是"校招求职顾问兼技术面试官"。核心的网络请求逻辑、超时控制、异常兜底、资源释放,全部可以直接复用。
9.2 复用带来的真实收益
这次开发,从搭工程骨架到四个 Tab 全部功能跑通,明显比第一次做毕设伴侣快了不少。具体体现在:
- 架构分层不用重新设计:直接照搬"入口层不碰网络、AI 交互收敛在 model 层"的分层原则。
- AI 服务封装可以复制粘贴:
AIService.chat/task几乎原样搬过来,只改提示词。 - 交互模式可以复用:"任务模板+多轮追问"用在了简历顾问,"出题-作答-点评"用在了模拟面试,都是从毕设伴侣里搬过来的成熟模式。
- 踩过的坑不会再踩:
@Prop装饰器不能漏、自定义组件属性不能跟内置属性方法同名、build()方法只能有一个根容器……这些坑在第一个项目里踩过一次,这次写代码时会本能地避开。
这让我真切体会到一件事:技术成长不是线性的,而是有复利效应的。第一个项目投入的时间和踩坑成本,会在后续项目里持续兑现。这也是我想对同样在学鸿蒙的同学说的——不要怕做第一个项目慢,方法论和踩坑经验积累起来后,第二个、第三个项目会明显加速。
十、这次踩到的新坑:ArkUI 保留字与容器根节点
虽然大部分架构是复用的,但求职罗盘的开发过程也不是一帆风顺,编译时遇到了两个新的报错,记录下来。
10.1 自定义方法名与框架保留字冲突
Methods, properties and accessors in structures decorated by '@Component'
and '@ComponentV2' cannot have name as 'activeCount'.
我在 BoardTab 组件里写了一个统计"进行中投递数"的方法,随手命名为 activeCount,结果编译报错——activeCount 是 ArkUI 框架内部保留的名字(大概率与 Tabs/动画等内部活跃状态计数机制相关),不能被自定义组件的方法或属性使用。解决很简单,换个名字:
// 之前:activeCount() —— 与框架保留字冲突
private notFailedCount(): number {
return this.list.filter((a: JobApp) => a.stage !== '已挂').length;
}
这个坑告诉我一个经验:ArkUI 组件内部方法命名,最好避开一些"看起来很像框架内置概念"的通用词(如 active、state、current 这类),命名时稍微具体一点(比如 notFailedCount 而不是 activeCount),能降低撞上保留字的概率。
10.2 build() 方法的根容器规则
In an '@Entry' decorated component, the 'build' method can have only one
root node, which must be a container component.
我在「我的」页面想在 Scroll 滚动区域之外叠加一个条件渲染的底部弹窗(写复盘的表单),最初写成了这样:
// 错误写法:build() 里出现了两个平级根节点
build() {
Scroll() { /* ... */ }
if (this.showAdd) { this.addSheet() } // 和 Scroll 是平级的第二个根节点!
}
build() 方法的规则是有且仅有一个根容器组件,不能出现两个平级的顶层节点。修复方式是用 Stack 把两者包裹成一个整体:
build() {
Stack({ alignContent: Alignment.Bottom }) {
Scroll() { /* ... */ }
if (this.showAdd) { this.addSheet() } // 现在是 Stack 内部的子节点,合法
}
.width('100%').height('100%')
}
这个坑其实我在毕设伴侣的看板页里已经用 Stack 处理过一次(当时是为了叠加"新增任务"的底部弹窗),但这次写「我的」页时因为切换了心思专注在复盘日记的逻辑上,一时疏忽又漏掉了这个包裹。这提醒我,即使是已经验证过的模式,写新页面时也要对照检查一遍,不能凭记忆想当然。
十一、总结:求职是一场值得被认真记录的修行
回顾这个项目,它对我的意义和「鸿蒙毕设伴侣」一样——技术上是练手,情感上是陪伴。
从技术角度,这次开发验证了一件事:当你把一个项目的架构、方法论沉淀下来,第二个类似的项目会明显更快、更稳。从投递看板的状态机设计,到 AI 顾问的提示词调优,再到面试陪练的出题-点评闭环,几乎每一处都能看到上一个项目的影子,但又结合求职场景做了针对性调整。
从意义角度,这个应用陪我走过了求职季最焦虑的那段时间。它让我看到自己投递的全貌,不再被"记不清进度"的焦虑困扰;它帮我把简历里模糊的表达改得更专业;它让我在真正的面试前,先在自己的应用里练了一遍又一遍;它记录下每一次面试后的反思,让"这次没答好"不再是白白经历的挫折,而是变成了下一次的底气。
如果你也是一名正在求职的鸿蒙方向应届生,我想把这段经历里最真实的几点体会留给你:
- 求职的焦虑,往往来自"不确定",而"记录"是对抗不确定感最好的工具。 把投递进度、复盘心得都写下来,你会发现自己其实比想象中更有掌控感。
- 让 AI 帮你换个角度看自己。 简历也好,面试作答也好,我们对自己的表达往往有盲区,AI 的第三方视角能帮你更快发现问题。
- 技术积累是有复利的。 如果你也在做鸿蒙项目,不要怕第一个项目做得慢、踩坑多——那些经验会在你做第二个项目时兑现成实实在在的效率提升。
毕业季,鸿蒙同行。愿每一位正在投简历、赶面试的同学,都能把这段求职路走得踏实、走得心里有数,最终找到属于自己的方向。
如果你对这个项目、对鸿蒙开发、或者对求职这件事本身有任何想聊的,欢迎交流——我们都是这条路上的同行者。
更多推荐





所有评论(0)