Skip to main content
Version: Next

登录页与接口调用(前端)

一个最小可用的登录页:输入用户名密码 → POST JSON 到后端 → 拿到令牌后跳转首页。核心是处理三种状态:加载中(按钮禁用)、失败(展示后端 message)、成功(保存令牌跳转)。

完整页面​

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
.login-box { width: 320px; margin: 100px auto; padding: 32px; border: 1px solid #ddd; border-radius: 8px; }
.form-item { margin-bottom: 16px; }
input { width: 100%; padding: 8px; box-sizing: border-box; }
button { width: 100%; padding: 10px; cursor: pointer; }
.msg { color: #e53935; min-height: 20px; font-size: 13px; }
</style>
</head>
<body>
<form id="login-form" class="login-box">
<h2>用户登录</h2>
<div class="form-item">
<label>用户名</label>
<input id="username" name="username" autocomplete="username" required>
</div>
<div class="form-item">
<label>密码</label>
<input id="password" name="password" type="password" required minlength="6">
</div>
<p id="msg" class="msg"></p>
<button type="submit" id="submit-btn">登录</button>
</form>

<script>
const form = document.getElementById('login-form');
const msg = document.getElementById('msg');
const btn = document.getElementById('submit-btn');

form.addEventListener('submit', async (e) => {
e.preventDefault(); // 阻止表单默认同步提交
const payload = {
username: form.username.value.trim(),
password: form.password.value
};
if (!payload.username || !payload.password) {
msg.textContent = '请输入用户名和密码';
return;
}

btn.disabled = true;
btn.textContent = '登录中...';

try {
const res = await fetch('http://localhost:8080/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const body = await res.json();

if (res.ok && body.code === 0) {
sessionStorage.setItem('token', body.data.token);
location.href = '/index.html';
} else {
msg.textContent = body.message || '登录失败,请重试';
}
} catch (err) {
msg.textContent = '网络异常,请稍后重试';
} finally {
btn.disabled = false;
btn.textContent = '登录';
}
});
</script>
</body>
</html>

关键点​

  • 请求方式:fetch 发 POST,body 是 JSON.stringify() 后的 JSON 字符串,必须设置 Content-Type: application/json,后端才能用 @RequestBody 反序列化。
  • 表单校验:required、minlength 由浏览器先校验一次,减少无意义请求;JS 里再做一次非空判断兜底。
  • 失败提示:后端返回统一结构(code / message / data),前端只展示 message,绝不出来自堆栈的原始报错。

令牌存储与携带​

// 后续请求统一封装:自动附加 Authorization 头
async function api(url, options = {}) {
const token = sessionStorage.getItem('token');
return fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers
}
});
}

:::warning 令牌安全 生产环境推荐后端通过 Set-Cookie(HttpOnly + Secure + SameSite)下发令牌,前端 JS 完全碰不到,天然防 XSS 窃取。上面的 sessionStorage 仅作演示;令牌禁止存入 localStorage,也绝不能拼进 URL 查询参数。 :::

面试常问​

  • 为什么用 JSON 而不是表单提交? 结构化、可扩展、前后端契约清晰;只有文件上传才用 multipart/form-data。
  • 密码要不要前端加密? 必须走 HTTPS 传输;前端 MD5 不增加安全性,真正的底线是密码绝不能明文入库——后端用 BCrypt 哈希存储。
  • 失败要不要自动重试? 4xx 是业务错误,重试没有意义,直接提示;只有 5xx 或网络异常才考虑有限次重试(最多 3 次)。