
做前端这些年我发现整个HTML体系里input标签大概是最分裂的一个元素——它本身不负责具体展示什么却能在不同type属性下变成文本框、密码框、日期选择器、滑块、文件上传框、颜色选择器……单行代码切换形态背后是一整套浏览器控件体系。这篇文章我不打算只列一张属性表而是想把input从基础属性、type选型、表单提交链路、原生校验到实战注册表单和常见踩坑完整串一遍。无论你是刚学HTML的新手还是写了几年表单却偶尔被细节绊住的老手这篇都能当一份查漏补缺的速查手册。1. 先从运行机制讲起为什么一个input能当二十种控件用要理解input不能把它当成一个普通的标签。div、p、span这类元素写出来是什么就是什么input不是——它是一个形态由属性决定的元素。同样的一个标签浏览器会根据type属性去调用不同的底层控件这是它和普通元素最本质的区别。1.1 type属性切换形态的总开关type是input最核心的属性没有之一。它的默认值是text所以你在HTML里写一个裸的input浏览器默认渲染成一个单行文本框。HTML4时代input的类型屈指可数text、password、checkbox、radio、submit、reset、file、hidden、button、image。到了HTML5一口气新增了email、url、tel、search、number、range、date、month、week、time、datetime-local、color总数一下子突破二十个。这些类型不只是长得不一样它们会改变浏览器的默认交互行为。最典型的是移动端typeemail在手机上会呼出带符号的键盘typenumber呼出数字键盘typeurl带斜杠和.com快捷键。开发者和用户感知到的控件其实是浏览器根据type替我们渲染出来的。选对type很多交互体验是白送的。1.2 name与value定义提交什么如果说type决定控件长什么样那name和value决定的就是数据层面这个框到底提交什么。name是表单数据提交时的键名。一个输入框如果没有name属性它就是一个纯粹的交互控件——用户在里面输了一堆字按下提交后端什么都收不到。这是新手最容易忽略的一点样式调得再漂亮忘写name数据永远到不了服务器。value的作用分两种情况。对于text、email这类输入型控件value是预设的初始值用户可以在页面上修改修改后的值就是最终提交的值。对于checkbox、radio这类选择型控件value是选中后提交什么——比如一个checkbox的value设为1用户勾选后提交name1不勾选就什么都不提交。这里有个经典坑如果不给radio设value一组选项都提交on后端根本分不清用户到底选了哪一项。1.3 id、class、label让控件接入页面type、name、value管的是控件本身但一个输入框要真正融入页面还需要id、class以及配套的label标签。id是控件在页面里的唯一标识作用有两个一是让label forid能把文字和控件关联起来用户点击文字就能聚焦输入框这个体验在小屏幕设备上尤其重要二是让JS通过document.getElementById快速定位到它。class纯粹用于样式挂载它不参与数据提交也不影响控件行为。我见过不少初学者用placeholder替代label觉得提示文字不就在输入框里吗还要什么label。这个习惯建议尽早改掉。placeholder在用户输入后会消失而且部分屏幕阅读器对placeholder的读取极不友好依赖它做唯一提示等于把无障碍体验放弃了。正确做法是label负责语义和可点击区域placeholder只做轻量补充。1.4 全局属性一览除了上面三个核心属性input还支持一批实用的全局属性和专有属性。列一张速查表方便写的时候对照属性作用补充说明autofocus页面加载后自动聚焦不要用在多个控件上移动端会直接弹键盘慎用autocomplete控制自动填充on、off以及username、new-password等具体语义值readonly只读仍会提交可聚焦、可复制但不能修改disabled禁用不提交不聚焦、不发事件提交时被忽略maxlength最大字符数用户无法输入更多JS也没办法通过value绕过minlength最小字符数输入不足时校验不通过placeholder占位提示不是label替代品list关联datalist提供候选值允许用户自由输入inputmode指定键盘类型numeric、decimal、tel、email等移动端友好tabindexTab键导航顺序非表单元素默认不参与Tab导航form指定所属表单让输入框可以放在form外面通过form表单id关联list属性值得单独拎出来说。它配合datalist能做出一个既能输入又能下拉选择的输入框不需要引入任何第三方组件input typetext listcities namecity placeholder选择或输入城市 datalist idcities option value北京 option value上海 option value广州 /datalist这个组合比select更灵活用户既可以从候选项里点也可以直接输入一个列表里没有的值。缺点是不同浏览器对候选项的样式和交互细节有差异但功能本身完全够用。2. 二十多种type按场景分组选型思路与每个控件的行为边界type太多一个个背没有意义关键要形成按场景选型的思维。我把二十多种类型归成四组每一组解决一类问题边界理清了选型就是顺理成章的事。2.1 文本输入类text、search、password、email、url、tel这组控件的特点是从用户那里接收字符串差异主要在于键盘形态、内置校验和浏览器附加行为。text是最通用的输入框。search在text基础上增加了一个清除输入的小叉号在WebKit内核浏览器里移动端键盘的回车键变成搜索适合站内搜索场景。password负责遮蔽输入内容额外要注意的是浏览器密码管理器对它的特殊对待——注册和登录场景建议区分autocompletenew-password和autocompletecurrent-password不然浏览器总是自动填充成旧密码体验很糟。email、url、tel三兄弟是HTML5新增的语义化输入类型。它们最重要的价值在移动端email弹出带的键盘url带斜杠快捷键tel直接切数字键盘。校验方面浏览器也会提供基本提示——email会检查格式是否合法url要求能解析成链接tel则几乎不做格式限制因为全球电话号码格式差异太大强行校验只会误伤用户。有一个细节很多人不知道加了multiple属性的email输入框允许用户输入多个邮箱地址并用逗号分隔这在做邮件群发收件人填写时非常有用。2.2 数值与日期类number、range、date、time、datetime-local、month、week、color这组控件的特征是值有明确的格式或范围浏览器借此提供专用选择界面。number配合min、max、step可以控制数值范围旁边还会出现上下步进按钮。但有个容易踩的细节number类型的值在JS里读出来依然是字符串做数值运算前需要parseFloat或Number转换。另外部分浏览器允许在number框里输入字母e因为科学计数法在数学上合法这就是为什么你明明设置了step1用户还是能敲出e——它不是数字但浏览器不拦。range是滑块适合不需要精确输入、只选大概值的场景比如音量、亮度、价格区间。它默认的最小值是0、最大值是100、步长是1。因为滑块本身不显示数值通常要配合一个output标签实时显示当前值。这里注意output和input的事件联动用input事件而不是change事件因为change要松手才触发input是拖动过程中持续触发。日期类控件date、time、datetime-local、month、week是HTML5里浏览器帮你干活的典型代表页面里直接出现日历或时间选择器省掉一堆日期插件的代码。但它们的value格式是固定的日期一定是YYYY-MM-DD不能用YYYY/MM/DD否则会被视为空值时间则是HH:mm或HH:mm:ss。这个格式很多人第一次用就被坑到赋值时一定要按ISO格式来。color调用浏览器原生取色器value返回#RRGGBB格式不支持透明度。它的实际应用场景不多但做主题色配置、简单绘图工具时很省事。2.3 勾选与单选checkbox、radio以及与select的取舍checkbox和radio经常被放在一起说但它们的语义完全不同checkbox是多选每个选项独立存在radio是单选一组内互斥。radio实现互斥靠的是name相同。同一组radio它们的name必须一致浏览器才会把它们归为一组点这个自动取消另一个。还有个使用要点一组radio里required只需要加在其中一个上整组就会校验——用户一个都没选时提交浏览器会提示必须选一个。因为required的语义是这组里必须有一个被选中。checkbox提交时有个默认值陷阱如果没写value勾选后提交的值是on而非布尔值true。后端接到的参数是nameon看着非常不明所以。所以每个checkbox都应该显式设置value1或value0这类明确的值。至于什么时候用radio什么时候用select我的经验是选项少于等于5个、且需要用户一眼看清所有选项时用radio选项多、默认值明显、用户大概率不会逐项比较时用select。前者把选项摊开在页面上后者折叠起来省空间。两者没有绝对优劣只有场景适配。2.4 按钮、文件与隐藏字段submit、reset、button、image、file、hidden这组不接收用户文本输入但每一个都有明确的动作语义。submit点击后触发表单提交reset把表单重置为初始值——注意是初始值不是清空成空白。button没有默认行为所有交互都交给JS。image是图片形式的提交按钮比较古老点击时会提交name.x和name.y两个坐标值现在的代码里基本见不到了。file是文件上传控件。它有四个关键属性accept限制文件类型按MIME类型或扩展名比如acceptimage/png或accept.jpg,.pngmultiple允许一次选多个文件capture在移动端会指定调起摄像头还是相册captureuser调前置摄像头captureenvironment调后置还有一个安全限制——因为浏览器不允许JS给file控件的value赋值你永远无法通过脚本伪造一个要上传的文件这是刻意的安全设计。hidden不渲染到页面上但会正常提交name和value。它经常用来携带不需要用户看到的参数比如用户的id、操作来源标识、分页页码等。注意一点hidden的值虽然用户看不到但用户可以通过开发者工具修改所以它只适合传输数据绝对不要放任何安全相关的机密字段。3. 从页面到后端提交链路里的每一条规则很多新手会把input当成一个孤立标签来学觉得能输入、能选中就够了。但input真正的价值在提交那一刻——用户输入的数据是怎么从浏览器走到后端的这条链路上有大量约定俗成的规则任何一个不和数据就悄悄丢了。3.1 form与input的关联方式input不能脱离form谈提交。常规写法是把输入框嵌套在form action提交地址 methodget/post内部表单的action决定数据发到哪里method决定用GET还是POST。但HTML5还提供了一个很多人不知道的form属性输入框可以放在form标签外面只要给输入框加上form表单id它依然属于那个表单。这个特性对于复杂页面布局非常有用——比如弹窗里的表单不想嵌套在页面主体结构里但又想复用同一个提交逻辑。form idsearch-form action/search methodget !-- 表单内容 -- /form !-- 这个输入框在form外面但仍然属于search-form -- input typetext namekeyword formsearch-form3.2 哪些值会被提交成功控件的判定浏览器提交表单时不是把form里所有input都一股脑发出去它有一套成功控件判定规则只有满足条件的控件才会被提交控件必须要有name属性没有name不参与提交disabled的控件不参与提交即便它有name、有值也会被忽略checkbox和radio只有在checked状态时才会提交同一组radio中没有被选中的项不提交文件上传只有在表单使用multipart/form-data编码时才会提交点击提交按钮时被点击的submit按钮本身如果带有name和value也会一并提交。这些规则里最容易被忽略的就是disabled和name。出现后端怎么收不到这个字段的问题时第一反应就应该是这个输入框有没有写name是不是被disabled了3.3 enctype与文件上传表单的enctype属性决定了提交数据的编码方式有三个值enctype用途说明application/x-www-form-urlencoded默认值键值对URL编码适合普通文本multipart/form-data文件上传二进制数据按multipart格式分段传输text/plain极少使用纯文本不编码空格等如果表单里有file输入框但enctype忘了改文件内容不会出现在请求里后端拿到的是空文件或者文件名伪元素。这个坑在初学阶段出现频率极高。3.4 用FormData在JS里接管表单现代前端开发里表单提交不一定走form的原生刷新跳转很多时候用JS的fetch异步发送。这时候FormData对象是读取表单字段的最优雅方式const form document.querySelector(#my-form); const formData new FormData(form); // 读取单个字段 const email formData.get(email); // 遍历所有字段 for (const [key, value] of formData.entries()) { console.log(key, value); } // 配合fetch发送 fetch(/api/register, { method: POST, body: formData });FormData会自动应用成功控件规则——disabled的控件不会被收集没勾选的checkbox也不会出现和原生提交行为保持一致。另外FormData里的值是字符串文件除外哪怕你input typenumberget出来依然是字符串。3.5 form系列属性不嵌套也能提交除了form属性input还支持一批以form前缀开头的属性formaction、formenctype、formmethod、formnovalidate、formtarget。它们的作用是让特定按钮覆盖所属表单的默认行为。最典型的是草稿保存和提交用不同按钮form action/submit methodpost input typetext nametitle !-- 默认提交到 /submit -- button typesubmit发布/button !-- 这个按钮走单独地址不做校验 -- button typesubmit formaction/save-draft formnovalidate存草稿/button /form点击存草稿时数据不会发到/submit而是发到/save-draft并且会跳过表单校验。这种细节原生就支持不需要额外写JS判断点击了哪个按钮。4. 不写JS的校验方案约束API与CSS状态反馈input最被低估的能力之一是原生表单校验。通过几个HTML属性就能实现大部分常规校验完全不写JavaScript。配合CSS伪类还能实时反馈校验状态。4.1 约束属性怎么用常用的约束属性有这六个required必填pattern正则表达式校验min/max数值和日期类的上下限step数值步长minlength/maxlength字符长度限制maxlength是硬限制直接不允许输入更多minlength是软校验输入不足时提交报错。一个组合示例注意pattern只针对text、search、url、tel、email和password这类文本型输入框label forphone手机号/label input typetel idphone namephone required pattern1[3-9]\d{9} placeholder11位手机号 用户输入不匹配pattern时点击提交浏览器会弹出提示并阻止提交焦点定位到该输入框。这一整套行为都是浏览器内置的不需要一行JS。4.2 校验触发时机与绕过手段原生校验的触发时机值得说清楚默认情况下只有提交表单或调用reportValidity()时校验提示才会出现。但浏览器会对invalid事件做特殊处理——如果用户提交过一次校验失败之后每失焦一次也会触发一次校验反馈这是浏览器的互动校验逻辑。如果不想让某个按钮触发校验用之前提到的formnovalidate属性。如果整个表单想完全关闭原生校验在form上加novalidate。这时候错误提示不会弹出但节点的validity状态依然会更新CSS伪类照样生效——这个组合在想自己控制错误提示样式时非常有用。4.3 CSS伪类做实时反馈约束API和CSS伪类是一对黄金搭档。input的校验状态会映射成以下伪类/* 必填项 */ input:required { border-color: #aaa; } /* 非必填项 */ input:optional { border-bottom: 1px dashed #ccc; } /* 合法 */ input:valid { border-color: #4caf50; } /* 非法 */ input:invalid { border-color: #f44336; } /* 数值在范围内 */ input:in-range { background: #e8f5e9; } input:out-of-range { background: #ffebee; } /* 只读与禁用 */ input:read-only { background: #f5f5f5; } input:disabled { opacity: 0.5; } /* 占位提示是否可见 */ input:placeholder-shown { border-color: #bbb; }实战中一个很实用的组合是既不是placeholder状态、校验又不通过时才标红——避免用户还没开始输入就一片红input:invalid:not(:placeholder-shown) { border-color: #f44336; box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.1); }这里有个细节没有required的空输入框在浏览器校验规则下是valid的。只有required且为空时才invalid。所以上面的组合能很精准地表达用户输入了但格式不对的状态。4.4 校验文案与自定义错误原生校验的提示文案是浏览器根据语言环境生成的你没法直接改。比如pattern不匹配时浏览器会提示请与所请求的格式保持一致但这个格式到底是什么用户看不出来。要让提示更友好需要用JS调用setCustomValidityconst phoneInput document.querySelector(#phone); phoneInput.addEventListener(input, function () { const pattern /^1[3-9]\d{9}$/; if (this.value !pattern.test(this.value)) { this.setCustomValidity(手机号格式不对请检查前两位和总位数); } else { this.setCustomValidity(); } });注意setCustomValidity()表示清除自定义错误。只要自定义错误消息非空这个输入框就是invalid状态原生校验会阻止提交并显示你设置的文案。如果你用了novalidate关闭原生提示再自己渲染错误节点那这套消息体系就完全由你自己掌控了——这是很多企业级项目的做法。5. 实战拆解一个带实时校验的注册表单前面讲了一大堆规则把它们落在一个完整例子里才真正有感觉。我以一个注册表单为例把input的选型、校验和提交流程完整走一遍。5.1 表单结构与字段设计这个注册表单包含用户名、邮箱、密码、确认密码、性别单选、技术栈多选、同意协议必勾、提交按钮。form idregister-form action/api/register methodpost novalidate div classform-group label forusername用户名/label input typetext idusername nameusername required minlength3 maxlength12 placeholder3-12个字符 autocompleteusername /div div classform-group label foremail邮箱/label input typeemail idemail nameemail required autocompleteemail placeholderyouexample.com /div div classform-group label forpassword密码/label input typepassword idpassword namepassword required minlength8 maxlength20 autocompletenew-password placeholder至少8位 /div div classform-group label forconfirm-password确认密码/label input typepassword idconfirm-password nameconfirm_password required autocompletenew-password placeholder再次输入密码 /div fieldset classform-group legend性别/legend labelinput typeradio namegender valuemale required 男/label labelinput typeradio namegender valuefemale 女/label labelinput typeradio namegender valueother 其他/label /fieldset div classform-group label技术栈/label labelinput typecheckbox nameskills valuehtml HTML/label labelinput typecheckbox nameskills valuecss CSS/label labelinput typecheckbox nameskills valuejs JavaScript/label /div div classform-group label classcheckbox-line input typecheckbox nameagree value1 required 我已阅读并同意服务协议 /label /div button typesubmit注册/button /form我特意在form上加了novalidate因为这里要演示自己接管校验行为。字段设计上有几个值得留意的点gender的required只加在第一个radio上整组就生效skills多个checkbox使用相同nameskills后端收到的是skillshtmlskillscss这样的多值参数密码框的autocomplete明确写了new-password防止浏览器的密码管理器干扰注册场景。5.2 CSS让校验状态看得见因为form加了novalidate原生提示弹窗不会出现但:valid、:invalid伪类依然会随输入状态更新。样式上做状态反馈.form-group { margin-bottom: 18px; } input { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s, box-shadow 0.2s; } /* 非空且非法标红 */ input:invalid:not(:placeholder-shown) { border-color: #e74c3c; box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.1); } /* 非空且合法标绿 */ input:valid:not(:placeholder-shown) { border-color: #2ecc71; box-shadow: 0 0 0 3px rgba(46, 204, 113, 0.1); } /* 聚焦时还原主色避免提示色抢焦点反馈 */ input:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2); } /* 勾选框不需要进度色 */ input[typecheckbox], input[typeradio] { width: auto; box-shadow: none; border: none; }这里有一个视觉设计上的经验:valid状态下给绿色反馈要克制。如果每个输入框刚填完一个合法字符就变绿页面会显得非常吵。我做项目时通常只显示非法的红色反馈合法状态只在提交失败时作为一种对比提示。上面代码保留绿色是为了演示实际项目请按产品气质取舍。5.3 JS补齐依赖判断的逻辑原生约束API处理不了确认密码是否一致这种跨字段校验这部分必须JS介入。我把校验逻辑写在input事件里做到实时反馈const form document.querySelector(#register-form); const passwordInput document.querySelector(#password); const confirmInput document.querySelector(#confirm-password); function validateConfirmPassword() { if (confirmInput.value ! passwordInput.value) { confirmInput.setCustomValidity(两次输入的密码不一致); } else { confirmInput.setCustomValidity(); } } passwordInput.addEventListener(input, validateConfirmPassword); confirmInput.addEventListener(input, validateConfirmPassword); form.addEventListener(submit, function (event) { // 所有字段的校验结果 const isValid form.checkValidity(); // 确认密码的自定义校验 validateConfirmPassword(); // 校准后确认密码是否合法 const confirmValid confirmInput.checkValidity(); if (!isValid || !confirmValid) { event.preventDefault(); // 把页面滚动到第一个非法字段附近 const firstInvalid form.querySelector(:invalid); if (firstInvalid) { firstInvalid.scrollIntoView({ behavior: smooth, block: center }); firstInvalid.focus(); } } else { event.preventDefault(); // 演示时不真提交 const formData new FormData(form); console.log(Object.fromEntries(formData.entries())); alert(校验通过注册数据已输出到控制台); } });这段代码补充了三层东西第一层每次密码或确认密码变化时实时同步自定义校验消息第二层提交时统一调用checkValidity()触发全表单校验第三层用:invalid选择器找到第一个非法控件滚动定位并聚焦提升操作效率。有几个细节值得注意。setCustomValidity会改变控件的validity.customError如果不及时清空即使两个密码已经一致控件依然是invalid。所以每次输入事件里都要根据最新状态重新设置。form.checkValidity()只会报告原生校验自定义错误需要单独调用confirmInput.checkValidity()才会反映到validity上。最后Object.fromEntries(formData.entries())看着方便但遇到同名多值的skills字段时后一个值会覆盖前一个所以示例里用Object.fromEntries只是方便在控制台看数据真实提交直接用formData作为fetch的body即可。6. 高频踩坑记录从兼容性到输入法最后这部分是我在真实项目里踩过、也看别人踩过的坑。多数问题不在教程里但一旦遇到排查半天才发现是input的某个隐藏行为。6.1 pattern的正则是全匹配而不是部分匹配pattern属性里写的正则浏览器会自动加上^(?:...)$要求整个输入值必须完整匹配正则而不是包含匹配。很多人在这里栽跟头写pattern[a-zA-Z]以为允许输入abc123因为包含字母实际浏览器要求整串都由字母组成abc123会校验失败因为1和3不匹配。如果需要包含某个规则的校验要显式留出前后通配。比如字符串中包含数字写成pattern.*\d.*前面和后面的.*都不能省。这类问题很难靠肉眼发现建议每次写完pattern都手动试几个边界值尤其是前缀目标后缀的组合。6.2 disabled和readonly提交行为完全不同disabled和readonly都能让输入框不可编辑但提交行为天差地别disabled的控件完全不参与提交readonly的控件正常参与提交。应用场景上的建议如果只是用户不能改但这个值后端需要用readonly如果是条件不满足时暂时不接收这个数据用disabled。有个常见做法是下拉选了A之后把B输入框置灰此时如果置灰用disabled实现提交时B字段直接消失后端拿不到这个值很容易造成意想不到的bug。正确的做法是视觉上置灰提交上保留字段那就用readonly加个样式而不是disabled。6.3 日期类控件在移动端的表现与降级日期类控件在不同平台差异很大。桌面端Chrome和Edge有成熟的日历组件但部分桌面浏览器对某些类型支持不完整会直接降级成text。移动端iOS Safari的日期控件体验和桌面端完全不同特别是week和month类型在部分环境里表现不稳定。降级带来的问题是灾难性的typedate在支持的浏览器里value格式是YYYY-MM-DD降级成text后用户输入什么格式完全不可控后端接到的可能是2024年3月5日也可能是03/05/2024。我的建议是需要兼容旧环境时提前做特性检测不支持的场景直接引入统一的日期选择组件否则表单数据格式会变成一场灾难。还有一个细节typedate的placeholder通常不生效因为浏览器觉得它有自己的格式提示。自定义placeholder前先确认浏览器是否按text模式降级了。6.4 中文输入法下的input事件陷阱做搜索框或即时过滤功能时中文输入法是一个永远绕不开的坑。用户在输入框里打拼音时input事件会在拼音组合过程中不断触发但此时输入框里的值是拼音字母不是最终汉字。如果用input事件做实时搜索用户打zhong的中间过程会触发三次无意义搜索。处理办法是监听compositionstart和compositionend事件用一个标志位标记当前是否处于输入法组合状态const searchInput document.querySelector(#search); let isComposing false; searchInput.addEventListener(compositionstart, () { isComposing true; }); searchInput.addEventListener(compositionend, (e) { isComposing false; // 组合结束这时候触发一次搜索 doSearch(e.target.value); }); searchInput.addEventListener(input, (e) { if (!isComposing) { doSearch(e.target.value); } });这个坑在移动端中文输入法同样存在。很多看似搜索卡顿的问题排查到最后都是因为输入法组合期间触发了大量无效请求。加上这个标志位性能立刻改善。6.5 autocomplete与密码管理器的相处之道autocompleteoff是开发者最常写的属性之一但它越来越不是一个可靠的禁用自动填充指令。现代浏览器会综合判断字段的name、type、id和近似的输入模式决定是否自动填充很多情况下开发者设置的off会被直接无视。与其和浏览器对抗不如顺着它的逻辑走。登录页的密码框用autocompletecurrent-password注册页的密码框用autocompletenew-password用户名框用autocompleteusername验证码用autocompleteone-time-code。这样浏览器能更准确地理解页面意图反而不会有乱填充的烦恼。最后补一个我自己的习惯每次写完一段表单我都会在浏览器里打开控制台手动查一遍form.elements看哪些字段会被收集、哪些没有name、哪些被disabled误伤。这个习惯帮我在上线前拦下过不少字段丢数据的bug。input的坑大多不是某个语法不会写而是因为规则太多、平时不太容易全部想起来——把这张属性表和提交规则存下来需要的时候翻一翻比临时去查文档要省心得多。