uniapp跨端开发:手写年月选择器模态框,避开第三方组件库的坑
前阵子在做一个跨端管理系统的账单周期模块时产品提了个需求表单里需要选择一个年月作为结算周期。放在PC上这是个再普通不过的日期控件可到了移动端尤其面对uniapp这种要兼容H5、小程序、App多端的场景事情就没那么简单了。翻了几个常见组件库不是样式跟设计稿差太远就是体积大到不忍直视在小程序端甚至出现弹层错位的兼容问题。最后我决定自己手搓一个年月选择器模态框从遮罩层到滚动选择器全部自己写半天时间搞定关键逻辑和交互全部可控。这篇就把整个实现过程、代码细节和多端适配的坑一次性梳理出来适合正在做跨端弹层组件、被第三方picker折磨过的同学直接参考。1. 手搓之前为什么不用现成组件我做了哪些选型对比1.1 现成组件的三个尴尬点第一个痛点是视觉不统一。设计稿里给的是一个白色圆角弹层顶部左侧取消右侧确定中间是两列并排的滚动列表选中年和月。但不少现成组件默认是iOS那种深色滚轮样式规则又多想改造成设计稿的样子覆盖样式的工作量一点也不比从零写小。尤其弹层的圆角、内边距、文字颜色这些细节在组件内部封得比较死想通过外部class覆盖经常要加上一堆不得已的!important。第二个痛点是体积。有些功能很全的日历组件为了一个年月选择拖进来几百KB的JS里面还绑了日历视图、周视图、多选区间逻辑。在uniapp打包时这些依赖会打入最终产物小程序包体积本来就紧张为了一个轻量交互背这么大的包袱实在不划算。我需要的只是一个两列数据的选择器数据量小、逻辑简单完全没有必要把整个日历引擎搬进来。第三个痛点是跨端表现不稳定。同一个组件在H5端正常到了小程序端可能出现样式错位、滚动卡顿甚至事件绑定失效。遇到这种情况你得去读组件源码、查版本差异排查成本很高。与其被动修补第三方代码不如自己写一个复杂度可控的组件uniapp官方的picker-view在大部分端上表现稳定我在它外面套一层自己控制的遮罩和面板问题面就大幅收窄了。1.2 原生picker与自绘弹层怎么选我整理了一个简单的选型对比供你直接抄作业方案优点缺点适用场景原生picker实现快、平台原生体验好样式固定、无法自定义弹层、边界行为各端不一对视觉没有要求、只求快速交付自绘弹层 picker-view完全可控、UI与交互自定义、边界逻辑可编程需要自己处理遮罩、动画、安全区视觉有要求、交互需要定制、需要跨端统一最终我选了第二种方案外面是一个自定义模态框里面放官方picker-view组件。为什么不直接用scroll-view手写滚动选择因为滚动选择器里最难的惯性滚动、回弹、边界阻尼picker-view官方已经做好了自己用scroll-view做要处理大量边界逻辑开发成本高而且真机上手感差很多。官方组件负责滚动能力自定义弹层负责视觉与控制各干各擅长的事。1.3 功能清单一览动手之前我先把需求收敛成下面几条后面的代码完全围绕这份清单展开支持默认值回填打开时能选中调用方传入的年月支持年份上下限可选范围通过startYear和endYear控制支持确定/取消回调确认时回传年、月和格式化好的文本支持点击遮罩关闭同时触发取消回调适配安全区iPhone底部横条不遮挡按钮标题文案可配置这份清单看起来很常规但每一条在实现里都对应着一个坑后面会逐个讲到。2. 年月数据与边界算法默认值回填和三行代码能解决的事2.1 年份、月份数据初始化第一步是准备好两列数据源。年份和月份本质上就是两个数组为了方便展示与取值我统一用对象数组每个元素包含label和value两个字段。// utils/date-helper.js function padZero(num) { return num 10 ? 0 num : num; } // 生成年份列表 function buildYears(startYear, endYear) { if (startYear endYear) return []; const list []; for (let y startYear; y endYear; y) { list.push({ label: y 年, value: y }); } return list; } // 生成月份列表 function buildMonths() { return Array.from({ length: 12 }, (_, i) { const month i 1; return { label: month 月, value: month }; }); } // 显示文案拼接例如 2024年08月 function formatYearMonth(year, month) { return year 年 padZero(month) 月; } module.exports { buildYears, buildMonths, formatYearMonth };为什么不用纯数字数组因为picker-view-column的每一项是展示内容confirm的时候要的是数值。对象数组一个结构同时满足两者。另外如果之后想在年份前面拼“今年”“本月”这样的特殊标记直接在label字段上做文章就可以了数据结构不用动。2.2 默认值回填与边界裁剪这里是最容易翻车的地方我单独拿出来说。默认值传入后必须先做边界裁剪再放进滚动列表。function normalizeDefault(defaultYear, defaultMonth, startYear, endYear) { let year defaultYear; let month defaultMonth; if (year startYear) year startYear; if (year endYear) year endYear; if (year startYear month 1) month 1; if (year endYear month 12) month 12; return { year, month }; }很多人会忽略这一步以为默认传2024年6月就万事大吉。可如果startYear设置成了2026默认值2024年6月根本不在这两列里picker-view的value去索引一个不存在的元素表现就是滚动列停在undefined位置显示空白或者乱跳。所以打开弹层之前必须把默认值裁剪到[startYear, endYear]区间内。月份边界同样需要注意如果年份被裁剪到了startYear那月份不能小于1如果年份被裁剪到了endYear那月份不能大于12。这个逻辑一分钟写完但能防住很多莫名其妙的bug。2.3 从数据到索引pickerValue的同步链路picker-view的value接收的是索引数组不是具体年月值这是新手最容易卡住的地方。整个同步过程是外部传defaultYear / defaultMonth→ 内部计算成两个index → 放进pickerValue→ picker显示对应行用户滚动时change事件带回index → 从years / months里取出真实值 → emit调给父层。const yearIndex years.findIndex(item item.value currentYear); const monthIndex months.findIndex(item item.value currentMonth); this.pickerValue [Math.max(yearIndex, 0), Math.max(monthIndex, 0)];我把这条链路里所有的临时索引都放在组件的data里change回调只更新索引不在滚动过程中反复向外emit。这样做的好处下文踩坑部分会详细说先记住一个原则滚动选择器的当前值永远用索引表达不要在滚动过程中同步真实值。3. 模态框三件套遮罩层、滚动面板与确定取消事件3.1 弹层结构与层级设计先看模板结构这是整个模态框的地基template view v-ifvisible classym-mask catchtouchmovenoop taponMaskTap view classym-panel tap.stop view classym-header text classym-btn taponCancel取消/text text classym-title{{ title }}/text text classym-btn ym-btn-primary taponConfirm确定/text /view picker-view classym-picker :valuepickerValue changeonPickerChange indicator-classym-picker-indicator picker-view-column view v-foritem in years :keyitem.value classym-item{{ item.label }}/view /picker-view-column picker-view-column view v-foritem in months :keyitem.value classym-item{{ item.label }}/view /picker-view-column /picker-view /view /view /template这里有几个关键设计第一用v-if控制渲染而不是v-show。uniapp在小程序端的v-show只是display切换但弹层组件内部还有picker-view关闭状态下不销毁反而可能残留状态。v-if保证弹层关闭时不在视图树里减少setData开销打开时重新挂载再配合初始化逻辑把内部状态重置。第二遮罩层上绑定了catchtouchmovenoop。在小程序端这是阻止滚动穿透的关键不写这句话页面在弹层打开后依然能上下滑动体验很出戏。第三弹层内部面板用tap.stop阻止事件冒泡。如果不加用户点面板空白区域时遮罩层的tap事件也会触发弹层会莫名其妙地关闭。3.2 picker-view高度与指示条的设置picker-view有两个容易踩坑的点一个是指示条样式一个是高度。指示条就是中间那条高亮背景默认样式在部分端比较生硬我用indicator-class自定义成半透明灰色背景.ym-picker { height: 260rpx; } .ym-picker-indicator { height: 80rpx; border-radius: 12rpx; background: rgba(0, 0, 0, 0.06); } .ym-item { height: 80rpx; line-height: 80rpx; font-size: 30rpx; color: #333; text-align: center; }高度设置的逻辑是每个item高80rpx指示条也是80rpx三行刚好显示一个完整的选中项和上下两个候选项视觉上最符合滚轮选择器的习惯。picker-view本身必须给height否则在部分端会渲染成0高度列内容根本不显示。3.3 事件收敛确认、取消与遮罩关闭确定和取消的处理逻辑如下methods: { onPickerChange(e) { const [yearIndex, monthIndex] e.detail.value; this.currentYearIndex yearIndex; this.currentMonthIndex monthIndex; }, onConfirm() { const year this.years[this.currentYearIndex].value; const month this.months[this.currentMonthIndex].value; this.$emit(confirm, { year, month, text: formatYearMonth(year, month) }); this.close(); }, onCancel() { this.$emit(cancel); this.close(); }, onMaskTap() { this.$emit(cancel); this.close(); }, close() { this.$emit(update:visible, false); this.$emit(cancel); } }我特意在confirm的事件载荷里直接带上text字段父层就不用再自己拼年月文本了。这个细节能给调用方省去一行格式化代码在表单提交场景里特别实用。4. 兼容多端不是口号单位、滚动穿透、安全区的实战处理4.1 尺寸单位rpx和px的取舍uniapp里rpx在小程序端是核心单位但在H5端和App的WebView渲染模式下rpx的换算逻辑偶尔会和预期不一致。我的经验是弹层宽度、高度、内边距这些布局尺寸用rpx完全没问题因为两端换算比例一致但如果要写固定阴影、边框或者1px细线建议用px否则在H5端偶发出现线条过粗或者阴影发虚的情况。在App端如果跑的是Vue页面而不是WebView渲染rpx也正常工作。所以不用太焦虑单位问题只要记住一句话布局用rpx像素级精细控制用px真机测试时重点检查H5端和高分辨率Android机。4.2 滚动穿透的根治方案弹层打开后页面还能滑动是最常见的bug各地表现形式不太一样小程序端遮罩层上加catchtouchmovenoop即可阻止事件继续向页面传递H5端需要在弹层显示时对body设置overflow: hidden关闭时恢复App端如果用的是Vue页面弹层fixed定位一般不会触发滚动穿透如果跑了WebView模式按H5方式处理H5端的处理代码大致是这样methods: { lockPageScroll() { document.body.style.overflow hidden; }, unlockPageScroll() { document.body.style.overflow ; } }注意H5端如果有滚动容器而不是body在滚那就要找到真正的滚动容器去锁。我在某次适配里就吃了这个亏body锁了没用因为页面滚动发生在一个内部scroll-view上。排查方法也很简单弹层打开后在浏览器控制台里逐级看哪个元素还在滚动锁定它就行。4.3 安全区适配与刘海屏iPhone X以后的机型底部有横条如果不处理面板底部按钮会被遮挡。解决方案是给面板底部加安全区留白.ym-panel { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); }safe-area-inset-bottom在iPhone X系列上等于底部横条的高度在普通机型上是0。两条声明都要写constant是为iOS 11.0-11.2准备的兼容写法env是iOS 11.2以后的标准写法。Android端通常不识别这两个值会自动忽略不影响布局。4.4 picker-view的初始渲染时序弹层刚打开时组件挂载和v-if渲染有先后如果立刻设置pickerValue组件内部可能还没准备好导致默认值选不中。我踩过这个坑后统一用了这样的时序watch: { visible(val) { if (val) { this.initData(); this.$nextTick(() { setTimeout(() { this.syncPickerValue(); }, 50); }); } } }先用initData准备数据再等nextTick完成渲染最后给一点余量设置索引。不要小看这50ms在某些Android WebView上nextTick并不等于picker-view内部已经完成布局留一个微任务的时间余量能避免首帧空白和默认值错位。5. 完整组件实操目录结构、props/events与页面调用5.1 组件目录与文件结构我用的目录结构是按照uniapp的easycom规范放的components/ year-month-picker/ index.vueeasycom规则下页面里直接写YearMonthPicker /就能自动引入不需要手动import。注意组件名使用PascalCase目录名使用kebab-case这是uniapp的默认匹配规则。5.2 props与事件设计完整的props配置如下属性类型默认值说明visibleBooleanfalse是否显示弹层titleString选择年月弹层标题defaultYearNumber当前年默认选中年份defaultMonthNumber当前月默认选中月份startYearNumber当前年-10可选最小年份endYearNumber当前年10可选最大年份组件向外抛出两个事件confirm和cancel。confirm的载荷里包含year、month、text三个字段cancel则没有载荷父层只需要关闭弹层即可。props: { visible: { type: Boolean, default: false }, title: { type: String, default: 选择年月 }, defaultYear: { type: Number, default: () new Date().getFullYear() }, defaultMonth: { type: Number, default: () new Date().getMonth() 1 }, startYear: { type: Number, default: () new Date().getFullYear() - 10 }, endYear: { type: Number, default: () new Date().getFullYear() 10 } }这里建议用update:visible事件配合父层的:visible.sync实现单向数据流弹层只负责通知父层“我想关”不直接修改props避免父子状态互相污染。5.3 页面中调用组件页面里调用这个年月选择器逻辑非常直接template view view classform-item tapshowPicker text结算周期/text text{{ selectedText || 请选择 }}/text /view YearMonthPicker :visible.syncpickerVisible :default-yearcurrentYear :default-monthcurrentMonth :start-yearstartYear :end-yearendYear confirmonPickerConfirm / /view /template script export default { data() { return { pickerVisible: false, currentYear: 2024, currentMonth: 8, selectedText: }; }, computed: { startYear() { return new Date().getFullYear() - 10; }, endYear() { return new Date().getFullYear() 10; } }, methods: { showPicker() { if (!this.selectedText) { this.currentYear new Date().getFullYear(); this.currentMonth new Date().getMonth() 1; } this.pickerVisible true; }, onPickerConfirm({ year, month, text }) { this.currentYear year; this.currentMonth month; this.selectedText text; this.pickerVisible false; } } }; /script调用时有两个细节第一打开弹层前先给defaultYear和defaultMonth赋值保证每次打开都有确定的默认值第二表单回显时如果已经保存过值就用保存的年月作为默认值否则用当前年月。5.4 组件完整代码把上面所有片段合并起来就是一个可以复制到项目里直接用的完整组件template view v-ifvisible classym-mask catchtouchmovenoop taponMaskTap view classym-panel tap.stop view classym-header text classym-btn taponCancel取消/text text classym-title{{ title }}/text text classym-btn ym-btn-primary taponConfirm确定/text /view picker-view classym-picker :valuepickerValue changeonPickerChange indicator-classym-picker-indicator picker-view-column view v-foritem in years :keyitem.value classym-item{{ item.label }}/view /picker-view-column picker-view-column view v-foritem in months :keyitem.value classym-item{{ item.label }}/view /picker-view-column /picker-view /view /view /template script import { buildYears, buildMonths, formatYearMonth } from /utils/date-helper.js; function normalizeDefault(defaultYear, defaultMonth, startYear, endYear) { let year defaultYear; let month defaultMonth; if (year startYear) year startYear; if (year endYear) year endYear; if (year startYear month 1) month 1; if (year endYear month 12) month 12; return { year, month }; } export default { name: YearMonthPicker, props: { visible: { type: Boolean, default: false }, title: { type: String, default: 选择年月 }, defaultYear: { type: Number, default: () new Date().getFullYear() }, defaultMonth: { type: Number, default: () new Date().getMonth() 1 }, startYear: { type: Number, default: () new Date().getFullYear() - 10 }, endYear: { type: Number, default: () new Date().getFullYear() 10 } }, data() { return { years: [], months: [], pickerValue: [0, 0], currentYearIndex: 0, currentMonthIndex: 0 }; }, watch: { visible(val) { if (val) { this.initData(); this.$nextTick(() { setTimeout(() this.syncPickerValue(), 50); }); } } }, methods: { initData() { this.years buildYears(this.startYear, this.endYear); this.months buildMonths(); const normalized normalizeDefault( this.defaultYear, this.defaultMonth, this.startYear, this.endYear ); this.currentYearIndex Math.max( this.years.findIndex(item item.value normalized.year), 0 ); this.currentMonthIndex Math.max( this.months.findIndex(item item.value normalized.month), 0 ); }, syncPickerValue() { this.pickerValue [this.currentYearIndex, this.currentMonthIndex]; }, onPickerChange(e) { const [yearIndex, monthIndex] e.detail.value; this.currentYearIndex yearIndex; this.currentMonthIndex monthIndex; }, onConfirm() { const year this.years[this.currentYearIndex].value; const month this.months[this.currentMonthIndex].value; this.$emit(confirm, { year, month, text: formatYearMonth(year, month) }); this.close(); }, onCancel() { this.$emit(cancel); this.close(); }, onMaskTap() { this.$emit(cancel); this.close(); }, close() { this.$emit(update:visible, false); } } }; /script style scoped .ym-mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 999; display: flex; align-items: center; justify-content: center; } .ym-panel { width: 86%; background: #fff; border-radius: 24rpx; overflow: hidden; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); } .ym-header { display: flex; align-items: center; justify-content: space-between; padding: 30rpx 32rpx 16rpx; } .ym-btn { font-size: 28rpx; color: #666; } .ym-btn-primary { color: #3478f6; font-weight: 500; } .ym-title { font-size: 32rpx; font-weight: 500; color: #222; } .ym-picker { height: 260rpx; } .ym-picker-indicator { height: 80rpx; border-radius: 12rpx; background: rgba(0, 0, 0, 0.06); } .ym-item { height: 80rpx; line-height: 80rpx; font-size: 30rpx; color: #333; text-align: center; } /style这套代码我已经在两个跨端项目里验证过H5、小程序端和App端都能正常工作。如果你要用Vue3版本的uniapp注意emits数组要显式声明样式部分基本不用动。6. 踩坑实录uniapp年月选择器的排查技巧与经验6.1 默认值回填失败的两种情况我遇到过两次回填失败现象和原因完全不同。第一次是startYear设置太晚默认值超出范围滚动列没有对应元素表现是选中项空白。解决办法就是上面说的normalizeDefault裁剪。第二次是弹层刚打开时立即设pickerValue但组件内部还没渲染完成索引传进去被忽略了。解决办法是nextTick加setTimeout延迟同步。这个坑在低端Android机上特别容易复现因为WebView布局速度慢模拟器不一定能看出来一定要真机测。6.2 遮罩点击穿透与事件冒泡点击面板内部弹层却关闭了多半是事件冒泡问题。遮罩的tap在事件冒泡阶段会收到面板内部元素的tap事件所以面板根节点必须加tap.stop。还有另一种情况面板内部如果有按钮按钮的tap也会冒泡到遮罩同样靠stop解决。我在某次排查中发现即便加了tap.stop在某些版本的百度小程序端仍然会触发遮罩关闭。后来把遮罩的tap改为catchtap彻底阻止事件向上传递问题才消失。如果你遇到类似诡异问题优先把tap换成catchtap。6.3 弹层打开后页面仍能滑动的排查清单这个问题我在三个端都遇过按以下顺序排查基本能解决检查遮罩层是否加了catchtouchmovenoop确认弹层打开时锁定了body或真正的滚动容器检查页面是不是用了scroll-view而不是页面级滚动如果是要对scroll-view单独处理真机上测试模拟器经常无法触发穿透问题6.4 快速点击确定导致重复回调用户连续点击确定按钮可能触发两次confirm回调导致父层逻辑执行两遍。我在组件内部加了简单的防抖确认后立刻关闭弹层close里把visible更新为falsev-if销毁组件后续点击就无从谈起了。如果业务上需要更严谨可以在确认回调里加一个状态锁确认完成后将isLocked置为true等弹层完全关闭后再重置。6.5 动画生硬与v-if的冲突使用v-if控制弹层后直接写过渡动画会遇到一个问题关闭时组件瞬间被销毁动画根本没机会播放。我的处理思路是用visible的两次状态切换驱动动画class关闭时先加一个离开动画的class等动画结束后再真正销毁。在uniapp中可以监听animationend事件或者简单地用一个leaving标志延迟关闭close() { this.leaving true; this.$emit(update:visible, false); setTimeout(() { this.leaving false; }, 300); }如果项目对动画要求不高直接不写动画也行功能上完全没问题。我的经验是弹层组件稳定优先动画是锦上添花为了一个弹入弹出效果去处理多端transition差异性价比并不高。6.6 多端真机测试才是最终验收最后必须强调的是跨端组件一定要在真机或云端真机房里过一遍。模拟器很多时候无法触发滚动穿透、安全区、键盘遮挡这类问题。我做完这个组件后至少会在三端各扫一遍看默认值是否回填正确、滚动是否跟手、遮罩点击是否正常、iPhone上安全区是否生效。这些问题全部过了组件才算真正完成。之前测试完组件后我又把它扩展成了一个支持“年月日”的通用日期选择器数据结构从两个数组变成三个数组核心逻辑几乎不用改。如果你后续也要做“年-月-日”或者“年-月”联动这套自绘模态框的思路完全能复用。手搓组件的价值不在于省那几百KB而在于每一次交互、每一个边界都掌握在自己手里跨端出了问题你一眼就能定位到自己的代码而不是去翻第三方组件的源码。

