一、引言密码是数字世界的第一道防线。一个强密码可以有效阻止暴力破解——8 位纯小写字母密码约需 2 秒破解而 16 位混合大小写 数字 符号的密码即使以每秒 10 亿次的尝试速度也需要数万亿年。这两者在使用体验上几乎没有区别都是复制粘贴但安全强度相差了 20 个数量级。为什么大多数用户不使用强密码因为自己想一个包含大小写数字符号的密码这件事本身就很痛苦。密码生成器解决了这个问题——它把构造强密码这个认知负担从用户转移到算法让用户只需点一个按钮就能得到一个安全的随机密码。从技术角度看密码生成器的核心挑战有三个第一字符池的动态组合。用户可以选择是否包含大写字母、小写字母、数字、特殊符号。每次生成时字符池是这四种选择的动态并集。如果四种都未选中需要保护——不能从空池中随机选取。第二密码强度评估。强度不是一个主观判断而是基于长度和字符多样性的客观公式。12 位纯数字 vs 12 位混合四种类型后者安全得多。强度评估需要即时反馈——用户调整参数的同时看到强度变化。第三每种字符类型至少命中一次保证。如果用户选择了大写字母密码中应该至少包含一个大写字母。纯随机从池中取字符可能恰好没取到某类字符需要在生成逻辑中保证每种选中类型至少出现一次。本文将用 ArkUI 从零构建一个密码生成器。功能包括四种字符类型切换大写 / 小写 / 数字 / 特殊符号、六档长度选择8-32 位、密码强度实时评估弱 / 中 / 强三档 颜色进度条、生成历史记录最多 5 条、一键生成。阅读完本文你将能够实现动态字符池的组合与随机选取使用 Fisher-Yates 洗牌算法打乱密码字符顺序设计密码强度评估的三维度模型长度 × 种类数 × 组合策略保证每种选中字符类型在密码中至少命中一次的生成策略二、密码生成的算法设计2.1 字符集定义四组字符覆盖了绝大多数网站的密码要求constUPPER:stringABCDEFGHIJKLMNOPQRSTUVWXYZ;// 26 个大写字母constLOWER:stringabcdefghijklmnopqrstuvwxyz;// 26 个小写字母constDIGITS:string0123456789;// 10 个数字constSYMBOLS:string!#$%^*()_-[]{}|;:,.?;// 26 个常用特殊符号总计 88 个可选字符。用户选择的每种类型会拼接到charPool中charPool():string{letpool;if(this.useUpper)poolUPPER;if(this.useLower)poolLOWER;if(this.useDigits)poolDIGITS;if(this.useSymbols)poolSYMBOLS;returnpool;}如果四种类型都选中字符池大小为 88。如果只选中小写字母字符池大小为 26。字符池大小直接决定了密码的搜索空间——破解者需要尝试的候选密码总数是poolSize ^ length。2.2 每种类型至少命中一次纯随机从字符池中取字符可能恰好没取到某类字符。例如用户选择了大写 小写 数字 符号但随机生成的 12 位密码中恰好没有符号。这会降低密码的实际安全性而且某些网站可能要求密码必须包含特殊字符。解决方案——先生成保底字符每类至少一个再用随机字符填充剩余长度最后打乱顺序generate():void{constpoolthis.charPool();if(pool.length0){this.password请至少选择一种字符类型;return;}constlenLENGTH_PRESETS[this.lengthIndex];letresult;// Step 1: 每类至少取一个字符consttypes:string[][];if(this.useUpper)types.push(UPPER);if(this.useLower)types.push(LOWER);if(this.useDigits)types.push(DIGITS);if(this.useSymbols)types.push(SYMBOLS);for(leti0;itypes.length;i){constcharstypes[i];resultchars[Math.floor(Math.random()*chars.length)];}// Step 2: 用字符池填充剩余长度while(result.lengthlen){resultpool[Math.floor(Math.random()*pool.length)];}// Step 3: 打乱顺序this.passwordthis.shuffleStr(result);}三个步骤的逻辑干净且高效保底 → 填充 → 打乱。顺序很重要——先保底确保每类至少一个字符如果先填充再保底可能破坏随机性。2.3 Fisher-Yates 洗牌算法步骤 3 的打乱使用 Fisher-Yates 洗牌算法Knuth ShuffleshuffleStr(s:string):string{constarrs.split();for(letiarr.length-1;i0;i--){constjMath.floor(Math.random()*(i1));consttmparr[i];arr[i]arr[j];arr[j]tmp;}returnarr.join();}Fisher-Yates 的核心从数组末尾开始每个位置与一个随机的前置位置包括自身交换。这个算法保证每个排列出现的概率相等均匀随机分布。时间复杂度 O(n)空间复杂度 O(n)split()创建新数组。对 32 位以下的密码长度性能完全不是问题。为什么不用sort(() Math.random() - 0.5)因为Array.sort比较器不是设计用于随机化的——它要求比较函数是确定性的相同输入永远返回相同结果而Math.random()不是。用 sort 洗牌会导致某些排列出现概率远高于其他排列不符合均匀随机的要求。2.4 历史记录管理生成新密码时当前密码如果有效被推入历史记录if(this.password.length0this.history.indexOf(this.password)-1){consth:string[][this.password,...this.history];if(h.length5)h.pop();this.historyh;}两项过滤去重this.history.indexOf(this.password) -1确保同一个密码不会重复存入历史。如果用户连续两次生成碰巧得到相同的密码概率极低但可能不会重复记录。上限最多保存 5 条历史。超过 5 条时删除最早的一条。历史过长会占据屏幕空间且用户不太可能需要 10 次以前的密码。this.history h触发State响应式更新历史记录区域立即刷新。三、密码强度评估3.1 三维度评估模型强度的评估不是简单的长就是强而是综合考虑三个维度维度影响示例密码长度最关键的因素8 位 vs 16 位 搜索空间多 88^8 倍字符种类数影响搜索空间的基数1 种 基数 264 种 基数 88组合方式是否每种至少一次保证策略让密码对所有字符类型免疫评估函数strengthLevel():number{constlenLENGTH_PRESETS[this.lengthIndex];consttypesthis.activeTypes();if(types0)return0;if(len16types3)return3;// 强长 多样if(len12types2)return2;// 中中等长度 至少两种return1;// 弱短或单一}三个等级的划分逻辑强等级 3长度 ≥ 16 且字符种类 ≥ 3。例如 16 位混合大小写 数字的密码搜索空间 62^16 ≈ 4.8×10^28暴力破解在可预见的时间内完全不可行。中等级 2长度 ≥ 12 且字符种类 ≥ 2。例如 12 位字母 数字搜索空间 36^12 ≈ 4.7×10^18专业破解设备需要数天到数月。弱等级 1其余所有情况。例如 8 位纯小写字母搜索空间 26^8 ≈ 2×10^11现代 GPU 集群可在几秒内暴力破解。3.2 强度可视化强度用文字标签 三格进度条展示Text(this.strengthLabel())// 弱 / 中 / 强.fontColor(this.strengthColor())// 红 / 蓝 / 绿ForEach([1,2,3],(level:number){Row().width(22).height(6).borderRadius(3).backgroundColor(this.strengthLevel()level?this.strengthColor():#E8E8EF)})三格进度条的工作原理等级 1弱第一格着色红色第二、三格灰色等级 2中第一、二格着色蓝色第三格灰色等级 3强三格全部着色绿色颜色的语义化映射红 危险/需要改进蓝 注意/正常绿 安全/最佳。用户无需阅读弱/中/强文字仅凭颜色就能判断密码质量。注意这里没有使用黄色——红 → 蓝 → 绿的渐变中蓝色替代了传统方案中的黄色位置。蓝色#1677FF与白色背景下配对的对比度约 4.3:1满足 WCAG 标准。3.3 实时评估强度评估在每次用户操作时重新计算——切换字符类型、改变长度、生成新密码都会触发strengthLevel()重新执行。原因是strengthLevel()是一个纯计算属性在 build() 中作为表达式使用ArkUI 的响应式系统会在State变量变化时自动重新执行。用户调整参数后看到的不是生成后的密码有多强而是用这些参数生成的密码大致有多强。这是一个预判——在生成之前就告知用户密码的质量帮助用户做出更好的参数选择。四、UI 设计4.1 整体布局PasswordPage ├── 深色标题栏52vp 密码生成器 生成按钮 ├── Scroll可滚动内容区 │ ├── 密码显示卡片白色大号等宽字体居中 │ ├── 强度指示器标签 三格进度条 │ ├── 长度选择区6 个预设值胶囊按钮 │ ├── 字符类型选择区4 行切换开关 │ └── 历史记录区最多 5 条每条可单独删除4.2 密码显示卡片生成的密码以大号等宽字体居中显示白底圆角卡片Text(this.password).fontSize(20).fontColor(#1a1a2e).fontWeight(FontWeight.Bold).fontFamily(monospace).width(100%).textAlign(TextAlign.Center).maxLines(3)monospace等宽字体的选择是有意为之——在等宽字体中每个字符占据相同的宽度用户更容易辨识密码中的相似字符如 I / l / 1 或 O / 0。在比例字体中Ill1三个字符的宽度可能非常接近难以区分。maxLines(3)限制密码最多显示 3 行。32 位密码在标准手机屏幕上通常需要换行约 1.5-2 行3 行的上限既保证了完整显示又防止了长密码占满整个屏幕。4.3 长度选择器六档预设长度覆盖了最常见的密码长度需求[ 8 ] [ 12 ] [ 16 ] [ 20 ] [ 24 ] [ 32 ]constLENGTH_PRESETS:number[][8,12,16,20,24,32];选取这几个数值的原因8许多网站的最低密码长度要求12推荐的正常安全性长度16推荐的高安全性长度20 / 24 / 32最高安全性适合银行账户、密码管理器主密码等场景每个按钮是圆角胶囊形选中态蓝色实心 白字未选中态灰色#F0F0F5。用户点击后立即生成新密码。4.4 字符类型切换行四行切换对应四种字符类型每行包含三个元素[A-Z] 大写字母 ● (蓝色实心) [a-z] 小写字母 ○ (灰色空心) [0-9] 数字 ● (蓝色实心) [!#] 特殊符号 ○ (灰色空心)使用Builder封装单行避免重复代码BuildertypeRow(symbol:string,name:string,value:boolean,type:string,showBorder:boolean){Row(){Text(symbol)// 左侧图标块44×44vp有/无蓝色背景Text(name)// 中间名称layoutWeight 占据剩余空间Text(value?●:○)// 右侧状态圆点蓝色实心 / 灰色空心}.onClick((){this.toggleType(type);})}整个行可点击——不需要精确点击右侧的圆点。这种宽泛的触控区域对移动端体验至关重要。showBorder控制是否显示底部分隔线。前三行之间有分隔线最后一行特殊符号没有——因为它是列表的最后一项下方没有需要分隔的内容。4.5 历史记录历史记录区域是一张白色卡片每条记录一行显示密码等宽字体灰色文字 右侧 × 删除按钮历史记录 ┌──────────────────────────────────┐ │ xK9#mP2vL7nQ4 × │ ├──────────────────────────────────┤ │ aB3$cD5%fG8h × │ ├──────────────────────────────────┤ │ Wq2#Rt9$Yu1P × │ └──────────────────────────────────┘ 清除全部历史每条记录可单独删除× 按钮底部有清除全部历史链接。历史记录使用不可变更新consth:string[][];for(leti0;ithis.history.length;i){if(i!hi)h.push(this.history[i]);}this.historyh;历史记录为空时不显示此区域——用户可能不希望看到空的历史标题。五、完整代码结构PasswordPage ├── Row标题栏 密码生成器 生成按钮 ├── Scroll内容区 │ ├── Column密码显示卡片白底圆角 │ ├── Row强度指示器标签 文字 三格进度条 │ ├── Text Row长度选择6 个胶囊按钮 │ ├── Text Column字符类型4 行 Builder typeRow │ └── if 有历史 → Column历史记录卡片 │ ├── ForEach → Row密码 删除按钮 │ └── Text清除全部历史 └── 方法 ├── charPool() — 动态字符池 ├── activeTypes() — 已选类型计数 ├── strengthLevel() — 强度等级0-3 ├── shuffleStr() — Fisher-Yates 洗牌 ├── generate() — 生成密码保底 填充 打乱 └── toggleType() — 切换字符类型六、总结本文从零构建了一个密码生成器。与前八篇的数据管理类和工具类应用不同密码生成器的核心是随机性生成 安全评估——没有持久化数据没有复杂列表只有字符池组合、随机选取、洗牌算法和强度评估。核心要点回顾动态字符池四组字符大写 26 小写 26 数字 10 符号 26 88 个字符根据用户选择动态拼接。charPool()返回当前有效的字符集activeTypes()提供种类计数。三种字符类型至少命中一次生成策略分三步——先从每种选中类型中随机取一个字符保底再用字符池随机填充剩余长度最后用 Fisher-Yates 洗牌打乱顺序。这保证了密码对每种字符类型的覆盖。Fisher-Yates 洗牌O(n) 时间复杂度均匀随机分布。不用sort(() Math.random() - 0.5)的原因是其分布不均匀且不符合 sort 比较器的语义契约。三维强度评估长度最关键的维度× 字符种类数 × 组合方式。等级 1弱 短或单一等级 2中 ≥12 位 ≥2 种字符等级 3强 ≥16 位 ≥3 种字符。三格进度条 颜色标签红/蓝/绿提供即时视觉反馈。历史记录去重上限最多 5 条indexOf去重防止重复记录。每条可单独删除支持一键清除全部。使用不可变更新模式管理历史数组。等宽字体的密码显示fontFamily(monospace)让每个字符占据相同宽度帮助用户区分 I/l/1 和 O/0 等易混淆字符。密码生成器是一个小而精的工具——一个字符池、一次洗牌、一次强度评估三件事组成一个完整的密码安全助手。它不产生数据不管理状态除了历史记录但它是数字安全工具箱中最基础也最实用的工具。
鸿蒙原生开发——从零构建密码生成器
一、引言密码是数字世界的第一道防线。一个强密码可以有效阻止暴力破解——8 位纯小写字母密码约需 2 秒破解而 16 位混合大小写 数字 符号的密码即使以每秒 10 亿次的尝试速度也需要数万亿年。这两者在使用体验上几乎没有区别都是复制粘贴但安全强度相差了 20 个数量级。为什么大多数用户不使用强密码因为自己想一个包含大小写数字符号的密码这件事本身就很痛苦。密码生成器解决了这个问题——它把构造强密码这个认知负担从用户转移到算法让用户只需点一个按钮就能得到一个安全的随机密码。从技术角度看密码生成器的核心挑战有三个第一字符池的动态组合。用户可以选择是否包含大写字母、小写字母、数字、特殊符号。每次生成时字符池是这四种选择的动态并集。如果四种都未选中需要保护——不能从空池中随机选取。第二密码强度评估。强度不是一个主观判断而是基于长度和字符多样性的客观公式。12 位纯数字 vs 12 位混合四种类型后者安全得多。强度评估需要即时反馈——用户调整参数的同时看到强度变化。第三每种字符类型至少命中一次保证。如果用户选择了大写字母密码中应该至少包含一个大写字母。纯随机从池中取字符可能恰好没取到某类字符需要在生成逻辑中保证每种选中类型至少出现一次。本文将用 ArkUI 从零构建一个密码生成器。功能包括四种字符类型切换大写 / 小写 / 数字 / 特殊符号、六档长度选择8-32 位、密码强度实时评估弱 / 中 / 强三档 颜色进度条、生成历史记录最多 5 条、一键生成。阅读完本文你将能够实现动态字符池的组合与随机选取使用 Fisher-Yates 洗牌算法打乱密码字符顺序设计密码强度评估的三维度模型长度 × 种类数 × 组合策略保证每种选中字符类型在密码中至少命中一次的生成策略二、密码生成的算法设计2.1 字符集定义四组字符覆盖了绝大多数网站的密码要求constUPPER:stringABCDEFGHIJKLMNOPQRSTUVWXYZ;// 26 个大写字母constLOWER:stringabcdefghijklmnopqrstuvwxyz;// 26 个小写字母constDIGITS:string0123456789;// 10 个数字constSYMBOLS:string!#$%^*()_-[]{}|;:,.?;// 26 个常用特殊符号总计 88 个可选字符。用户选择的每种类型会拼接到charPool中charPool():string{letpool;if(this.useUpper)poolUPPER;if(this.useLower)poolLOWER;if(this.useDigits)poolDIGITS;if(this.useSymbols)poolSYMBOLS;returnpool;}如果四种类型都选中字符池大小为 88。如果只选中小写字母字符池大小为 26。字符池大小直接决定了密码的搜索空间——破解者需要尝试的候选密码总数是poolSize ^ length。2.2 每种类型至少命中一次纯随机从字符池中取字符可能恰好没取到某类字符。例如用户选择了大写 小写 数字 符号但随机生成的 12 位密码中恰好没有符号。这会降低密码的实际安全性而且某些网站可能要求密码必须包含特殊字符。解决方案——先生成保底字符每类至少一个再用随机字符填充剩余长度最后打乱顺序generate():void{constpoolthis.charPool();if(pool.length0){this.password请至少选择一种字符类型;return;}constlenLENGTH_PRESETS[this.lengthIndex];letresult;// Step 1: 每类至少取一个字符consttypes:string[][];if(this.useUpper)types.push(UPPER);if(this.useLower)types.push(LOWER);if(this.useDigits)types.push(DIGITS);if(this.useSymbols)types.push(SYMBOLS);for(leti0;itypes.length;i){constcharstypes[i];resultchars[Math.floor(Math.random()*chars.length)];}// Step 2: 用字符池填充剩余长度while(result.lengthlen){resultpool[Math.floor(Math.random()*pool.length)];}// Step 3: 打乱顺序this.passwordthis.shuffleStr(result);}三个步骤的逻辑干净且高效保底 → 填充 → 打乱。顺序很重要——先保底确保每类至少一个字符如果先填充再保底可能破坏随机性。2.3 Fisher-Yates 洗牌算法步骤 3 的打乱使用 Fisher-Yates 洗牌算法Knuth ShuffleshuffleStr(s:string):string{constarrs.split();for(letiarr.length-1;i0;i--){constjMath.floor(Math.random()*(i1));consttmparr[i];arr[i]arr[j];arr[j]tmp;}returnarr.join();}Fisher-Yates 的核心从数组末尾开始每个位置与一个随机的前置位置包括自身交换。这个算法保证每个排列出现的概率相等均匀随机分布。时间复杂度 O(n)空间复杂度 O(n)split()创建新数组。对 32 位以下的密码长度性能完全不是问题。为什么不用sort(() Math.random() - 0.5)因为Array.sort比较器不是设计用于随机化的——它要求比较函数是确定性的相同输入永远返回相同结果而Math.random()不是。用 sort 洗牌会导致某些排列出现概率远高于其他排列不符合均匀随机的要求。2.4 历史记录管理生成新密码时当前密码如果有效被推入历史记录if(this.password.length0this.history.indexOf(this.password)-1){consth:string[][this.password,...this.history];if(h.length5)h.pop();this.historyh;}两项过滤去重this.history.indexOf(this.password) -1确保同一个密码不会重复存入历史。如果用户连续两次生成碰巧得到相同的密码概率极低但可能不会重复记录。上限最多保存 5 条历史。超过 5 条时删除最早的一条。历史过长会占据屏幕空间且用户不太可能需要 10 次以前的密码。this.history h触发State响应式更新历史记录区域立即刷新。三、密码强度评估3.1 三维度评估模型强度的评估不是简单的长就是强而是综合考虑三个维度维度影响示例密码长度最关键的因素8 位 vs 16 位 搜索空间多 88^8 倍字符种类数影响搜索空间的基数1 种 基数 264 种 基数 88组合方式是否每种至少一次保证策略让密码对所有字符类型免疫评估函数strengthLevel():number{constlenLENGTH_PRESETS[this.lengthIndex];consttypesthis.activeTypes();if(types0)return0;if(len16types3)return3;// 强长 多样if(len12types2)return2;// 中中等长度 至少两种return1;// 弱短或单一}三个等级的划分逻辑强等级 3长度 ≥ 16 且字符种类 ≥ 3。例如 16 位混合大小写 数字的密码搜索空间 62^16 ≈ 4.8×10^28暴力破解在可预见的时间内完全不可行。中等级 2长度 ≥ 12 且字符种类 ≥ 2。例如 12 位字母 数字搜索空间 36^12 ≈ 4.7×10^18专业破解设备需要数天到数月。弱等级 1其余所有情况。例如 8 位纯小写字母搜索空间 26^8 ≈ 2×10^11现代 GPU 集群可在几秒内暴力破解。3.2 强度可视化强度用文字标签 三格进度条展示Text(this.strengthLabel())// 弱 / 中 / 强.fontColor(this.strengthColor())// 红 / 蓝 / 绿ForEach([1,2,3],(level:number){Row().width(22).height(6).borderRadius(3).backgroundColor(this.strengthLevel()level?this.strengthColor():#E8E8EF)})三格进度条的工作原理等级 1弱第一格着色红色第二、三格灰色等级 2中第一、二格着色蓝色第三格灰色等级 3强三格全部着色绿色颜色的语义化映射红 危险/需要改进蓝 注意/正常绿 安全/最佳。用户无需阅读弱/中/强文字仅凭颜色就能判断密码质量。注意这里没有使用黄色——红 → 蓝 → 绿的渐变中蓝色替代了传统方案中的黄色位置。蓝色#1677FF与白色背景下配对的对比度约 4.3:1满足 WCAG 标准。3.3 实时评估强度评估在每次用户操作时重新计算——切换字符类型、改变长度、生成新密码都会触发strengthLevel()重新执行。原因是strengthLevel()是一个纯计算属性在 build() 中作为表达式使用ArkUI 的响应式系统会在State变量变化时自动重新执行。用户调整参数后看到的不是生成后的密码有多强而是用这些参数生成的密码大致有多强。这是一个预判——在生成之前就告知用户密码的质量帮助用户做出更好的参数选择。四、UI 设计4.1 整体布局PasswordPage ├── 深色标题栏52vp 密码生成器 生成按钮 ├── Scroll可滚动内容区 │ ├── 密码显示卡片白色大号等宽字体居中 │ ├── 强度指示器标签 三格进度条 │ ├── 长度选择区6 个预设值胶囊按钮 │ ├── 字符类型选择区4 行切换开关 │ └── 历史记录区最多 5 条每条可单独删除4.2 密码显示卡片生成的密码以大号等宽字体居中显示白底圆角卡片Text(this.password).fontSize(20).fontColor(#1a1a2e).fontWeight(FontWeight.Bold).fontFamily(monospace).width(100%).textAlign(TextAlign.Center).maxLines(3)monospace等宽字体的选择是有意为之——在等宽字体中每个字符占据相同的宽度用户更容易辨识密码中的相似字符如 I / l / 1 或 O / 0。在比例字体中Ill1三个字符的宽度可能非常接近难以区分。maxLines(3)限制密码最多显示 3 行。32 位密码在标准手机屏幕上通常需要换行约 1.5-2 行3 行的上限既保证了完整显示又防止了长密码占满整个屏幕。4.3 长度选择器六档预设长度覆盖了最常见的密码长度需求[ 8 ] [ 12 ] [ 16 ] [ 20 ] [ 24 ] [ 32 ]constLENGTH_PRESETS:number[][8,12,16,20,24,32];选取这几个数值的原因8许多网站的最低密码长度要求12推荐的正常安全性长度16推荐的高安全性长度20 / 24 / 32最高安全性适合银行账户、密码管理器主密码等场景每个按钮是圆角胶囊形选中态蓝色实心 白字未选中态灰色#F0F0F5。用户点击后立即生成新密码。4.4 字符类型切换行四行切换对应四种字符类型每行包含三个元素[A-Z] 大写字母 ● (蓝色实心) [a-z] 小写字母 ○ (灰色空心) [0-9] 数字 ● (蓝色实心) [!#] 特殊符号 ○ (灰色空心)使用Builder封装单行避免重复代码BuildertypeRow(symbol:string,name:string,value:boolean,type:string,showBorder:boolean){Row(){Text(symbol)// 左侧图标块44×44vp有/无蓝色背景Text(name)// 中间名称layoutWeight 占据剩余空间Text(value?●:○)// 右侧状态圆点蓝色实心 / 灰色空心}.onClick((){this.toggleType(type);})}整个行可点击——不需要精确点击右侧的圆点。这种宽泛的触控区域对移动端体验至关重要。showBorder控制是否显示底部分隔线。前三行之间有分隔线最后一行特殊符号没有——因为它是列表的最后一项下方没有需要分隔的内容。4.5 历史记录历史记录区域是一张白色卡片每条记录一行显示密码等宽字体灰色文字 右侧 × 删除按钮历史记录 ┌──────────────────────────────────┐ │ xK9#mP2vL7nQ4 × │ ├──────────────────────────────────┤ │ aB3$cD5%fG8h × │ ├──────────────────────────────────┤ │ Wq2#Rt9$Yu1P × │ └──────────────────────────────────┘ 清除全部历史每条记录可单独删除× 按钮底部有清除全部历史链接。历史记录使用不可变更新consth:string[][];for(leti0;ithis.history.length;i){if(i!hi)h.push(this.history[i]);}this.historyh;历史记录为空时不显示此区域——用户可能不希望看到空的历史标题。五、完整代码结构PasswordPage ├── Row标题栏 密码生成器 生成按钮 ├── Scroll内容区 │ ├── Column密码显示卡片白底圆角 │ ├── Row强度指示器标签 文字 三格进度条 │ ├── Text Row长度选择6 个胶囊按钮 │ ├── Text Column字符类型4 行 Builder typeRow │ └── if 有历史 → Column历史记录卡片 │ ├── ForEach → Row密码 删除按钮 │ └── Text清除全部历史 └── 方法 ├── charPool() — 动态字符池 ├── activeTypes() — 已选类型计数 ├── strengthLevel() — 强度等级0-3 ├── shuffleStr() — Fisher-Yates 洗牌 ├── generate() — 生成密码保底 填充 打乱 └── toggleType() — 切换字符类型六、总结本文从零构建了一个密码生成器。与前八篇的数据管理类和工具类应用不同密码生成器的核心是随机性生成 安全评估——没有持久化数据没有复杂列表只有字符池组合、随机选取、洗牌算法和强度评估。核心要点回顾动态字符池四组字符大写 26 小写 26 数字 10 符号 26 88 个字符根据用户选择动态拼接。charPool()返回当前有效的字符集activeTypes()提供种类计数。三种字符类型至少命中一次生成策略分三步——先从每种选中类型中随机取一个字符保底再用字符池随机填充剩余长度最后用 Fisher-Yates 洗牌打乱顺序。这保证了密码对每种字符类型的覆盖。Fisher-Yates 洗牌O(n) 时间复杂度均匀随机分布。不用sort(() Math.random() - 0.5)的原因是其分布不均匀且不符合 sort 比较器的语义契约。三维强度评估长度最关键的维度× 字符种类数 × 组合方式。等级 1弱 短或单一等级 2中 ≥12 位 ≥2 种字符等级 3强 ≥16 位 ≥3 种字符。三格进度条 颜色标签红/蓝/绿提供即时视觉反馈。历史记录去重上限最多 5 条indexOf去重防止重复记录。每条可单独删除支持一键清除全部。使用不可变更新模式管理历史数组。等宽字体的密码显示fontFamily(monospace)让每个字符占据相同宽度帮助用户区分 I/l/1 和 O/0 等易混淆字符。密码生成器是一个小而精的工具——一个字符池、一次洗牌、一次强度评估三件事组成一个完整的密码安全助手。它不产生数据不管理状态除了历史记录但它是数字安全工具箱中最基础也最实用的工具。