码农日常:花一下午撸了个密码生成器,顺便学了鸿蒙开发
码农日常:花一下午撸了个密码生成器,顺便学了鸿蒙开发
周末闲着没事,寻思着学点新东西。正好最近鸿蒙挺火的,就想着搞个实战项目练练手。目标很简单:一个密码生成器。别看功能不复杂,但五脏俱全,从UI到逻辑到交互,该有的都有。
这篇文章就当是我的开发笔记,记录一下整个过程。
一、为啥选密码生成器?
选项目就像选对象,得看对眼。
考虑过几个方向:
- 记账App?太复杂,数据库那一套够喝一壶的
- 天气预报?需要调API,网络请求还不太熟
- 计算器?太简单,学不到啥东西
最后选定密码生成器,原因有三:
- 功能聚焦:核心就一个——生成随机密码
- 技术点全:状态管理、UI布局、用户交互都有
- 实用性强:自己也能用,不完全是玩具
而且我还有个私心:朋友的账号被盗了,原因就是密码太简单。所以这玩意儿还真有市场需求。
二、开发环境搭建
2.1 安装DevEco Studio
到华为开发者官网下载DevEco Studio,安装过程就不废话了,一路Next就行。
有个坑要提醒:一定要选对API版本!
HarmonyOS NEXT是API 12+,别用老版本。我一开始选了API 10,结果发现好多新特性用不了,又重新创建项目。
2.2 创建项目
选择 Empty Ability 模板,这就是个空项目,啥都没有,正好从头开始写。
项目配置:
- Project name: MyApplication
- Bundle name: com.example.myapplication
- Compile SDK: API 12
- Model: Stage
2.3 项目结构初探
项目创建完成后,目录结构是这样的:
MyApplication/
├── AppScope/ # 全局配置
│ └── app.json5 # 应用配置
├── entry/ # 主模块
│ └── src/main/
│ ├── ets/ # 源码目录
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 入口
│ │ └── pages/
│ │ └── Index.ets # 主页面 ⭐
│ └── resources/ # 资源文件
└── build-profile.json5 # 构建配置
说实话,一开始看着挺懵的,一堆配置文件。但写着写着就明白了,其实就两个核心文件:
EntryAbility.ets:应用入口,生命周期管理Index.ets:主页面,所有UI和逻辑都在这
三、UI设计:丑是丑了点,但能用
我不是设计师,审美也就那样。所以UI设计遵循一个原则:简洁、实用。
3.1 布局结构
整体采用纵向布局,从上到下:
┌────────────────────────┐
│ 🔐 密码生成器 │ 标题
│ 生成高强度随机密码 │
├────────────────────────┤
│ [密码显示区] │ 结果
│ [强度指示] │
├────────────────────────┤
│ 密码长度 │
│ [-] 12 [+] │ 长度控制
├────────────────────────┤
│ 字符类型 │
│ A-Z [开关] │
│ a-z [开关] │ 类型选择
│ 0-9 [开关] │
│ !@#$% [开关] │
├────────────────────────┤
│ [生成密码] │ 按钮
│ [复制密码] │
└────────────────────────┘
3.2 标题实现
代码很简单:
Text('🔐 密码生成器')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#1C1C1E')
.margin({ top: 30, bottom: 4 })
Text('生成高强度随机密码')
.fontSize(14)
.fontColor('#8E8E93')
.margin({ bottom: 24 })
这里有个小技巧:emoji可以直接放在字符串里,会自动渲染成图标。🔐这个锁的emoji就挺合适的。
3.3 密码显示区
这是整个应用的核心视觉区域,我用卡片样式:
Column() {
Text(this.password)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
.width('100%')
// 强度指示条
if (this.password !== '点击生成' && !this.password.startsWith('⚠️')) {
Row() {
Row() {
Text(this.getStrengthText())
.fontSize(12)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
}
.width(this.getStrengthWidth() + 20)
.height(22)
.backgroundColor(this.getStrengthColor())
.borderRadius(11)
.justifyContent(FlexAlign.Center)
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.padding(20)
.backgroundColor('#F2F2F7')
.borderRadius(16)
注意那个条件渲染:if (this.password !== '点击生成' && !this.password.startsWith('⚠️'))
只有当密码是有效值时,才显示强度指示条。这个细节一开始没注意,结果"点击生成"几个字下面也跟着个强度条,看着很蠢。
3.4 长度调节
用两个圆形按钮加中间的数字显示:
Row() {
Button('-')
.type(ButtonType.Circle)
.width(36)
.height(36)
.backgroundColor('#007AFF')
.onClick(() => {
if (this.passwordLength > 4) this.passwordLength--;
})
Text(`${this.passwordLength}`)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.width(60)
Button('+')
.type(ButtonType.Circle)
.width(36)
.height(36)
.backgroundColor('#007AFF')
.onClick(() => {
if (this.passwordLength < 24) this.passwordLength++;
})
}
.justifyContent(FlexAlign.Center)
这里有个交互逻辑:长度限制在4-24之间,不能无限加减。
3.5 字符类型选择
用Toggle组件实现开关效果:
Column({ space: 10 }) {
Row() {
Text('A-Z').fontSize(16)
Blank()
Toggle({ type: ToggleType.Switch, isOn: this.hasUpper })
.selectedColor('#34C759')
.onChange((value: boolean) => {
this.hasUpper = value;
})
}.width('100%')
// ... 其他类型类似
}
.padding(16)
.backgroundColor('#F2F2F7')
.borderRadius(16)
Blank()是个很有用的组件,它会自动填充剩余空间,把后面的元素推到右边。一开始我不知道这个组件,用了很多嵌套Row才实现同样的效果,代码又臭又长。
3.6 操作按钮
两个按钮,一个生成,一个复制:
Button('🔄 生成密码')
.type(ButtonType.Capsule)
.width(240)
.height(52)
.backgroundColor('#007AFF')
.onClick(() => {
this.generatePassword();
})
Button('📋 复制密码')
.type(ButtonType.Capsule)
.width(240)
.height(44)
.backgroundColor('#34C759')
.margin({ top: 10 })
.onClick(() => {
this.copyPassword();
})
四、核心逻辑:算法其实很简单
4.1 密码生成算法
说穿了就三步:
- 把用户选择的字符类型拼成一个字符串(字符池)
- 随机从字符池中取字符
- 重复N次,得到N位密码
代码实现:
generatePassword(): void {
// 第一步:组装字符池
let chars: string = '';
if (this.hasUpper) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (this.hasLower) chars += 'abcdefghijklmnopqrstuvwxyz';
if (this.hasNumber) chars += '0123456789';
if (this.hasSymbol) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
// 第二步:校验(重要!)
if (chars.length === 0) {
this.password = '⚠️ 请选择至少一种字符类型';
return;
}
// 第三步:随机生成
let result: string = '';
for (let i: number = 0; i < this.passwordLength; i++) {
const randomIndex: number = Math.floor(Math.random() * chars.length);
result += chars[randomIndex];
}
this.password = result;
}
踩坑提醒:第二步的校验非常重要!
一开始我没加这个校验,结果用户把所有开关都关掉后,点击生成直接崩溃。因为字符池为空,chars.length是0,取模运算会出问题。
4.2 密码强度检测
这个规则是我自己定的,参考了一些安全规范:
| 强度 | 条件 | 颜色 |
|---|---|---|
| 强 | 长度≥16 且 包含≥3种字符类型 | 🟢 绿色 |
| 中 | 长度≥10 且 包含≥2种字符类型 | 🟠 橙色 |
| 弱 | 其他情况 | 🔴 红色 |
代码:
getStrengthLevel(): string {
let types: number = 0;
if (this.hasUpper) types++;
if (this.hasLower) types++;
if (this.hasNumber) types++;
if (this.hasSymbol) types++;
if (this.passwordLength >= 16 && types >= 3) return 'strong';
if (this.passwordLength >= 10 && types >= 2) return 'medium';
return 'weak';
}
4.3 复制功能
这个功能其实有点"伪",因为模拟器的剪贴板不一定能用。但我还是做了个提示效果:
copyPassword(): void {
if (this.password === '' ||
this.password === '点击生成' ||
this.password.startsWith('⚠️')) {
return;
}
this.showCopyTip = true;
// 2秒后隐藏提示
const timer = setTimeout(() => {
this.showCopyTip = false;
clearTimeout(timer);
}, 2000);
}
真要做剪贴板功能,需要用@ohos.pasteboard模块,这个在真机上才能正常工作。
五、状态管理:@State是核心
ArkTS的状态管理真的很方便,@State装饰器一加,变量变化时UI自动更新。
我的状态变量:
@State password: string = '点击生成'; // 生成的密码
@State passwordLength: number = 12; // 密码长度
@State hasUpper: boolean = true; // 大写开关
@State hasLower: boolean = true; // 小写开关
@State hasNumber: boolean = true; // 数字开关
@State hasSymbol: boolean = false; // 符号开关
@State showCopyTip: boolean = false; // 复制提示
这些变量一变化,UI立刻响应。比如点击Toggle开关,hasUpper从true变成false,对应的UI状态马上更新。
这就是声明式UI的好处:你只管改数据,UI自己会跟上。
六、调试过程:踩坑无数
坑1:Toggle状态不同步
现象:Toggle开关显示的状态和实际值不一致。
原因:onChange回调里忘了更新状态变量。
解决:
Toggle({ type: ToggleType.Switch, isOn: this.hasUpper })
.onChange((value: boolean) => {
this.hasUpper = value; // 必须加上这行
})
坑2:强度指示条不显示
现象:生成密码后,强度指示条有时出现有时不出现。
原因:条件判断不完整。
解决:加上完整的判断条件:
if (this.password !== '点击生成' && !this.password.startsWith('⚠️')) {
// 渲染强度指示条
}
坑3:按钮样式不一致
现象:两个按钮,一个圆角一个方形,看着很别扭。
原因:没有显式指定type属性。
解决:
Button('生成密码')
.type(ButtonType.Capsule) // 统一用胶囊样式
坑4:字符池为空崩溃
现象:关闭所有开关后点击生成,程序崩溃。
原因:字符池为空,随机数生成失败。
解决:添加校验:
if (chars.length === 0) {
this.password = '⚠️ 请选择至少一种字符类型';
return;
}
七、运行效果展示

八、总结:学到了啥
技术层面
- ArkTS语法:和TypeScript基本一样,上手很快
- 声明式UI:改数据就完事了,UI自动更新
- 组件使用:Column、Row、Button、Toggle这些基础组件玩明白了
- 状态管理:
@State是核心,理解了响应式编程的思想
项目经验
- 从小项目开始:别一上来就想做大而全的应用
- 边做边学:遇到问题再查文档,效率更高
- 多写多练:看再多教程都不如自己动手写一遍
- 注意细节:空值校验、条件判断这些细节很容易被忽略
代码量
最后统计了一下,核心代码大概200行左右(Index.ets单文件)。对于一个完整的应用来说,这个量级刚刚好——既不太简单显得敷衍,也不太复杂让人望而却步。
九、后续改进方向
应用虽然完成了,但还有很多可以改进的地方:
- 密码历史:保存生成过的密码,方便查看
- 自定义符号集:让用户自己选择要包含的特殊字符
- 真随机数:用加密API生成更安全的随机数
- 暗黑模式:适配系统深色主题
- 密码规则预设:提供"银行级"、"网站级"等一键配置
这些功能留着以后慢慢迭代吧,先把基础打牢。
写在最后
一下午的时间,从零开始撸了个密码生成器。虽然功能简单,但麻雀虽小五脏俱全,整个开发流程都走了一遍。
最大的感受是:鸿蒙开发的门槛其实没想象中那么高。ArkTS语法和TypeScript几乎一样,会前端的同学上手很快。声明式UI也比传统的命令式UI好理解,数据驱动视图的思想很直观。
如果你也想学鸿蒙开发,建议:
- 从小项目开始,别贪大
- 多看官方文档,它是最权威的
- 遇到问题多搜、多问
- 坚持写代码,保持手感
希望这篇流水账能给你一些参考。有问题欢迎评论区交流!
开发时间:约4小时
代码行数:约200行
难度等级:⭐⭐(适合新手)
更多推荐


所有评论(0)