鸿蒙ArkUI实现单格单字符输入框:验证码/手机号优雅方案

1. 项目概述

1.1 为什么需要「单格单字符」输入框

做鸿蒙应用开发时,手机号、验证码这类输入场景是最常见的需求之一。但很多新手甚至有一定经验的开发者,第一反应都是直接用TextInput配合maxLength限制长度,再套一层样式就完事。这种方案在功能上没毛病,可在实际用户体验上,和主流App里那种“一格一字带下划线”的输入框差距很大。

原因很简单:手机号输入时,用户希望知道自己输到第几位了;验证码输入时,用户期望是“一格一字”的视觉反馈,每个数字独立占位,底部带下划线,输完自动跳到下一格。原生TextInput做不到这种体验,因为它是“一段连续文本”,而不是“N个独立字符格子”。所以需要我们自己用ArkUI的组件组合去实现。

这个项目就是我在鸿蒙应用里实现该交互的完整记录。最终效果是:屏幕上横向排列N个格子,每个格子底部有一条下划线,用户输入时,字符逐个填充到格子中,并且光标自动“跳格”,支持删除键回退。整个实现不依赖第三方库,全部用ArkUI声明式语法完成,核心代码量控制在200行以内。

1.2 方案适用范围

如果你正在做以下类型的页面,这个方案可以直接复用:

  • 手机号/邮箱/身份证等固定位数信息输入
  • 短信/语音验证码输入(4位、6位最常用)
  • 密码“单格圆点”输入(稍改渲染逻辑即可)
  • 任何需要“逐字符反馈”的输入场景

适合的读者:刚接触鸿蒙ArkUI的初级开发者、想优化表单输入体验的中级开发者、以及需要在项目中快速落地该组件的工程师。

1.3 关键技术点一览

在动手写代码前,先明确这个方案需要解决的几个核心问题:

  1. 布局问题:如何把N个格子横向等分排列,且自适应不同屏幕宽度。
  2. 输入焦点问题:如何保证“点击任意格子”都能唤起输入法,且输入框始终处于激活状态。
  3. 字符映射问题:如何把输入框里的连续字符串,拆分成单个字符并填充到对应格子中。
  4. 交互细节:删除时如何回退、超过位数时如何限制、格式校验如何处理。

下面逐个拆解。

2. 整体设计与思路拆解

2.1 核心设计思想:隐藏输入框 + 渲染格子

这个功能最巧妙的点在于:用户看到的格子不是真正的输入框,真正的输入框被隐藏了。

我用了一个几乎不可见的TextInput覆盖在整个格子区域上方,用户点击时,实际点中的是这个透明的TextInput,输入法正常弹出,字符正常接收。与此同时,通过onChange回调实时读取输入框内容,把每个字符拆出来,填充到下方渲染的N个格子中。

为什么不用N个独立的TextInput?说实话我也试过,那是第一个踩坑版本:每个格子单独一个输入框,onFocus事件里手动把焦点切到下一个。但实测下来问题很多,比如:

  • 焦点切换时机难以控制,字符还没输入就被切走,体验很差。
  • 软键盘弹出时,多个输入框的focus状态难以管理。
  • 删除键在跨输入框回退时行为不一致,有的输入法直接不触发事件。

所以后来参考了一些成熟方案,改成“单个隐藏输入框 + 状态驱动渲染”的思路,所有问题迎刃而解。这也是ArkUI声明式开发的核心优势:UI是状态的下游产物,只要状态正确,UI自动正确。

2.2 为什么选择 Column + Row + Divider 组合

具体到格子渲染,ArkUI里能用来做“下划线”的组件不少,比如Divider、Border,甚至Canvas。我最终选择的是:每个格子用Column包一个Text,底部用Divider绘制下划线。

选择这个组合的理由:

  • Column天然支持纵向排列:上面显示字符,下面显示线,结构清晰。
  • Divider是独立的线条组件,可以单独控制颜色、粗细、间距,选中态和默认态切换时只需要改strokeWidth和color,不用动外层布局。
  • 如果用Border,虽然也能画底部线,但边框属于组件自身,想单独改某一边的粗细、颜色,还得自己写Border的EdgeWidths和EdgeColors,代码量反而更多。

从状态管理的角度看,每个格子只需要两个状态:char(该格子的字符)和isActive(当前是否处于输入焦点位置)。这两个状态直接映射到UI:字符为空时格子显示占位符(比如一个浅色短横),字符非空时显示数字(或密码圆点);isActive为真时下划线高亮加粗,否则保持灰色细线。