相关新闻

AnyPS5:PS5远程串流与网络配置全攻略,解锁任何设备随时畅玩

AnyPS5:PS5远程串流与网络配置全攻略,解锁任何设备随时畅玩

项目标题 “AnyPS5” 乍一看像个硬件改造项目,其实圈内人一眼就能明白——这是一套围绕“随时随地游玩 PS5”的场景化解决方案。核心命题很简单:如何把客厅里那台躺在 HDMI 线后面的主机,变成一台真正属于你自己的云游戏节点。这个项目解决的…

2026/10/13 21:35:29 阅读更多 →
AI智能体内容如何下载?三类形态与本地备份方案

AI智能体内容如何下载?三类形态与本地备份方案

直接回答开头的问题:大部分能,但"怎么下载"取决于你用的是哪种智能体。这个问题的坑在于,大家习惯把所有 AI 工具都叫"智能体",可实际上它们的产出形态天差地别,下载路径也就完全不一样。我把这些…

2026/10/13 21:35:29 阅读更多 →
递归题目训练2:从会写到写好的系统化进阶指南

递归题目训练2:从会写到写好的系统化进阶指南

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

2026/10/13 21:35:29 阅读更多 →

最新新闻

Python超市管理系统源码实战:Flask+SQLite收银库存全流程

Python超市管理系统源码实战:Flask+SQLite收银库存全流程

简介:这是一份面向Python初学者与课程设计学习者的超市管理系统源码包,围绕员工管理、客户管理、登录验证与商品库存记录等日常运营场景展开,适合用来练习GUI界面搭建、文件持久化存储与模块化组织方式。压缩包共13个文件,约8KB&a…

2026/10/13 22:18:56 阅读更多 →
AI制图实测:10分钟生成架构图与流程图

AI制图实测:10分钟生成架构图与流程图

相信不少朋友都经历过这样的场景:刚画完一张系统架构图,产品经理又改了两版需求;刚理清一条业务主线,对接方又加了三个分支。画图这件事,听着很轻松,真正动手才发现,框要对齐、线要避开交叉、字…

2026/10/13 22:18:56 阅读更多 →
AI编程大模型免费活动的技术真相与工程实践指南

AI编程大模型免费活动的技术真相与工程实践指南

1. 项目概述:这不是“薅羊毛清单”,而是一份AI编程模型能力演进的实时观测日志“最新免费AI编程大模型活动汇总 2026年10月8日更新”——这个标题乍看像电商促销页,实则藏着当前AI工程落地最真实的脉搏。我从2023年起持续跟踪国内大模型在开发…

2026/10/13 22:18:56 阅读更多 →
PyTorch目标检测实战:小黄人检测从数据准备到模型训练全流程

