简介:一套基于Html+Css+JavaScript实现的人机交互大作业项目,完整模仿了QQ与中国银行的常用界面及交互流程。项目代码全部测试运行正常,适合计算机相关专业学生用于课程设计、毕业设计或前端入门学习。浏览时建议从start.html开始,QQ模块实现了基本登录、登录信息完整性校验、密码错误提示及聊天窗口等交互功能;网上银行模块覆盖登录、转账、基金与股票查看等典型业务场景,另外还展示了热力学界面的三套改造效果,从社交应用到金融业务覆盖多种界面设计维度。资源包为ZIP压缩格式,大小54.41MB,解压后共663个文件。其中以54个HTML页面、49个CSS样式表、85个JavaScript脚本为核心,辅以274个PNG和159个SVG图片素材、16个GIF动效,以及DOC实验报告与MD说明文档,便于对照学习视觉呈现与交互逻辑。目前已有239人学习/下载。附带的实验报告和文档说明能帮助理解整体设计思路,也便于基于源码二次扩展或修改,是一份可直接提交的完整课程大作业素材。
1. 把 QQ 和银行界面搬进浏览器:一门课设的取舍
拿人机交互课程设计凑评分的时候,很多人的第一反应是去找一个静态模板。但老师的验收标准里往往有一条“有完整的交互闭环”。这套基于 Html+Css+JavaScript 的模仿 QQ 和中国银行的源代码,恰好把两套高频产品的界面搬进了浏览器:QQ 侧覆盖填写账号密码、校验报错、进入聊天并收发消息,银行侧覆盖登录、转账、查看基金股票。整体还带上了文档说明和实验报告,入口页 start.html 把三个模块(QQ、网银、热力学界面改造)串起来。对计科、人工智能、通信工程这些专业的学生来说,它既是课设的三级项目参考,也是从静态页面走向事件驱动交互的现成样本。与其反复查“css 怎么做登录框”,不如把它拆开看一遍事件绑定和状态更新。
2. 页面骨架:start.html 入口与多页面资源组织
2.1 start.html 为什么要做导航中枢
资源里反复提到“推荐从 start.html 页开始浏览”,这句话不是随便写的。整个大作业被拆成了三个相对独立的模块,如果直接双击某个子页面,比如 qq-chat.html,浏览器只会加载当前页,样式和脚本的路径可能会因为相对路径计算错误而拿不到,看起来就像功能失效了。start.html 在这里承担的是导航中枢的角色,用一组链接把 QQ、网银和热力学页面串起来。它本身没有复杂交互,但通过合理的 href 规划让整个项目在解压后可以直接演示。
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>人机交互大作业导航</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="nav-container"> <h1>人机交互课程设计</h1> <ul> <li><a href="qq/qq-login.html">QQ 模拟</a></li> <li><a href="bank/bank-login.html">中国银行网银</a></li> <li><a href="thermo/thermo-a.html">热力学界面 A</a></li> </ul> </div> </body> </html>这里的链接路径要求与真实目录结构一致。常见做法是把每个模块放在自己的子目录里,例如 qq/、bank/、thermo/,这样后续写 CSS 或 JS 时引用相对路径就不会穿模。你可能会问为什么不直接写成一个单页应用?因为课程设计往往要求展示多个独立页面,老师会逐个点开验收,多页面结构更贴近传统网页的组织方式,也更容易用浏览器自带的开发者工具按页面排查问题。解压后先核对目录结构,下表是每个模块应该对应的入口和资源文件。
| 模块 | 入口文件 | 样式文件 | 脚本文件 |
|---|---|---|---|
| QQ 模拟 | qq-login.html | qq/styles.css | qq/qq.js |
| 网银模拟 | bank-login.html | bank/styles.css | bank/bank.js |
| 热力学界面 | thermo-a.html | thermo/styles.css | thermo/thermo.js |
2.2 CSS 作用域隔离:多人样式打架的解法
项目里出现了多个同名 styles.css,这是资源打包时最常见的复制冲突来源。如果所有页面都直接引用根目录下的 styles.css,那么 QQ 登录框的样式很可能被网银页面的按钮样式干扰。我一般会在每个模块下面放独立样式文件,并用一段“三行模式”来组织:第一行写基础重置,第二行写公共组件,第三行写当前页面私有规则。
/* 1. 基础重置 */ * { margin: 0; padding: 0; box-sizing: border-box; } /* 2. 公共组件 */ .btn-primary { display: inline-block; padding: 8px 20px; border: none; border-radius: 4px; cursor: pointer; } .btn-primary:hover { opacity: 0.85; } /* 3. 页面私有 */ body.qq-login-body { background-color: #f2f2f2; } .qq-login-box { width: 360px; margin: 100px auto; padding: 30px; background: #fff; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }这样写的好处是,当你需要给热力学界面做三套视觉改造时,只需替换第三段规则,前面两段基础样式不受影响。如果需要更深一层的避让,还可以引入命名空间,给每个模块的根节点加qq-、bank-前缀,配合 css 伪类选择器使用,比如.bank-transfer-form input[type="text"],能有效阻止样式泄漏。这个项目里没有用 CSS 预处理器,所以保持选择器简洁、避免全局标签选择器是关键。
2.3 JavaScript 按页面拆分还是合并
看到项目里有多个页面,很多人会顺手把全部 js 写进一个 app.js。对于这个体量的课设,合并文件确实好维护,但也会带来一个麻烦:QQ 页面的元素在银行页面里不存在,代码走到一半会报 null 引用错误。所以我更倾向于每个模块单独一个 JS 文件,比如 qq.js、bank.js、common.js。common.js 放公共函数,其他页面按需引入。
// common.js function getVal(id) { var input = document.getElementById(id); return input ? input.value.trim() : ''; } function showError(boxId, message) { var box = document.getElementById(boxId); if (box) { box.textContent = message; box.style.display = 'block'; } }这个文件里两个 javascript 函数都是纯 DOM 操作:getVal负责安全地读取输入框的值,空值情况返回空字符串;showError负责把错误提示写到指定元素并显示出来。参数boxId传入的是元素 id,而不是元素本身,这样在调用时即使 DOM 尚未渲染完成也不会报错。引入顺序上,先引入 common.js,再引入业务 JS,函数声明会提升,顺序反了也不会影响执行,但为了阅读性还是建议固定顺序。如果是多人合作,这个拆分方式还可以避免提交代码时的冲突。
3. QQ 模拟界面:从登录校验到聊天状态管理
3.1 登录信息不完整与密码报错
qq-login.html 里的登录逻辑是整个项目最容易交叉复用的一部分。它解决了两类提示:一类是用户没填完整,提示“登录信息不完整”;另一类是账号存在但密码不对,提示“密码错误”。在纯前端环境里,这个判断只能靠硬编码,但结构上要保持和真实登录一致。
function handleLogin(event) { event.preventDefault(); var username = getVal('qq-user'); var password = getVal('qq-pwd'); var errorBox = document.getElementById('login-error'); if (!username || !password) { showError('login-error', '登录信息不完整'); return; } if (password !== '123456') { showError('login-error', '密码错误,请重新输入'); return; } location.href = 'qq-chat.html?user=' + encodeURIComponent(username); }这里event.preventDefault()是表单提交的关键,它阻止浏览器默认的 GET 跳转,让我们有条件进入后续校验。为什么用了!username而不是username === ''?因为getVal已经执行过.trim(),空字符串和纯空格都会落到空分支,这样更稳妥。密码用!==比较,和判空分开写,顺序也重要:先判空提示“不完整”,再判密码错误,符合用户心理预期。通过后用location.href跳转,并把用户名通过 URL 参数带到聊天页,这是一个很常见的页面间传值方式。
| 事件 | 绑定元素 | 触发条件 | 处理函数 |
|---|---|---|---|
| submit | 登录表单 | 点击登录按钮 | handleLogin |
| keydown | 聊天输入框 | 按下 Enter 且非 Shift | sendMessage |
| click | 发送按钮 | 点击发送 | sendMessage |
3.2 聊天窗口的消息渲染逻辑
进入 qq-chat.html 后,这个模块的核心变成一个消息列表。我看到这个资源里处理得很好的一点是,它没有直接操作 DOM 往里 append,而是先维护一个messages数组,每次发送后重新渲染。
var messages = []; function sendMessage() { var input = document.getElementById('chat-input'); var text = input.value.trim(); if (!text) return; messages.push({ from: 'me', text: text }); input.value = ''; renderMessages(); setTimeout(function () { messages.push({ from: 'friend', text: '收到:' + text }); renderMessages(); }, 600); } function renderMessages() { var list = document.getElementById('chat-list'); list.innerHTML = ''; messages.forEach(function (msg) { var item = document.createElement('div'); item.className = msg.from === 'me' ? 'chat-item chat-me' : 'chat-item chat-friend'; item.textContent = msg.text; list.appendChild(item); }); list.scrollTop = list.scrollHeight; }这套结构的本质是“数据驱动渲染”。messages是唯一数据源,renderMessages根据它重建列表,避免了多次 append 造成的顺序混乱。setTimeout模拟对方延迟回复,虽然只是动画效果,却让人机交互的节奏更真实。list.scrollTop = list.scrollHeight这一行很多初学者会漏,没有它,当消息超出可视区时,用户看不到最新消息,聊天体验会大打折扣。如果想记录未读消息数,还可以在渲染前对比messages.length和 DOM 中的行数,这属于同一个模式的延伸。
3.3 回车发送与事件绑定顺序
聊天框通常希望能按回车发送消息,同时按 Shift+Enter 换行。如果只用onsubmit处理,从 input 里按回车不会触发 submit。常见做法是给输入框绑定keydown事件。
document.getElementById('chat-input').addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } });注意e.key在键盘事件里已经是字符串,用e.key === 'Enter'比判断keyCode === 13更直观。e.preventDefault()是为了避免回车在表单内触发隐式提交,否则页面会刷新,刚聊的状态全没了。另外按钮的type要显式设置为button,否则放在<form>里仍然会被当成提交按钮,这是从视觉效果上看不出来的坑。
提示:如果聊天记录需要保留多行文本,记得给消息列表加上
white-space: pre-wrap,否则文本里的换行会被浏览器折叠成空格。
4. 中国银行网银:登录、转账与基金股票的数据展示
4.1 网银登录与会话保持
网银模块比 QQ 多了一层“会话”概念。QQ 登录后只需要跳转,网银则需要记住当前是谁,后面的转账页面才能标识操作者。在一个不支持后端的课设里,sessionStorage 是最省事的方案。
function bankLogin(event) { event.preventDefault(); var cardNo = getVal('card-no'); var pin = getVal('card-pin'); if (!cardNo || !pin) { alert('请输入卡号和密码'); return; } if (cardNo !== '6222020202020202' || pin !== '888888') { alert('卡号或密码错误'); return; } sessionStorage.setItem('bankUser', cardNo); location.href = 'bank-home.html'; }这里用了alert而不是showError,是因为银行登录页面一般用弹窗提示,更符合用户对银行的认知。sessionStorage和localStorage的区别是前者在标签页关闭后自动清空,适合模拟会话;后者会持久化,适合保存主题设置。页面跳转后,在 bank-home.html 里可以直接读取sessionStorage.getItem('bankUser')来显示欢迎语,不需要再传一次 URL 参数,这比 QQ 模块的传参方式省心。要注意sessionStorage只在当前标签页有效,如果老师用新标签页打开银行首页,登录态会丢失,属于合理现象。
4.2 转账表单校验与确认流
转账是整个网银模块里交互路径最长的一个功能,包含输入、校验、确认、成功回调四步。代码需要同时处理三种异常:收款账号为空、金额不合法、余额不足。
function transfer() { var toAccount = getVal('to-account'); var amount = parseFloat(getVal('amount')); var balance = 10000.00; var errorBox = 'transfer-error'; if (!toAccount) { showError(errorBox, '收款账号不能为空'); return; } if (isNaN(amount) || amount <= 0) { showError(errorBox, '请输入有效的转账金额'); return; } if (amount > balance) { showError(errorBox, '余额不足'); return; } if (confirm('确认向 ' + toAccount + ' 转账 ' + amount.toFixed(2) + ' 元?')) { balance -= amount; updateBalance(balance); showError(errorBox, '转账成功'); } }注意这里balance是用局部变量写死的,真实页面里应该维护在一个state对象中,每次转账后更新。用parseFloat解析输入框内容时,如果用户输入了空串,得到的是NaN,所以先做isNaN判断,再判断<= 0,能把“0”“负数”“非数字”三种情况一起拦下来。最后confirm的作用是让用户确认一次再执行扣款,这是银行类页面应该有的防误触设计。updateBalance是自定义函数,负责更新页面上的余额文本,在这个资源里可以直接找到对应实现。
4.3 基金股票列表的渲染结构
基金和股票页面本质上是一个表格的渲染问题。数据用数组存起来,每条记录包含代码、名称、净值、涨跌幅,然后循环生成行。涨跌颜色通过类名来控制,符合国内“红涨绿跌”的惯例。
var funds = [ { code: '000001', name: '华夏成长', netValue: 1.2563, change: '+0.37%' }, { code: '110022', name: '易方达消费', netValue: 3.4187, change: '-0.52%' } ]; function renderFunds() { var tbody = document.getElementById('fund-table-body'); tbody.innerHTML = ''; funds.forEach(function (f) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + f.code + '</td>' + '<td>' + f.name + '</td>' + '<td>' + f.netValue.toFixed(4) + '</td>' + '<td class="change-' + (f.change.startsWith('+') ? 'up' : 'down') + '">' + f.change + '</td>'; tbody.appendChild(tr); }); }使用innerHTML拼接行模板在数据来自本地模拟数组时是没问题的,比单纯textContent赋值快很多。f.change.startsWith('+')根据涨跌幅字符串首字符决定样式类,.change-up { color: #e61736; }、.change-down { color: #0a8f4a; }。如果是从接口拿数据,就要注意防止 XSS,那时应该改用textContent逐一赋值。下表是 funds 数组里的字段结构,把握住这个结构就能往里扩展更多的基金条目。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | string | 基金代码,用于展示和检索 |
| name | string | 基金名称 |
| netValue | number | 净值,用 toFixed 控制小数位 |
| change | string | 涨跌幅,首字符决定涨跌样式 |
提示:sessionStorage 在同一标签页的多个页面间共享,但关闭标签页后会清空,适合课设这类不要求持久化的场景。
5. 热力学界面改造与验收前的自查技巧
5.1 三套视觉方案通过类名切换
“热力学界面实现三个界面改造效果,分别在不同页展示”,这句话可以理解成同一个数据内容用三种风格渲染。最常见的做法是准备三个页面 thermo-a.html、thermo-b.html、thermo-c.html,共用同一套 JS 逻辑,只替换 CSS 类名。这样老师点开每个页面时,功能一样但视觉完全不同,正好体现了人机交互中的“变换性”。
function setTheme(theme) { var page = document.getElementById('thermo-container'); page.className = 'theme-' + theme; localStorage.setItem('thermo-theme', theme); }在调用时setTheme('dashboard')会在容器上生成class="theme-dashboard",对应的 CSS 规则如下:
.theme-dashboard { background: linear-gradient(135deg, #1f1c2c, #928dab); } .theme-card { background: #f8f8f8; border-radius: 16px; } .theme-terminal { background: #000; color: #0f0; font-family: monospace; }这里用localStorage保存用户偏好,刷新后仍然保留。比起在三个页面间复制粘贴大段样式,这种“改一个类名就换皮”的方式维护成本更低,实验报告里也更好写。
5.2 用浏览器开发者工具快速验证
验收大作业时,最容易出问题的是文件路径和脚本加载顺序。打开 start.html 后,按 F12 进入 Console,如果页面空白或样式错乱,第一看 Network 里有哪个文件 404,第二看 Console 有没有红色报错。这个资源代码都测试过,但如果自己改了路径,就需要重新确认。还可以直接在 Console 执行 JavaScript 函数来做冒烟测试,例如:
// 模拟一次不完整登录 getVal = function(id) { return ''; }; handleLogin({ preventDefault: function() {} });如果页面正确弹出“登录信息不完整”,说明事件绑定和校验函数是通的。这个技巧比反复点按钮更高效,也方便在实验报告里截图作为验证证据。
5.3 实验报告里可以用的测试用例
课程设计提交时,实验报告占分不低。建议把验收过程整理成表格,老师一眼就能看出你做没做测试。下面是我常用的测试用例模板:
| 测试项 | 操作步骤 | 预期结果 |
|---|---|---|
| QQ 登录空校验 | 不输入内容,点击登录 | 出现“登录信息不完整” |
| QQ 密码错误 | 输入账号 12345,密码 111111 | 出现“密码错误” |
| QQ 聊天 | 聊天框输入内容,回车 | 消息追加到列表,输入框清空 |
| 网银登录 | 卡号 6222020202020202,密码 888888 | 进入银行主界面 |
| 网银转账 | 输入 0 元 | 提示“请输入有效的转账金额” |
| 热力学主题切换 | 点击方案 B 按钮 | 页面整体换肤,无 JS 报错 |
把这几个用例跑一遍,并在实验报告里配上截图和结果,整套材料就很完整了。如果时间充裕,还可以把转账后的余额变化做成一个全局变量,让连续转账的状态保持一致,这样人机交互的完成度会更高。
本文还有配套的精品资源,点击获取