做了几年前端开发,接手过不少新人带教和作业评审。说句实在话,不管后来技术栈换成React还是Vue,凡是能把HTML里的表单和表格玩明白的,上手业务系统的效率往往都不会太差。这两块看起来基础,里面的门道却一点也不少——一个考验对输入控件的理解,一个考验对二维结构的组织能力。今天不聊虚的,直接拆两个我反复批改过的经典作业案例:学生注册表单和课程表制作,从需求分析、结构设计到具体代码,一步一步还原整个思考过程,顺带把新手最容易踩的坑都标出来。
这两个案例之所以经典,是因为它们几乎覆盖了HTML布局阶段最重要的知识点:form表单的action、method、各类input控件、label关联、下拉框、多选框、表格的tr/td、行列合并、thead/tbody分组、还有最基本的CSS美化。无论你是刚学完HTML标签想找练手项目,还是做作业卡在某个边界情况,或者只是想看看别人怎么组织表单和表格代码,这篇都值得你花十分钟读完。
1. 内容整体设计与思路拆解
1.1 为什么作业里总拿“表单”和“表格”来练手
很多新手不理解,觉得表单就是几个输入框叠在一起,表格就是几行几列摆数据,有什么好练的。实际上这两个东西背后对应的,是Web开发里最核心的两种信息组织形态:数据的采集和数据的展示。
表单练的是你如何设计一条“数据收集链路”:用户要填哪些字段、每个字段用什么控件最合适、哪些是必填哪些是选填、提交到哪个地址、用什么方式提交。别看一个注册表单小,你把它做清楚了,等于把业务系统里所有表单页面的底层逻辑都过了一遍。桌面软件时代有句老话叫“无对话框不成软件”,放到Web时代就是“无表单不成系统”。
表格练的是你对二维结构数据的表达能力。课程表、成绩单、排班表、报表,这些在真实业务里天天见。表格的难点在于它不只是画格子,还要处理“跨行”“跨列”“表头分组”“数据对齐”这些真实需求。能把一个课程表在不借助任何框架的情况下用原生HTML写对,你对表格语义的理解就过关了。
1.2 两个案例的定位差异与布局选型
第一个案例“学生注册表单”,我把它定位为“控件组合与页面布局”训练。它涉及超过六种表单控件,每一种都有不同的默认行为和交互方式,放在一起时很容易排版混乱。这里重点考察你如何用最合理的方式把控件“摆整齐”,同时保证label和控件绑定正确,让用户点文字也能聚焦到输入框。
第二个案例“课程表”,我把它定位为“表结构组织与合并逻辑”训练。它比普通数据表多了一层复杂性——课程有跨行跨列的情况,上午和下午也是不同的节次区间。这里重点考察你对rowspan和colspan的理解,以及对表格语义分组(thead、tbody、tfoot)的掌握。
在布局选型上,我特意保留一套传统的“表格嵌套”方案,再说说现在更推荐的“语义化结构+CSS”方案。不是说表格布局一无是处,它当年能统治Web十几年,就是因为浏览器兼容性和排版稳定性都极强。但在现代Web标准下,页面结构和表现分离才是正路。两个方案对照着看,你对为什么提倡“能用CSS就不用表格排版”会有更直观的理解。
2. 核心细节解析与实操要点
2.1 表单控件的选型逻辑
做表单第一步不是写代码,是画字段清单。把你要收集的信息一一列出来,再逐个想清楚用哪种控件。以学生注册表单为例:
| 信息字段 | 推荐控件 | 选型理由 |
|---|---|---|
| 姓名 | 单行文本框 input text | 短文本输入,用一个text足够 |
| 学号 | 单行文本框 input text | 学号虽常为数字,但不能参与运算,用text而不用number |
| 性别 | 单选按钮 radio | 选项固定且需互斥,两个radio同一name |
| 出生日期 | 日期选择 input date | 原生日期控件,作业阶段最省事 |
| 专业 | 下拉选择 select | 选项较多,收起状态节省页面空间 |
| 选修方向 | 多选复选框 checkbox | 可多选,每个checkbox独立name |
| 邮箱 | 单行文本框 input email | 自带格式校验,移动端会弹对应键盘 |
| 自我介绍 | 多行文本框 textarea | 长文本输入,可换行 |
选型的原则很简单:能用语义化控件解决的,不要自己写。比如性别,有人喜欢用两个checkbox模拟单选,那是找麻烦。radio天然具备互斥性,同一个name下只能选一个,这正是你要的效果。邮箱用input type="email",在支持H5的浏览器里会自动校验格式,省掉一部分前端判断逻辑。
有几个容易忽略的细节我在批作业时反复强调过:checkbox的name要加方括号后缀,比如name="hobby[]",因为后端接收多选值时通常按数组处理;radio必须设置一个选中值checked属性,否则默认一个都不选,提交时该字段就是空;select占位提示要做到第一项option上,value留空,用户不选时提交的就是空字符串。
2.2 label关联的两种方式
label标签的正确用法是很多人栽跟头的地方。它的作用是让用户点击文字时也能聚焦到对应的控件,这不仅是体验问题,更是无障碍访问的基本要求。实现关联有两种方式:
第一种是显式关联,通过for属性指向控件的id:
<label for="username">用户名</label> <input type="text" id="username" name="username">第二种是隐式包含,直接把控件嵌在label内部:
<label> 用户名 <input type="text" name="username"> </label>显式关联的兼容性更好,代码更清晰,我推荐使用。实际批作业时看到最多的问题就是for写了,但控件的id对不上;或者一个页面上有好几个id相同的控件。这些都是低级但容易犯的错。检车方法很简单:浏览器里点击文字,光标如果没跳到对应输入框,说明关联失效了。
2.3 表格合并单元格的边界思路
表格里最容易写错的就是合并。很多新手不了解浏览器处理合并的底层方式,其实浏览器先按“通格”数来计算表格的总列数。所谓通格,就是最上面一行(若有colspan则不合并计算)各单元格展开后的总列数。例如一个3列表格,第二行的第一个单元格写了colspan="2",那这一行就只剩两个td位置,后面再有一个td就是整行占满,必须已经够了总列数3。
再讲rowspan,它是纵向合并。第十八讲里的情况是:第1节课和第2节课在表格中占同一行,但在课程表里它们是跨行,所以合并。写rowspan的那个单元格要放在最上面那行,下面被合并的行里对应的位置要删掉。很多新手合并后总行数对不上,就是忘了删下面那行的单元格。
具体的合并思路用标准表述是这样:先画通格,再算合并,最后删多余的格子。先脑子里把表格画成规则矩阵,标出哪些格子合并了,再去代码里写,基本不会错。
3. 实操过程与核心环节实现
3.1 案例一完整实现:学生注册表单
先做一个干净的版本,包含所有常见字段,再用CSS做一个清爽的外观。下面是完整代码,我会拆开一句句解释关键点。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>学生注册表单</title> <style> body { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; background: #f5f7fa; margin: 0; padding: 30px; } .register-wrapper { max-width: 720px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 30px 40px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); } h2 { text-align: center; color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 12px; margin-top: 0; } .form-row { display: flex; align-items: center; margin-bottom: 18px; } .form-row label { width: 100px; color: #34495e; font-weight: 600; } .form-row input[type="text"], .form-row input[type="email"], .form-row input[type="date"], .form-row select, .form-row textarea { flex: 1; padding: 8px 12px; border: 1px solid #dcdfe6; border-radius: 6px; font-size: 14px; transition: border-color 0.2s; } .form-row input:focus, .form-row select:focus, .form-row textarea:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 2px rgba(52,152,219,0.2); } .radio-group, .checkbox-group { flex: 1; display: flex; gap: 20px; flex-wrap: wrap; } .radio-group label, .checkbox-group label { width: auto; font-weight: 400; } .btn-group { display: flex; justify-content: center; gap: 15px; margin-top: 24px; } .btn-group button { padding: 10px 35px; border: none; border-radius: 6px; font-size: 15px; cursor: pointer; } .btn-group .submit-btn { background: #3498db; color: #fff; } .btn-group .reset-btn { background: #ecf0f1; color: #2c3e50; } </style> </head> <body> <div class="register-wrapper"> <h2>学生注册</h2> <form action="register.php" method="POST"> <div class="form-row"> <label for="student-name">姓名</label> <input type="text" id="student-name" name="name" placeholder="请输入真实姓名" required> </div> <div class="form-row"> <label for="student-id">学号</label> <input type="text" id="student-id" name="sid" placeholder="请输入学号" required> </div> <div class="form-row"> <span class="label-text">性别</span> <div class="radio-group"> <label> <input type="radio" name="gender" value="male" checked> 男 </label> <label> <input type="radio" name="gender" value="female"> 女 </label> </div> </div> <div class="form-row"> <label for="birthday">出生日期</label> <input type="date" id="birthday" name="birthday"> </div> <div class="form-row"> <label for="major">专业</label> <select id="major" name="major"> <option value="">请选择专业</option> <option value="computer">计算机科学与技术</option> <option value="software">软件工程</option> <option value="data">数据科学</option> <option value="ai">人工智能</option> </select> </div> <div class="form-row"> <span class="label-text">选修方向</span> <div class="checkbox-group"> <label> <input type="checkbox" name="hobby[]" value="frontend"> 前端开发 </label> <label> <input type="checkbox" name="hobby[]" value="backend"> 后端开发 </label> <label> <input type="checkbox" name="hobby[]" value="ai"> 机器学习 </label> <label> <input type="checkbox" name="hobby[]" value="design"> UI设计 </label> </div> </div> <div class="form-row"> <label for="email">邮箱</label> <input type="email" id="email" name="email" placeholder="example@mail.com"> </div> <div class="form-row"> <label for="intro">自我介绍</label> <textarea id="intro" name="intro" rows="4" placeholder="简单介绍一下自己..."></textarea> </div> <div class="btn-group"> <button type="submit" class="submit-btn">提交注册</button> <button type="reset" class="reset-btn">重新填写</button> </div> </form> </div> </body> </html>这段代码里几个用心的细节:所有控件都加了label,并且label有统一的width,让整列对齐;radio和checkbox用span.label-text做左侧标题,宽度和label一致,保证视觉对齐;input在focus状态下有蓝色边框和阴影,提升交互反馈;必填字段加了required属性,浏览器会自动提示;textarea用rows属性控制默认高度,而不是CSS写死高度,这样在移动设备上更灵活。
3.2 老式表格嵌套布局方案
如果你只是想快速交作业,不想引入flex,传统做法是用table包form。这套方案的写法我反而建议所有初学者练一遍,因为你能从中看到“在不依赖弹性布局的年代,人们是如何用表格实现精确对齐的”。
<form action="register.php" method="POST"> <table class="form-table"> <tr> <td class="label-cell">姓名</td> <td><input type="text" name="name" required></td> </tr> <tr> <td class="label-cell">学号</td> <td><input type="text" name="sid" required></td> </tr> <tr> <td class="label-cell">性别</td> <td> <label><input type="radio" name="gender" value="male" checked> 男</label> <label><input type="radio" name="gender" value="female"> 女</label> </td> </tr> <tr> <td class="label-cell">出生日期</td> <td><input type="date" name="birthday"></td> </tr> <tr> <td class="label-cell">专业</td> <td> <select name="major"> <option value="">请选择专业</option> <option value="computer">计算机科学与技术</option> <option value="software">软件工程</option> <option value="data">数据科学</option> </select> </td> </tr> <tr> <td class="label-cell">邮箱</td> <td><input type="email" name="email"></td> </tr> <tr> <td class="label-cell">自我介绍</td> <td><textarea name="intro" rows="4"></textarea></td> </tr> <tr> <td class="label-cell" colspan="2" style="text-align:center;"> <button type="submit">提交</button> <button type="reset">重置</button> </td> </tr> </table> </form>表格嵌套的优点是天然的对齐能力:左边一列td宽度由内容撑开,右边一列自动等宽,垂直和水平对齐都很稳定,老浏览器也不会出问题。缺点是结构语义不清晰,table在视觉上并不是真正的数据表格,使用屏幕阅读器时会被误解。所以这个方案我称之为“应付作业方案”——不建议用于真实项目,但作为理解表格对齐原理的练习非常好。现代项目里用flex或grid替代,结构更干净。
3.3 案例二完整实现:课程表
课程表的难点在于合并。一份典型课表包含上午和下午两个时段,每节课是有时间的。我用一个5列(周一至周五)、每列5节课的标准模型,其中上午第1、2节为一节大课(跨2行),下午第5节有时跨3、4节。我把它简化成统一结构,方便讲解合并逻辑。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>课程表</title> <style> body { font-family: "Microsoft YaHei", sans-serif; background: #f8f9fa; padding: 30px; display: flex; justify-content: center; } .schedule { border-collapse: collapse; width: 100%; max-width: 900px; background: #fff; box-shadow: 0 4px 16px rgba(0,0,0,0.08); } .schedule caption { font-size: 22px; font-weight: 700; padding: 15px; caption-side: top; color: #2c3e50; } .schedule th, .schedule td { border: 1px solid #dce1e6; padding: 14px 10px; text-align: center; vertical-align: middle; } .schedule thead th { background: #3498db; color: #fff; font-weight: 600; } .schedule .time-col { background: #f0f4f8; font-weight: 600; width: 90px; } .schedule .section-label { background: #e8eef3; font-weight: 700; color: #2c3e50; } .schedule tbody tr:nth-child(even) td:not(.time-col) { background: #f9fbfd; } .course { background: #eaf2f8; border-radius: 6px; padding: 8px; font-size: 14px; color: #1a5276; font-weight: 600; } </style> </head> <body> <table class="schedule"> <caption>大二上学期课程表</caption> <thead> <tr> <th>节次</th> <th>周一</th> <th>周二</th> <th>周三</th> <th>周四</th> <th>周五</th> </tr> </thead> <tbody> <!-- 上午开始 --> <tr> <td class="section-label" rowspan="2">上午</td> <td class="time-col">第1节</td> <td class="time-col">第2节</td> <td class="time-col">第3节</td> <td class="time-col">第4节</td> <td class="time-col">第5节</td> </tr> </tbody> </table> </body> </html>等一下,我上面这段写乱了。考试的tile逻辑让我重新组织。标准课程表的正确写法是“节次”列单独占一列,每个td内写节次和时间。上午如果从第1节到第4节是连续的第1-4节,则上午标签要跨4行。我把结构重写一个清晰版本。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>大一课程表</title> <style> body { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; background: #f0f3f7; padding: 30px; } .schedule { border-collapse: collapse; width: 100%; max-width: 960px; margin: 0 auto; background: #fff; box-shadow: 0 4px 16px rgba(0,0,0,0.08); } .schedule caption { font-size: 22px; font-weight: 700; padding: 16px; color: #2c3e50; caption-side: top; } .schedule th, .schedule td { border: 1px solid #d9dfe5; padding: 12px 8px; text-align: center; vertical-align: middle; } .schedule thead th { background: #3a7bd5; color: #fff; font-weight: 600; } .schedule .time-col { background: #f4f6f9; font-weight: 600; width: 90px; } .schedule .noon-label { background: #eaf0f6; font-weight: 700; color: #2c3e50; font-size: 16px; letter-spacing: 4px; } .course { display: inline-block; background: #e8f0fe; color: #1a3a6b; border-radius: 6px; padding: 8px 10px; font-size: 14px; font-weight: 600; line-height: 1.4; width: 90%; } .course.blue { background: #e8f0fe; } .course.green { background: #e6f6ec; color: #216c46; } .course.orange { background: #fef4e6; color: #a45d0a; } .course.purple { background: #f3e8fa; color: #6c2d8e; } </style> </head> <body> <table class="schedule"> <caption>大一第二学期课程表</caption> <thead> <tr> <th>节次</th> <th>周一</th> <th>周二</th> <th>周三</th> <th>周四</th> <th>周五</th> </tr> </thead> <tbody> <!-- 上午:第1-4节,合并行标签"上午"跨4行 --> <tr> <td class="noon-label" rowspan="4">上午</td> <td class="time-col">第1节<br>8:00-8:45</td> <td><span class="course blue">高等数学</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course purple">线性代数</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course orange">程序设计</span></td> </tr> <tr> <td class="time-col">第2节<br>8:55-9:40</td> <td><span class="course blue">高等数学</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course purple">线性代数</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course orange">程序设计</span></td> </tr> <tr> <td class="time-col">第3节<br>10:00-10:45</td> <td><span class="course orange">程序设计</span></td> <td><span class="course purple">线性代数</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course orange">程序设计</span></td> </tr> <tr> <td class="time-col">第4节<br>10:55-11:40</td> <td><span class="course orange">程序设计</span></td> <td><span class="course purple">线性代数</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course orange">程序设计</span></td> </tr> <!-- 下午:第5-6节,中间穿插第5节大课跨2行, 这里为简化结构,我只展示第5节和第6节,上午跨4行,下午跨2行 --> <tr> <td class="noon-label" rowspan="2">下午</td> <td class="time-col">第5节<br>14:00-14:45</td> <td><span class="course green">体育</span></td> <td><span class="course orange">程序设计</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course purple">心理健康</span></td> </tr> <tr> <td class="time-col">第6节<br>14:55-15:40</td> <td><span class="course green">体育</span></td> <td><span class="course orange">程序设计</span></td> <td><span class="course blue">高等数学</span></td> <td><span class="course green">大学英语</span></td> <td><span class="course purple">心理健康</span></td> </tr> </tbody> </table> </body> </html>这个版本里的关键操作是:上午标签td上写了rowspan="4",它竖向占了4行,那么后面跟着的第2、3、4行里就不要再写“上午”这个td了。这样表格的“上午”标签只在视觉上出现一次,实际占用4个格子。同理“下午”标签rowspan="2”。
注意每行的列数保持一致:第一行有“节次列”(含上午标签)+“第1节”+周一~周五5个课程td,一共7个单元格。第二行因为上午标签被第一行的rowspan占了,所以这里只需要“第2节”+5个课程td,共6个单元格。第三、四行同理。这种“被合并的行减少单元格数量”的规则,是合并单元格的核心,也是新手最容易数不清列数导致错位的原因。
3.4 表单与表格的样式美化思路
作业里通常要求“页面整洁美观”,所以样式不能省。我的美化原则有三条:统一间距、统一边框、统一颜色。表单页面用flex实现表单行布局,每个label固定宽度,输入控件自适应剩余宽度,加上focus状态和圆角边框,整体观感就会干净很多。表格页面用border-collapse:collapse去掉单元格之间的双边框,颜色浅淡柔和,表头用深蓝底色白色文字,隔行变色让数据扫描起来更舒服。
这里插一句:border-collapse:collapse可能是表格样式里性价比最高的一个属性。不写它的时候,每个单元格各自画边框,邻接处会出现两条平行线,看起来像是表格有多重边框,特别粗糙。加了collapse后所有相邻边框合并为一条,整个表格立刻就“专业”了。新手做表格作业,如果觉得自己的表格发虚、线条乱飞,十有八九是忘了collapse。
4. 常见问题与排查技巧实录
4.1 表单字段排版乱、label点了没反应
这是作业里出现频率最高的两个问题,本质都在label上。
字段排版乱,先检查是不是所有label的宽度不一致。用flex布局时,label设了统一的width后,右边的input就会在一条垂直线上对齐。如果你发现某个input总是偏左或偏右,去看它所在的form-row结构是不是和其他行一致,是不是多套了一层div。
label点了没反应,就检查for属性和id是否完全一致。注意大小写,HTML的id是敏感的,for="userName"和id="username"不匹配。还有一种情况是你用隐式包含时把input放进了label里,却没有设置for,这种情况点击文字也能触发,因为浏览器会自动建立关联。但如果你把input放在label外面,又没有for,那点击文字就什么都不发生。
4.2 表格出现双边框或单元格错位
双边框就一句话:在table上加上border-collapse: collapse;。还有一种情况是你同时给table和td都设置了border,且值不同,collapse之后会取较大值或冲突值,看起来怪。解决办法是把边框规则只写在th/td上,table本身不设border,只用collapse。
单元格错位分两种。第一种是总列数不一致,某一行的td数量比其他行多或少。这种问题的修复方法是从“通格”入手——把每行的td按展开的列数相加,必须等于同一总数。第二种是合并本身写错了,比如rowspan跨行后,被跨的那几行里本来该删的单元格没删,导致后续所有单元格跟着偏移。你的课程表如果从某一行开始看起来“整体向右挪了一格”,八成就是这种情况。
修正的方法是:先把表格内容清空,只保留最小结构,逐行数单元格个数,确保每一行在“合并前”的展开列数相等,再逐步把课程填回去。
4.3 select默认不选中、checkbox无法多选分组的坑
select想要有占位提示,就写一行<option value="">请选择专业</option>。注意value要留空,这样用户没选择时提交的值就是空字符串,后端判断起来也方便。如果这行的value写成了某些真实值,用户一旦提交,这个提示项就会被当作真实选项传过去,这是数据质量的隐患。
checkbox有一个比较隐蔽的坑:如果你没有给checkbox的name加方括号,后端很可能只会收到最后一个被选中的值,而不是一组值。原因是同一name下有多个checkbox时,浏览器把它们按同样name处理,提交时同名数据以逗号连成一串,不同后端语言解析方式不一样。为了统一,一般是name="hobby[]"这种带数组标志的写法,Spring等框架会直接绑定为String数组或List。作业阶段知道这个约定即可。
4.4 更多小的坑
表格里的th和td语义不能混用。表头那行的列名必须用th,浏览器和搜索引擎会把它理解为“这个表的列的标签”,也有利于屏幕阅读器读列头。如果你全部用td,视觉上没什么区别,但语义丢了。我批作业时看到每行都是td,通常会给一个注释:“表头请用th”。
textarea的行列控制用rows和cols属性,而不是硬设width和height。textarea默认可以拉伸,如果你CSS设了固定height,右下角的拖拽手柄仍会把内容拖出边界。做作业时我会顺手在CSS里写一行textarea { resize: vertical; }限制只能上下拉伸,页面体验会好很多。
input的placeholder不能替代label。placeholder是在输入框里有内容时消失的灰色提示,不是字段名本身。如果忘了放label,为空时看起来像有提示,真正填写时用户反而不知道这一项是什么,可访问性也差。作业要求里如果有“表单元素需使用label”,占位符不能直接算数。
5. 作业之外的延伸:这两个案例在真实开发里怎么演化
5.1 表单从手写HTML到组件化
我们作业里手写的原生表单,到了真实项目里一般会封装成组件,比如输入框组件统一踩点,下拉选择组件统一拖动。但万变不离其宗,组件内部最终调的标签和属性,还是作业里那套东西。表单的校验也不再简单依赖required属性,而是用正则库配合框架的校验规则,实时提示、异步校验、动态禁用提交按钮。最核心的数据流转思路不变:控件name是后台字段的映射,用户输入只是value,提交时把这些value组装成请求体发给后端。想通了作业里的action和method,理解框架的提交逻辑就是一通百通。
5.2 表格从HTML标签到数据网格组件
作业里的课程表是静态HTML,真实业务里的表格大多是动态数据:从接口拉记录,渲染成行,支持排序、筛选、分页、行选择。但组件的报表其实还是在底层用table加一堆JavaScript操作DOM。理解了td/th、rowspan/colspan这些基础,用数据表格组件时心里就有底了——为什么配置项里会有spanMethod,为什么某些跨行合并要以rowspan为维度,为什么表头配置要分列。课程表这个作业做明白的人,学这些组件往往更快。
5.3 响应式布局的新思路
作业页面通常只适配电脑屏幕,但真实项目里表单和表格都要适配手机。表单在手机上通常变成单列布局,label压到输入框上方,宽度100%。表格则要复杂得多:一种做法是横向滚动,加一个容器设overflow-x: auto;另一种是卡片式重组,用媒体查询把每条记录的td隐藏掉,改用div卡片展示。没有空间横向展示多列时,优先考虑卡片展开模式。但底层排序、筛选逻辑仍是围绕表格数据展开的,所以做作业时把数据组织好,比单纯换个移动端框架重要。
在我经手的培训和带教经历里,能提前把表单label关联、表格合并规则、语义化标签这些基本功吃透的人,后面接触框架做组件配置特别快。反观一些上来就抄框架源码却没做过原生表单的新人,遇到自定义校验、动态增删行、复杂表头合并且经常手足无措。所以这两个经典作业案例,建议不要当成“交差”的作业去抄,而是当成理解浏览器原生能力的敲门砖去练,一份代码加注释加变体,做扎实了比背十个框架配置都有用。