2.3 状态数据结构设计

既然是ArkUI声明式开发,第一步就是定义状态。我定义的数据结构如下:

@State inputValue: string = '' // 隐藏输入框的真实值 @State codeArray: string[] = [] // 每个格子的字符,初始为空字符串数组 @State activeIndex: number = 0 // 当前高亮的格子下标,同时也是“光标位置”

inputValue是隐藏输入框的绑定值,一切输入都经过它;codeArray长度等于格子总数,初始全部是空字符串;activeIndex表示当前输入焦点对应的格子位置,控制下划线的高亮显示。

他们三者之间的关系是这样的:

  • 用户输入字符 →inputValue变化 →onChange触发 → 更新codeArray→ 格子渲染变化
  • 用户删除字符 →inputValue变化 →onChange触发 →codeArray同步减少一个字符 → 格子回退

这里有个非常有用的细节:activeIndex不需要单独维护“当前是第几格”,因为它永远等于codeArray中最后一个非空字符的下标加1。换句话说,只要统计codeArray里非空元素的数量,就知道光标停在哪。我后续的代码就是基于这个逻辑。

3. 核心细节解析与实操要点

3.1 两套视觉状态:默认态与激活态

每一个格子都有两套视觉状态。默认态是灰色下划线、字符灰色;激活态是主题色下划线加粗,字符变黑。这种视觉反馈让用户知道自己正在输入哪个位置,对验证码这类短输入尤其重要。

实现时我给Column绑定了一个状态判断:

Button() // 伪代码思路,实际不是按钮 Column({ space: 4 }) { Text(this.getCharText(index)) .fontSize(28) .fontWeight(FontWeight.Medium) .fontColor(this.activeIndex === index ? '#1A1A1A' : '#999999') Divider() .strokeWidth(this.activeIndex === index ? 3 : 1.5) .color(this.activeIndex === index ? '#007AFF' : '#D0D0D0') }

这里有个细节:fontColor和strokeWidth的变化如果太突兀,会显得很生硬,但验证码输入框本身就是“瞬间切换”的交互节奏,不需要动画。如果你想要平滑过渡,可以给这两个属性加.animation(),我在某些项目里试过,0.15秒的缓动效果最好,时间长了反而拖沓。

3.2 隐藏输入框的三个关键设置

隐藏输入框是整个方案的灵魂,但隐藏不等于“消失”。它必须满足三个条件:

  1. 尺寸要能覆盖整个格子区域,这样点击任意格子都能聚焦到它。
  2. 透明度要低到看不见,但不能设置visibility: Hidden,否则组件不参与布局也无法聚焦。
  3. 文本颜色要设成透明,否则输入内容会露馅。

我的实现如下:

TextInput({ text: this.inputValue, placeholder: '' }) .type(InputType.NUMBER) // 数字键盘 .borderRadius(0) .backgroundColor(Color.Transparent) .caretColor(Color.Transparent) // 光标也隐藏 .fontColor(Color.Transparent) // 文本透明 .opacity(0.01) // 整个组件接近不可见 .focusable(true) .defaultFocus(true) // 页面加载后自动聚焦,直接弹出键盘 .width('100%') .height('100%') .position({ x: 0, y: 0 }) // 绝对定位覆盖格子区域

实践经验提醒几点:

  • .opacity(0.01)比.opacity(0)稳妥。某些鸿蒙版本上,opacity为0的组件会被判定为不可交互区域,点击无法聚焦。保留0.01的可见度可以避免这个坑。
  • .caretColor(Color.Transparent)一定要设置,否则你能看到一个光标在闪,非常奇怪。
  • defaultFocus(true)要慎用。如果页面还有别的输入需求,一进来就弹键盘可能不是用户预期;但对验证码页来说,弹键盘恰恰是减少用户操作的关键一步。
  • 别忘了给TextInput设置.onSubmit事件。用户输完最后一位后,自动触发“下一步”或“校验”逻辑,体验闭环。

3.3 字符填充规则:长度限制与非法字符过滤

输入框虽然是InputType.NUMBER,但为了防止极端情况(比如某些输入法允许粘贴字母),必须在onChange里做二次过滤。

.onChange((value: string) => { // 1. 只保留数字字符 const filtered = value.replace(/[^0-9]/g, ''); // 2. 截取前 codeLen 位 const limited = filtered.substring(0, this.codeLen); // 3. 同步回填到隐藏输入框(防止非法字符短暂显示) this.inputValue = limited; // 4. 更新格子数组 this.codeArray = []; for (let i = 0; i < this.codeLen; i++) { this.codeArray.push(limited.charAt(i) || ''); } // 5. 更新激活位置(光标停在最后一个非空字符之后) this.activeIndex = limited.length; if (this.codeLen > 0 && limited.length >= this.codeLen) { // 输完了,可以自动触发下一步 } })

这个处理逻辑把所有状态更新集中到了一处,简洁且可控。

这里有一个容易踩的坑:不能直接用this.inputValue.length来计算激活下标。因为长度和内容不等于有效位数,一旦用户粘贴了非法字符,length会大于有效数字个数,导致activeIndex越界。我上面用了limited.length,它一定是合法且截断后的长度,不会出问题。

还有一个细节:光标位置与activeIndex不完全等价。如果用户把光标移到中间插入字符,这种实现方式是“尾部追加”逻辑,不支持中间插入。对于手机号/验证码场景,这完全没问题,行为反而更符合预期:用户点击第3格,实际焦点还是落在隐藏输入框上,新增字符总是append到末尾。过度设计成“支持中间插入”反而会导致格子错乱。

4. 实操过程与核心环节实现

4.1 第一步:定义组件参数与属性

我封装成了一个自定义组件CodeInputView,对外暴露两个参数:格子数量和完整输入回调。

@Component export struct CodeInputView { // 格子数量,默认为6 @Prop codeLength: number = 6; // 输入完成回调 onCompleted: (code: string) => void = () => {}; @State inputValue: string = ''; @State codeArray: string[] = []; @State activeIndex: number = 0; aboutToAppear(): void { this.codeArray = new Array(this.codeLength).fill(''); } }

这里要注意@Prop和@State的配合。codeLength由父组件传入,只在初始化时生效,后续如果动态改变格子数量,需要额外处理(比如用@Watch监听变化重置codeArray),但大多数场景下格子数量是固定的,@Prop足够。

4.2 第二步:完整页面结构与网格布局

页面的主体结构分三层:

  1. 最外层是一个Stack容器,用于叠加隐藏输入框和格子层。
  2. 第二层是Row,横向排列N个格子。
  3. 第三层是每个格子的Column(字符 Text + 下划线 Divider)。
build() { Stack({ alignContent: Alignment.Center }) { // 格子渲染层 Row({ space: 12 }) { ForEach(this.codeArray, (char: string, index: number) => { Column({ space: 6 }) { Text(char ? char : ' ') .fontSize(30) .fontWeight(FontWeight.Bold) .fontColor(this.activeIndex === index ? '#1A1A1A' : '#BBBBBB') .height(40) Divider() .strokeWidth(this.activeIndex === index ? 3 : 1.5) .color(this.activeIndex === index ? '#007AFF' : '#D8D8D8') } .width(this.getCellWidth()) .onClick(() => { // 点击格子时,确保隐藏输入框获得焦点 this.$inputController.setText(this.inputValue); }) }, (item: string, index: number) => `${index}-${item}`) } .width('100%') .padding({ left: 20, right: 20 }) // 隐藏输入层 TextInput({ text: this.inputValue, controller: this.$inputController }) // ... 上述透明设置 .position({ x: 0, y: 0 }) .width('100%') .height(56) } .width('100%') .height(70) }

getCellWidth()这个方法值得说一下。如果格子数量是4/6等偶数,且屏幕宽度固定,可以简单做除法;但更好的做法是根据屏幕宽度动态计算,减去Row内边距和格子间距的总和:

private getCellWidth(): string { const screenWidth = this.getScreenWidth(); // 通过 px2vp 或 window 获取 const spacingTotal = (this.codeLength - 1) * 12; const paddingTotal = 40; return `${(screenWidth - spacingTotal - paddingTotal) / this.codeLength}vp`; }

实操中我推荐把间距固定为12vp,屏幕宽度用display.getDefaultDisplaySync().width获取。鸿蒙的vp单位在不同设备上能自适应,但px不行,务必用vp。

4.3 第三步:处理删除回退与光标移动

用户按删除键时,系统会先删除inputValue的最后一个字符,然后触发onChange,我们上面的事件处理逻辑已经涵盖了回退:limited.length变小,codeArray最后一项变成空,activeIndex自动回退。

但onChange有一个坑:部分输入法在连续快速删除时,不会每个动作都触发onChange,而是间隔触发,导致UI更新不够及时。我的解决办法是额外监听onKeyEvent中的删除键事件,主动同步一次状态:

.onKeyEvent((event: KeyEvent) => { if (event.type === KeyType.Down && event.keyCode === 2055) { // 删除键 this.$inputController.setText(this.inputValue.substring(0, this.inputValue.length - 1)); } })

重点说明:2055是通用删除键的keyCode,不同设备/输入法可能会有差异,所以这个只能作为兜底方案。主力还得是onChange,它在绝大多数情况下都是可靠且及时的。

4.4 第四步:完整输入完成判断与自动提交

当codeArray中填满所有格子时,通常要自动触发后续动作。我在onChange里做了判断,输满后直接调用onCompleted:

if (limited.length >= this.codeLength) { this.onCompleted(limited); }

但如果用户删除后再重新输入,还是会走到这个判断。如果需要“只触发一次”,可以在组件里加一个@State submitted: boolean = false,首次提交后置为真,重置时再恢复。

还有一个容易被忽略的细节:输入完成时隐藏键盘。用inputController.stopEditing()即可。否则用户输满后键盘还占着半屏,后面弹出的确认弹窗会被顶上去,体验很差。

if (limited.length >= this.codeLength) { this.$inputController.stopEditing(); this.onCompleted(limited); }

4.5 第五步:整段代码合并与页面调用

把上述代码整理成一个完整的自定义组件后,在页面中调用:

CodeInputView({ codeLength: 6, onCompleted: (code: string) => { // 发起验证码校验请求 } })

到这里,核心功能已经全部实现:格子渲染、输入填充、删除回退、自动提交。我在多个项目里跑过这套逻辑,稳定性和流畅度都没问题。

5. 常见问题与排查技巧实录

5.1 点击格子无法唤起键盘

现象:页面加载后可以自动弹键盘,但手动点击某个格子没反应。

排查步骤:

  1. 确认隐藏TextInput是否覆盖在格子区域上方。用opacity(1)临时显示它,看它的实际位置和大小对不对。
  2. 确认position或者Stack的alignContent设置是否正确,通常是Stack里子组件的层级问题。
  3. 检查TextInput是否被if条件错误地移除了。

解决方案:把TextInput的宽高设成与格子区域相同,或者直接width('100%').height(56),配合Stack的默认对齐方式,基本能解决。另外,点击格子时用this.$inputController.setText(this.inputValue)强制刷新输入框内容,也能唤起输入法。

5.2 输入法每次都显示字母键盘

现象:.type(InputType.NUMBER)也设置了,但弹出的还是全键盘。

原因分析:鸿蒙部分版本对InputType.NUMBER的支持不彻底,或者输入法App自身设定优先。这种通常出现在第三方输入法上,系统自带输入法一般没问题。

解决方案:再加一行.enableKeyboardOnFocus(true),如果还不行,就换用InputType.PHONE_NUMBER,它在大多数输入法下会稳定弹出数字键盘。实测中PHONE_NUMBER比NUMBER兼容性更好,因为后者有时被输入法当作“小数输入”处理,会出现小数点键位。

5.3 高亮下划线显示错误

现象:输入到第3位,但第2格的下划线高亮了。

排查思路:这是activeIndex计算错误。回顾逻辑:activeIndex = limited.length,如果limited被截断或过滤后长度不对,光标位置就会偏。在代码里打印一下limited.length和codeArray,两相对比就知道哪里出问题了。

常见原因:codeArray初始化长度与实际codeLength不一致,比如父组件传入codeLength=6,但子组件aboutToAppear里用new Array(4)初始化,导致下标错位。

解决方案:强制在aboutToAppear里用this.codeLength初始化codeArray,并在onChange里也一样用this.codeLength截断,两处保持一致。

5.4 单格单字符模式下粘贴不生效

现象:用户通过输入法粘贴一串验证码,结果只有第一位显示了。

原因分析:部分输入法的粘贴行为会逐字符触发onChange,而且每次触发时传入的是“增量”而非“全量”,我们基于全量字符串的覆盖逻辑就会被后面的增量覆盖掉。

解决方案:在onChange里不要直接用传入的value,而是读取this.inputController.getTextContent()拿输入框的真实全量内容,或者把传入值和当前状态做合并:

const finalValue = value.length > this.inputValue.length ? value : this.inputValue;

这个方法不是100%可靠,但能解决多数场景。更稳的做法是在onPaste事件里拦截并手动赋值。我在一个金融类App项目里遇到过粘贴不生效的问题,最后就是用onPaste拦截,解析剪贴板内容后直接this.inputValue = parsedCode,效果最好。

5.5 页面回退后输入框状态残留

现象:从验证码页退到上一个页面,再回来,输入框还是上次的输入内容。

原因分析:页面没有被销毁,@State数据还保留着。

解决方案:在页面onPageHide或aboutToDisappear里清空状态。如果是用Navigation管理的多页面,可以在onHidden回调里执行:

this.inputValue = ''; this.codeArray = new Array(this.codeLength).fill(''); this.activeIndex = 0;

这一步看着简单,但很多测试同学会专门去验证“退出重进”后的状态问题,建议从一开始就加上。

6. 性能优化与扩展方向

6.1 减少不必要的组件重绘

ArkUI的ForEach在列表渲染时,如果item的key不唯一或频繁变化,会导致整个ForEach重建。我在生成key时用了${index}-${item},这样即使字符变化,只要下标不变,key就稳定,组件不会重建,只会更新Text内容。

这一点在格子数量少时看不出差别,但如果未来做成“动态长度”输入(比如短信验证码可能是6位也可能是8位),或者格子数量很多时,就会明显影响流畅度。实测在低端设备上,不写key和写key的渲染耗时差距能有3倍。

6.2 密码模式扩展

把Text的内容从纯数字改成圆点(如●),只需要一个三元判断:

Text(char ? '●' : ' ')

并且把fontSize调小一点,因为圆点视觉上比数字大。密码输入时通常不想看到真实的字符,这个扩展基本零成本。

6.3 支持倒计时与自动校验联动

验证码输入框通常伴随“获取验证码”按钮倒计时。组件可以对外暴露一个方法,供父组件调用重置:

public resetCodeInput(): void { this.inputValue = ''; this.codeArray = new Array(this.codeLength).fill(''); this.activeIndex = 0; }

配合@State的响应式机制,父组件修改codeLength时,子组件也能通过@Watch自动重置:

@Watch('onCodeLengthChange') @Prop codeLength: number = 6; onCodeLengthChange() { this.codeArray = new Array(this.codeLength).fill(''); this.inputValue = ''; this.activeIndex = 0; }

6.4 深色模式适配

目前色值是写死的浅色主题色。鸿蒙的深色模式适配很简单:用Resource颜色替代硬编码色值,在resources/base/element/color.json和resources/dark/element/color.json里分别定义同一名称的颜色,在代码里引用$r('app.color.input_line_default')即可。下划线的默认色和高亮色、字符颜色各定义一对,适配工作量很小。

7. 实操心得与踩坑总结

整个组件从第一版到现在,踩过最大的坑有两个,值得单独拿出来说。

第一个是输入法“增量回调”问题。第一版我天真地以为onChange每次都会给我完整的字符串,直到某台测试机上反复出现字符错位,才发现部分输入法粘贴时是分段回调的。后来改成以输入框控制器读到的真实内容为准,同时在过滤正则上做了兜底,才算彻底稳定。

第二个是透明输入框的opacity设置。之前踩过一次opacity(0)后组件完全无法交互的坑,查了半天文档才定位到是鸿蒙对“完全透明组件”的点击穿透处理。所以现在用0.01这个数值,既满足视觉隐藏,又保留交互能力。

要说这套方案的取舍,我认为它把“输入体验”和“视觉表现”做了很好的解耦。真正的输入逻辑只有一个隐藏输入框承载,不用管焦点分发和跨组件事件,天然没有竞态问题;而视觉部分完全交给状态驱动,改样式时只需动Text和Divider,不用碰输入逻辑。

如果你是在做一个对输入体验有要求的鸿蒙应用,或者想给自己的组件库加一个验证码输入组件,这套方案可以直接照搬。后续还可以往振动反馈、音频提示、错误状态抖动这些方向扩展,本质上都是给同一个状态模型加效果,扩展性很好。

相关新闻

企业知识图谱+AI智能体:破解研发全流程工具孤岛

企业知识图谱+AI智能体:破解研发全流程工具孤岛

1. 从"工具孤岛"到"知识流动"&#xff1a;这次合作到底在解决什么问题如果你在软件研发团队待过一段时间&#xff0c;大概率见过这样的场景&#xff1a;需求写在文档系统里&#xff0c;任务追踪在项目管理工具中&#xff0c;代码托管在另一个平台&#xff…

2026/10/20 19:08:06 阅读更多 →
GoAhead 5.1.1编译原理详解:从源码到嵌入式部署的完整构建链路

GoAhead 5.1.1编译原理详解:从源码到嵌入式部署的完整构建链路

1. 项目拆解&#xff1a;先弄清楚这个版本到底要做什么1.1 编译一个东西之前&#xff0c;首先得知道它是什么GoAhead 5.1.1 是一个典型的嵌入式 Web 服务器&#xff0c;日常总能在路由器管理后台、工业网关、远程 IO 模块、智能终端设备里看到它的身影。它不像 Nginx 或者 Apac…

2026/10/20 19:08:06 阅读更多 →
99.8%通过率背后:AI编程的自主推理能力与工程落地

99.8%通过率背后:AI编程的自主推理能力与工程落地

前阵子有朋友转给我一条消息&#xff1a;某海外AI研究机构在内部评测中&#xff0c;让大模型在编程竞赛里拿到了99.8%的通过率&#xff0c;而且号称实现了“完全自主推理”。我的第一反应不是兴奋&#xff0c;而是去找评测说明。做开发这么多年&#xff0c;我太清楚这种数字背后…

2026/10/20 19:08:06 阅读更多 →

最新新闻

SolidWorks二次开发模板:基于.NET 6的工程化插件架构

SolidWorks二次开发模板:基于.NET 6的工程化插件架构

简介&#xff1a;本资源是面向机械设计工程师与CAD二次开发初学者的SolidWorks C#插件开发实战模板&#xff0c;聚焦Windows平台下基于.NET的COM Add-in定制化扩展。资源提供开箱即用的完整VS解决方案&#xff08;.sln&#xff09;&#xff0c;含99个文件、10.24MB&#xff0c;…

2026/10/20 19:46:36 阅读更多 →
Basler工业相机C++ SDK原理与GenICam协议深度解析

Basler工业相机C++ SDK原理与GenICam协议深度解析

简介&#xff1a;本资源是一套面向C开发者、机器视觉工程师及工业相机应用学习者的Basler工业相机SDK封装类库&#xff0c;聚焦于简化Basler相机的初始化、参数配置、图像采集与事件回调等核心操作&#xff0c;显著降低底层Pylon SDK调用复杂度。压缩包仅含2个关键文件&#xf…

2026/10/20 19:46:36 阅读更多 →
C# Winform 连接 SQL Server 环境配置与避坑指南

C# Winform 连接 SQL Server 环境配置与避坑指南

简介&#xff1a;这份资源面向C#与数据库开发的入门学习者&#xff0c;聚焦Winform桌面端如何同时实现SQL Server的网络连接与本地连接&#xff0c;并完成增删改查这一企业级应用的核心数据管理能力。包内共28个文件&#xff0c;以cs源码、resx与resources资源文件、exe可执行程…

2026/10/20 19:46:36 阅读更多 →
AI数据分析翻译官:把Excel表格变成会说话的业务结论

AI数据分析翻译官:把Excel表格变成会说话的业务结论

看到数据就头大&#xff1f;我可太懂了。我见过不少朋友&#xff0c;一提到“看报表”就开始眼神放空&#xff0c;运营、产品、市场&#xff0c;甚至部分管理者&#xff0c;几乎天天被Excel糊脸&#xff0c;却不知道从哪里下嘴。这种状态下&#xff0c;写周报、做复盘、准备汇报…

2026/10/20 19:46:36 阅读更多 →
SpringBoot全国降水数据分析与可视化系统实战:从数据清洗到ECharts联动

SpringBoot全国降水数据分析与可视化系统实战:从数据清洗到ECharts联动

上个月有个同学拿了个毕业设计题目来问&#xff0c;题目大概是"基于SpringBoot的全国降水数据分析与展示可视化系统"。他当时第一反应是&#xff1a;这不就是一个地图页面&#xff0c;后端查数据返回&#xff0c;前端把点和颜色画出来就行了吗&#xff1f;等我让他把…

2026/10/20 19:46:36 阅读更多 →
ST-LINK固件升级指南:V2.37.26救砖实操与避坑全解析

ST-LINK固件升级指南:V2.37.26救砖实操与避坑全解析

简介&#xff1a;本资源是ST官方STLink调试编程器的固件升级工具包STSW-LINK007 V2-37-26正式发布版&#xff0c;面向STM32/STM8嵌入式开发者、硬件工程师及高校实验教学人员&#xff0c;解决STLink设备固件陈旧导致的IDE兼容性异常、新MCU型号识别失败、调试连接不稳定等实际问…

2026/10/20 19:45:34 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →