最快实现是使用 ,现代浏览器(chrome、edge、firefox 57+、safari 14.1+)原生支持,一行 html 即可渲染月份选择器,但旧版 safari 不兼容。

用 <input type="month"> 最快实现,但要注意兼容性
现代浏览器(Chrome、Edge、Firefox 57+、Safari 14.1+)原生支持 <input type="month">,一行 HTML 就能渲染出月份选择器,无需额外脚本。但它在旧版 Safari(
如果你的用户群体明确覆盖较新设备(比如企业内部系统),直接用它最省事;否则必须加降级处理。
- 值格式固定为
"YYYY-MM"(如"2024-03"),提交前无需手动格式化 - 不支持设置最小/最大年份以外的粒度控制(比如不能禁用某个月)
- 移动端会调起原生日期滚轮,体验比自定义组件更一致
当需要兼容老浏览器时,用 <select></select> + JavaScript 动态生成选项
核心思路是:用两个 <select></select> 分别选年份和月份,再用 JS 合并成标准 YYYY-MM 值,同步到隐藏的 <input type="hidden"> 或直接提交。
避免手写 12 个 <option></option>,用脚本生成更可靠:
<select id="year-select"></select><select id="month-select"></select><input type="hidden" name="month" id="month-value"><p><script>
const yearSel = document.getElementById('year-select');
const monthSel = document.getElementById('month-select');
const hiddenInput = document.getElementById('month-value');</script></p><p>// 生成近10年选项
for (let y = new Date().getFullYear(); y >= new Date().getFullYear() - 9; y--) {
const opt = document.createElement('option');
opt.value = y;
opt.textContent = y + ' 年';
yearSel.appendChild(opt);
}</p><p>// 月份固定1–12
for (let m = 1; m </p><p>// 同步更新隐藏字段
function updateMonthValue() {
const year = yearSel.value;
const month = monthSel.value;
if (year && month) {
hiddenInput.value = <code>${year}-${month}</code>;
}
}
yearSel.addEventListener('change', updateMonthValue);
monthSel.addEventListener('change', updateMonthValue);
</p>
注意:不要依赖 select 的 value 属性自动拼接,因为月份可能被用户手动输入非数字内容;务必用 JS 显式构造 YYYY-MM。
用 moment.js 或 date-fns 处理边界逻辑更稳妥
纯原生 JS 处理跨年、闰年、动态年份范围时容易出错。比如“从当前月往前推 24 个月”,手动算年份和月份容易漏掉进位或借位。
用 date-fns(轻量、tree-shakable)几行就能搞定:
import { format, subMonths, startOfMonth } from 'date-fns';
<p>// 生成过去24个月的选项(含当前月)
const options = [];
for (let i = 0; i </p><p>如果项目已用 <code>moment.js</code>,注意它默认不处理 <code>YYYY-MM</code> 字符串的解析歧义(比如 <code>"2024-02"</code> 可能被当成 UTC 时间),建议统一用 <code>moment("2024-02", "YYYY-MM")</code> 显式指定格式。</p><h3>表单提交前校验 <code>month</code> 值是否合法,别只信前端 UI</h3><p>无论用原生 <code>type="month"</code> 还是自定义 <code>select</code>,用户都可能绕过 UI 直接修改 DOM 或发请求。后端必须校验字段是否符合 <code>^\d{4}-(0[1-9]|1[0-2])$</code> 格式,且月份在合理范围内(比如不能是 <code>"2024-13"</code>)。</p><p>前端校验只是辅助,重点检查三点:</p>
- 字符串长度是否为 7
- 是否匹配正则
/^\d{4}-(0[1-9]|1[0-2])$/ - 年份是否在业务允许区间内(如 2000–2030)
别用 new Date(value) 判断有效性——new Date("2024-13") 在某些浏览器返回 Invalid Date,但 new Date("2024-00") 却可能被解析成 2023-12,造成误判。
实际落地时,最常被忽略的是:没有把自定义组件的值同步到表单提交目标字段(比如忘了设 name 或没绑定到 FormData),或者在 React/Vue 等框架中未正确处理受控状态。这些细节比选哪种方案更影响交付。