PyTorch目标检测实战:小黄人检测从数据准备到模型训练全流程

最近用PyTorch做图像识别练习,选了个特别有画面感的入门任务——小黄人检测。目标检测这个名字听着有点唬人,但拆开其实就两件事:先把图片里的小黄人找出来,再用矩形框把它框住。上学那会儿做这类需求,第一反应是搞滑动…

2026/10/13 22:18:56 阅读更多 →
矩阵置零:原地算法与状态管理,LeetCode 73 题全解析

矩阵置零:原地算法与状态管理,LeetCode 73 题全解析

矩阵置零这个题,我第一次在刷题平台上看到时,内心是有点不屑的:两层循环,遇到 0 就改行改列,这不是有手就行?可当我真的原地写起来,才发现完全不是那么回事。题目本身很短:给定一个m…

2026/10/13 22:18:56 阅读更多 →
利用阿里云alidns实现DDNS动态解析:公网IP变化自动更新教程

利用阿里云alidns实现DDNS动态解析:公网IP变化自动更新教程

家里的宽带每隔一段时间公网IP就会变,尤其是不满运营商最长在线时长之后强制断线重拨的那种,老域名解析记录一直是旧IP,访问家里NAS或者是远程桌面全都得靠临时记IP来连,烦得不行。后来我把域名托管到了阿里云云解析,直…

2026/10/13 22:17:56 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →