使用的环境:
MySQL5.5.50
腾讯云(可以注册免费试用)
体验页面
https://wyjfjs.site/demo/html/login.html
打包的源代码
通过网盘分享的文件:登录注册
链接: https://pan.baidu.com/s/1nBxxwwKFX4rAHqwQE8g0_A 提取码: jjgp
登录

注册

登陆后的页面

创建数据库

打开数据库
systemctl stop mysqld
mysqld_safe --skip-grant-tables &
systemctl restart mysqld
mysql -u root
USE jssj;

创建登录和注册的数据表
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(191) DEFAULT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

目录结构
demo/
├─ html/
│ ├─ login.html
│ ├─ register.html
│ └─ dashboard.html
└─ php/
├─ config.php
├─ register_handler.php
├─ login_handler.php
├─ dashboard.php
└─ logout.php
源代码
login.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>登录控制台</title>
<meta name="color-scheme" content="dark light" />
<style>
:root{
--bg0:#070A12;
--bg1:#0B1020;
--text:#EAF0FF;
--muted: rgba(234,240,255,.72);
--border: rgba(255,255,255,.14);
--shadow: 0 20px 60px rgba(0,0,0,.55);
--accent1:#7C3AED;
--accent2:#22D3EE;
--radius: 18px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "PingFang SC", "Microsoft YaHei";
color:var(--text);
background:
radial-gradient(1000px 600px at 15% 20%, rgba(124,58,237,.28), transparent 55%),
radial-gradient(900px 600px at 85% 25%, rgba(34,211,238,.25), transparent 55%),
linear-gradient(180deg, var(--bg0), var(--bg1));
overflow-x:hidden;
}
.grid{
position:fixed; inset:0;
background-image:
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at 50% 30%, rgba(0,0,0,1), rgba(0,0,0,.2) 55%, rgba(0,0,0,0) 75%);
pointer-events:none;
opacity:.55;
}
.wrap{
min-height:100%;
display:grid;
place-items:center;
padding:40px 16px;
}
.card{
width:min(860px, 100%);
display:grid;
grid-template-columns: 1fr 1fr;
gap:22px;
padding:22px;
border:1px solid var(--border);
border-radius: calc(var(--radius) + 10px);
background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
}
@media (max-width: 860px){
.card{grid-template-columns:1fr; padding:18px}
}
.hero{
padding:18px;
border-radius: var(--radius);
background:
radial-gradient(900px 420px at 25% 15%, rgba(124,58,237,.22), transparent 60%),
radial-gradient(800px 360px at 70% 30%, rgba(34,211,238,.22), transparent 60%),
rgba(255,255,255,.05);
border:1px solid rgba(255,255,255,.10);
}
.title{
margin:0;
font-size: 36px;
line-height: 1.1;
letter-spacing: -0.6px;
}
.grad{
background: linear-gradient(90deg, #EAF0FF, rgba(34,211,238,.95), rgba(124,58,237,.95));
-webkit-background-clip:text;
background-clip:text;
color: transparent;
}
.sub{
margin:10px 0 0;
color: var(--muted);
font-size: 14px;
line-height: 1.65;
max-width: 52ch;
}
.panel{
padding:18px;
border-radius: var(--radius);
border:1px solid rgba(255,255,255,.12);
background: rgba(10,14,28,.55);
box-shadow: 0 0 0 1px rgba(255,255,255,.10), 0 18px 60px rgba(0,0,0,.55);
}
.panel h2{margin:0 0 6px 0; font-size:16px}
.panel .tip{margin:0 0 14px 0; color:var(--muted); font-size:13px}
form{display:grid; gap:12px}
label{font-size:12px; color: rgba(234,240,255,.78); letter-spacing:.2px}
.row{display:grid; gap:8px}
.field{position:relative}
input{
width:100%;
padding: 12px 12px 12px 40px;
border-radius: 14px;
border:1px solid rgba(255,255,255,.14);
outline:none;
background: rgba(255,255,255,.06);
color: var(--text);
font-size: 14px;
transition: .18s ease;
}
input::placeholder{color: rgba(234,240,255,.35)}
input:focus{
border-color: rgba(34,211,238,.55);
box-shadow: 0 0 0 4px rgba(34,211,238,.12);
background: rgba(255,255,255,.08);
}
.icon{
position:absolute;
left:12px; top:50%;
transform: translateY(-50%);
width:18px; height:18px;
opacity:.75;
pointer-events:none;
fill: rgba(234,240,255,.78);
}
.btn{
appearance:none;
border:0;
cursor:pointer;
padding: 12px 14px;
border-radius: 14px;
color: #06101a;
font-weight: 800;
letter-spacing:.2px;
background: linear-gradient(135deg, rgba(34,211,238,.95), rgba(124,58,237,.95));
box-shadow: 0 14px 40px rgba(34,211,238,.18);
transition: transform .15s ease, filter .15s ease;
width: 100%;
}
.btn:hover{transform: translateY(-1px); filter: brightness(1.05)}
.btn:active{transform: translateY(0px); filter: brightness(.98)}
.link{
color: rgba(234,240,255,.80);
text-decoration:none;
font-size: 13px;
}
.link:hover{color:#fff; text-decoration:underline}
.foot{margin-top: 14px; color: rgba(234,240,255,.55); font-size: 12px; line-height:1.6}
</style>
</head>
<body>
<div class="grid"></div>
<div class="wrap">
<div class="card">
<section class="hero">
<h1 class="title"><span class="grad">登录</span> 控制台</h1>
<p class="sub">
请输入你的账号与密码。系统将通过 PHP 后端验证并创建安全会话,然后跳转至控制台页面。
</p>
<p class="sub" style="margin-top:14px;color:rgba(234,240,255,.62)">
Tip:账号可使用「用户名或邮箱」。
</p>
</section>
<aside class="panel">
<h2>账号登录</h2>
<p class="tip">登录成功将进入 dashboard。</p>
<form method="post" action="../php/login_handler.php" autocomplete="off">
<div class="row">
<label for="account">用户名或邮箱</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-4.42 0-8 2-8 4.5V21h16v-2.5C20 16 16.42 14 12 14z"/></svg>
<input id="account" name="account" placeholder="例如:admin / name@example.com" required />
</div>
</div>
<div class="row">
<label for="password">密码</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8h-1V6a4 4 0 0 0-8 0v2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2zm-7-2a2 2 0 0 1 4 0v2h-4zm7 14H7V10h10z"/></svg>
<input id="password" type="password" name="password" placeholder="请输入密码" required />
</div>
</div>
<button class="btn" type="submit">进入系统</button>
<div class="foot">
还没有账号?
<a class="link" href="register.html">去注册</a>
</div>
</form>
</aside>
</div>
</div>
</body>
</html>
register.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>创建账号</title>
<meta name="color-scheme" content="dark light" />
<style>
:root{
--bg0:#070A12;
--bg1:#0B1020;
--card: rgba(255,255,255,.06);
--card2: rgba(255,255,255,.10);
--text:#EAF0FF;
--muted: rgba(234,240,255,.72);
--border: rgba(255,255,255,.14);
--shadow: 0 20px 60px rgba(0,0,0,.55);
--glow: 0 0 0 1px rgba(255,255,255,.10), 0 18px 60px rgba(0,0,0,.55);
--accent1:#7C3AED; /* 紫 */
--accent2:#22D3EE; /* 青 */
--accent3:#A3FF12; /* 绿 */
--danger:#FF4D6D;
--ok:#2EE59D;
--radius: 18px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "PingFang SC", "Microsoft YaHei";
color:var(--text);
background:
radial-gradient(1000px 600px at 10% 15%, rgba(34,211,238,.25), transparent 55%),
radial-gradient(900px 600px at 90% 20%, rgba(124,58,237,.28), transparent 55%),
radial-gradient(900px 700px at 60% 95%, rgba(163,255,18,.12), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
overflow-x:hidden;
}
.grid{
position:fixed; inset:0;
background-image:
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at 50% 30%, rgba(0,0,0,1), rgba(0,0,0,.2) 55%, rgba(0,0,0,0) 75%);
pointer-events:none;
opacity:.55;
}
.wrap{
min-height:100%;
display:grid;
place-items:center;
padding:40px 16px;
}
.card{
width:min(960px, 100%);
display:grid;
grid-template-columns: 1.1fr .9fr;
gap:22px;
padding:22px;
border:1px solid var(--border);
border-radius: calc(var(--radius) + 10px);
background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
}
@media (max-width: 860px){
.card{grid-template-columns:1fr; padding:18px}
}
.hero{
padding:18px 18px 18px 18px;
border-radius: var(--radius);
background:
radial-gradient(900px 420px at 30% 15%, rgba(34,211,238,.20), transparent 60%),
radial-gradient(700px 360px at 70% 30%, rgba(124,58,237,.22), transparent 60%),
linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
border:1px solid rgba(255,255,255,.10);
position:relative;
overflow:hidden;
}
.hero:before{
content:"";
position:absolute; inset:-2px;
background: conic-gradient(from 220deg,
rgba(34,211,238,.0),
rgba(34,211,238,.35),
rgba(124,58,237,.35),
rgba(163,255,18,.20),
rgba(34,211,238,.0));
filter: blur(22px);
opacity:.55;
transform: translate3d(0,0,0);
pointer-events:none;
}
.brand{
position:relative;
display:flex;
align-items:center;
gap:12px;
margin-bottom:12px;
}
.logo{
width:44px;height:44px;border-radius:14px;
background:
radial-gradient(circle at 30% 25%, rgba(255,255,255,.55), rgba(255,255,255,0) 55%),
linear-gradient(135deg, rgba(34,211,238,.9), rgba(124,58,237,.9));
box-shadow: 0 14px 40px rgba(124,58,237,.25);
border:1px solid rgba(255,255,255,.20);
}
.brand h1{
margin:0;
font-size: 18px;
letter-spacing:.2px;
color: rgba(234,240,255,.92);
}
.brand p{
margin:2px 0 0 0;
color: var(--muted);
font-size: 13px;
}
.headline{
position:relative;
margin:14px 0 10px;
font-size: 38px;
line-height: 1.1;
letter-spacing: -0.6px;
}
.headline .grad{
background: linear-gradient(90deg, #EAF0FF, rgba(34,211,238,.95), rgba(124,58,237,.95));
-webkit-background-clip:text;
background-clip:text;
color: transparent;
}
.sub{
position:relative;
margin: 8px 0 0;
color: rgba(234,240,255,.72);
font-size: 14px;
line-height: 1.65;
max-width: 52ch;
}
.pills{
position:relative;
display:flex; flex-wrap:wrap;
gap:10px;
margin-top:14px;
}
.pill{
padding:8px 10px;
border-radius: 999px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
font-size: 12px;
color: rgba(234,240,255,.78);
}
.panel{
padding:18px;
border-radius: var(--radius);
border:1px solid rgba(255,255,255,.12);
background: rgba(10,14,28,.55);
box-shadow: var(--glow);
}
.panel h2{
margin:0 0 6px 0;
font-size: 16px;
letter-spacing:.2px;
}
.panel .tip{
margin:0 0 14px 0;
color: var(--muted);
font-size: 13px;
}
form{display:grid; gap:12px}
.row{display:grid; gap:8px}
label{
font-size: 12px;
color: rgba(234,240,255,.78);
letter-spacing:.2px;
}
.field{
position:relative;
}
input{
width:100%;
padding: 12px 12px 12px 40px;
border-radius: 14px;
border:1px solid rgba(255,255,255,.14);
outline:none;
background: rgba(255,255,255,.06);
color: var(--text);
font-size: 14px;
transition: .18s ease;
}
input::placeholder{color: rgba(234,240,255,.35)}
input:focus{
border-color: rgba(34,211,238,.55);
box-shadow: 0 0 0 4px rgba(34,211,238,.12);
background: rgba(255,255,255,.08);
}
.icon{
position:absolute;
left:12px; top:50%;
transform: translateY(-50%);
width:18px; height:18px;
opacity:.75;
pointer-events:none;
fill: rgba(234,240,255,.78);
}
.actions{
display:flex;
gap:10px;
align-items:center;
margin-top:4px;
}
.btn{
appearance:none;
border:0;
cursor:pointer;
padding: 12px 14px;
border-radius: 14px;
color: #06101a;
font-weight: 700;
letter-spacing:.2px;
background: linear-gradient(135deg, rgba(34,211,238,.95), rgba(124,58,237,.95));
box-shadow: 0 14px 40px rgba(34,211,238,.18);
transition: transform .15s ease, filter .15s ease;
width: 100%;
}
.btn:hover{transform: translateY(-1px); filter: brightness(1.05)}
.btn:active{transform: translateY(0px); filter: brightness(.98)}
.link{
color: rgba(234,240,255,.80);
text-decoration:none;
font-size: 13px;
}
.link:hover{color:#fff; text-decoration:underline}
.foot{
margin-top: 14px;
color: rgba(234,240,255,.55);
font-size: 12px;
line-height:1.6;
}
/* 简易前端提示(HTML5 invalid) */
input:invalid{border-color: rgba(255,77,109,.30)}
input:invalid:focus{box-shadow: 0 0 0 4px rgba(255,77,109,.12)}
</style>
</head>
<body>
<div class="grid"></div>
<div class="wrap">
<div class="card">
<section class="hero" aria-label="介绍">
<div class="brand">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>Auth Console</h1>
<p>Secure · Minimal · Modern</p>
</div>
</div>
<div class="headline">
<span class="grad">创建你的新身份</span><br/>
进入系统控制台
</div>
<p class="sub">
使用安全的密码哈希存储与会话管理。此页面为纯 HTML 前端,提交给 PHP 后端处理注册逻辑。
</p>
<div class="pills" aria-label="功能亮点">
<span class="pill">Glass UI</span>
<span class="pill">Gradient</span>
<span class="pill">Secure Session</span>
<span class="pill">PDO</span>
<span class="pill">Password Hash</span>
</div>
</section>
<aside class="panel" aria-label="注册表单">
<h2>创建账号</h2>
<p class="tip">填写信息后提交,注册成功将跳转到登录页。</p>
<form method="post" action="../php/register_handler.php" autocomplete="off">
<div class="row">
<label for="username">用户名</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 12a4 4 0 1 0-4-4 4 4 0 0 0 4 4zm0 2c-4.42 0-8 2-8 4.5V21h16v-2.5C20 16 16.42 14 12 14z"/></svg>
<input id="username" name="username" placeholder="例如:admin" required minlength="2" maxlength="50" />
</div>
</div>
<div class="row">
<label for="email">邮箱(可选)</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 4-8 5L4 8V6l8 5 8-5z"/></svg>
<input id="email" name="email" placeholder="name@example.com" type="email" maxlength="191" />
</div>
</div>
<div class="row">
<label for="password">密码</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8h-1V6a4 4 0 0 0-8 0v2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2zm-7-2a2 2 0 0 1 4 0v2h-4zm7 14H7V10h10z"/></svg>
<input id="password" name="password" placeholder="至少 6 位" type="password" required minlength="6" />
</div>
</div>
<div class="row">
<label for="confirm">确认密码</label>
<div class="field">
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 1a11 11 0 1 0 11 11A11 11 0 0 0 12 1zm-1 16-4-4 1.41-1.41L11 14.17l5.59-5.59L18 10z"/></svg>
<input id="confirm" name="confirm" placeholder="再次输入密码" type="password" required minlength="6" />
</div>
</div>
<div class="actions">
<button class="btn" type="submit">立即注册</button>
</div>
<div class="foot">
已有账号?
<a class="link" href="login.html">去登录</a>
</div>
</form>
</aside>
</div>
</div>
<script>
// 前端小校验:确认密码一致(不影响后端校验)
const pw = document.getElementById('password');
const cf = document.getElementById('confirm');
function check(){
if (!pw.value || !cf.value) { cf.setCustomValidity(''); return; }
cf.setCustomValidity(pw.value === cf.value ? '' : '两次输入的密码不一致');
}
pw.addEventListener('input', check);
cf.addEventListener('input', check);
</script>
</body>
</html>
dashboard.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>控制台</title>
<meta name="color-scheme" content="dark light" />
<style>
:root{
--bg0:#070A12; --bg1:#0B1020;
--text:#EAF0FF; --muted: rgba(234,240,255,.72);
--border: rgba(255,255,255,.14);
--shadow: 0 20px 60px rgba(0,0,0,.55);
--accent1:#7C3AED; --accent2:#22D3EE; --accent3:#A3FF12;
--radius: 18px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "PingFang SC", "Microsoft YaHei";
color:var(--text);
background:
radial-gradient(1000px 600px at 10% 15%, rgba(34,211,238,.25), transparent 55%),
radial-gradient(900px 600px at 90% 20%, rgba(124,58,237,.28), transparent 55%),
radial-gradient(900px 700px at 60% 95%, rgba(163,255,18,.10), transparent 60%),
linear-gradient(180deg, var(--bg0), var(--bg1));
overflow-x:hidden;
}
.grid{
position:fixed; inset:0;
background-image:
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at 50% 30%, rgba(0,0,0,1), rgba(0,0,0,.2) 55%, rgba(0,0,0,0) 75%);
pointer-events:none;
opacity:.55;
}
.wrap{min-height:100%; padding:34px 16px;}
.top{
max-width:1100px; margin:0 auto 16px auto;
display:flex; align-items:center; justify-content:space-between; gap:12px;
}
.brand{display:flex; align-items:center; gap:12px;}
.logo{
width:44px;height:44px;border-radius:14px;
background:
radial-gradient(circle at 30% 25%, rgba(255,255,255,.55), rgba(255,255,255,0) 55%),
linear-gradient(135deg, rgba(34,211,238,.9), rgba(124,58,237,.9));
box-shadow: 0 14px 40px rgba(124,58,237,.25);
border:1px solid rgba(255,255,255,.20);
flex:0 0 auto;
}
.brand h1{margin:0; font-size:16px; letter-spacing:.2px;}
.brand p{margin:2px 0 0 0; color:var(--muted); font-size:12px;}
.user{color:rgba(234,240,255,.82); font-size:13px}
.btn{
display:inline-flex; align-items:center; justify-content:center;
padding:10px 12px; border-radius:14px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: rgba(234,240,255,.88);
text-decoration:none;
transition:.15s ease;
}
.btn:hover{transform: translateY(-1px); background: rgba(255,255,255,.08);}
.btn.primary{
border:0;
color:#06101a;
font-weight:800;
background: linear-gradient(135deg, rgba(34,211,238,.95), rgba(124,58,237,.95));
box-shadow: 0 14px 40px rgba(34,211,238,.18);
}
.container{
max-width:1100px; margin:0 auto;
display:grid; grid-template-columns: 1fr;
gap:16px;
}
.hero{
border:1px solid rgba(255,255,255,.12);
border-radius: calc(var(--radius) + 10px);
background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04));
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
padding:22px;
overflow:hidden;
position:relative;
}
.hero:before{
content:""; position:absolute; inset:-2px;
background: conic-gradient(from 220deg,
rgba(34,211,238,.0),
rgba(34,211,238,.30),
rgba(124,58,237,.32),
rgba(163,255,18,.14),
rgba(34,211,238,.0));
filter: blur(26px);
opacity:.55;
pointer-events:none;
}
.hero h2{
position:relative;
margin:0;
font-size:34px;
line-height:1.15;
letter-spacing:-.6px;
}
.grad{
background: linear-gradient(90deg, #EAF0FF, rgba(34,211,238,.95), rgba(124,58,237,.95));
-webkit-background-clip:text; background-clip:text; color:transparent;
}
.hero p{position:relative; margin:10px 0 0; color:var(--muted); line-height:1.7}
.cards{
display:grid;
grid-template-columns: repeat(12, 1fr);
gap:12px;
}
.card{
grid-column: span 4;
border:1px solid rgba(255,255,255,.12);
border-radius: var(--radius);
background: rgba(10,14,28,.55);
box-shadow: 0 0 0 1px rgba(255,255,255,.10), 0 18px 60px rgba(0,0,0,.55);
padding:16px;
min-height:110px;
}
@media (max-width: 900px){ .card{grid-column: span 6;} }
@media (max-width: 620px){ .card{grid-column: span 12;} }
.card h3{margin:0 0 6px 0; font-size:14px}
.card p{margin:0; color:var(--muted); font-size:13px; line-height:1.6}
.meta{
margin-top:10px;
display:flex; gap:10px; flex-wrap:wrap;
color: rgba(234,240,255,.68);
font-size:12px;
}
.tag{
padding:6px 8px; border-radius:999px;
border:1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
}
/* ===== 修复:标题竖排/容器过窄导致的异常换行 ===== */
.hero{min-width:0;}
.hero h2{
writing-mode: horizontal-tb !important; /* 强制横排 */
text-orientation: mixed !important;
white-space: normal;
word-break: keep-all; /* 中文不要逐字断 */
overflow-wrap: anywhere; /* 必要时才断 */
}
/* 防止 grid 子项被挤爆(尤其在有全局样式时) */
.container, .cards, .card{min-width:0;}
.cards{grid-template-columns: repeat(12, minmax(0, 1fr));}
/* 如果外部样式把所有元素设了很小宽度,这里兜底 */
.hero, .card{
width: 100%;
}
</style>
</head>
<body>
<div class="grid"></div>
<div class="wrap">
<header class="top">
<div class="brand">
<div class="logo" aria-hidden="true"></div>
<div>
<h1>Auth Console</h1>
<p>Dashboard · Secure Session</p>
</div>
</div>
<div style="display:flex; align-items:center; gap:10px;">
<div class="user">当前状态:已登录</div>
<a class="btn" href="../php/logout.php">退出</a>
</div>
</header>
<main class="container">
<section class="hero hero-full">
<h2><span class="grad">控制台</span> 主页面</h2>
<p>这是一个静态 HTML 页面(用于展示 UI/布局),但访问入口会由 PHP 进行登录校验后再输出。</p>
</section>
<section class="cards" aria-label="功能模块">
<div class="card">
<h3>账户概览</h3>
<p>展示当前登录态、账号信息(如需动态展示可后续改成模板变量或 AJAX)。</p>
<div class="meta"><span class="tag">Session</span><span class="tag">Secure</span></div>
</div>
<div class="card">
<h3>系统状态</h3>
<p>这里可以放:服务状态、公告、最近登录记录等。</p>
<div class="meta"><span class="tag">Status</span><span class="tag">Logs</span></div>
</div>
<div class="card">
<h3>快捷入口</h3>
<p>放你的业务模块入口链接。</p>
<div class="meta"><span class="tag">Links</span><span class="tag">Apps</span></div>
</div>
</section>
</main>
</div>
</body>
</html>
config.php
<?php
declare(strict_types=1);
session_start();
// ====== 数据库配置(按需修改)======
$DB_HOST = '127.0.0.1';
$DB_NAME = 'jssj';
$DB_USER = 'jssj';
$DB_PASS = '000000';
$DB_CHARSET = 'utf8mb4';
// ===============================
$dsn = "mysql:host={$DB_HOST};dbname={$DB_NAME};charset={$DB_CHARSET}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $DB_USER, $DB_PASS, $options);
function h(string $s): string {
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
register_handler.php
<?php
declare(strict_types=1);
require __DIR__ . '/config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../html/register.html');
exit;
}
$username = trim($_POST['username'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = (string)($_POST['password'] ?? '');
$confirm = (string)($_POST['confirm'] ?? '');
if ($username === '' || $password === '' || $confirm === '') {
http_response_code(400);
exit('参数不完整:用户名/密码/确认密码必填');
}
if ($password !== $confirm) {
http_response_code(400);
exit('两次输入的密码不一致');
}
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
exit('邮箱格式不正确');
}
// 用户名查重
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? LIMIT 1');
$stmt->execute([$username]);
if ($stmt->fetch()) {
http_response_code(409);
exit('用户名已存在');
}
// 邮箱查重(仅在填写邮箱时)
if ($email !== '') {
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
$stmt->execute([$email]);
if ($stmt->fetch()) {
http_response_code(409);
exit('邮箱已存在');
}
}
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)');
$stmt->execute([$username, $email !== '' ? $email : null, $hash]);
header('Location: ../html/login.html');
exit;
login_handler.php
<?php
declare(strict_types=1);
require __DIR__ . '/config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ../html/login.html');
exit;
}
$account = trim($_POST['account'] ?? '');
$password = (string)($_POST['password'] ?? '');
if ($account === '' || $password === '') {
http_response_code(400);
exit('请输入账号和密码');
}
$stmt = $pdo->prepare('SELECT id, username, password_hash FROM users WHERE username = ? OR email = ? LIMIT 1');
$stmt->execute([$account, $account]);
$user = $stmt->fetch();
if (!$user || !password_verify($password, $user['password_hash'])) {
http_response_code(401);
exit('账号或密码错误');
}
session_regenerate_id(true);
$_SESSION['user_id'] = (int)$user['id'];
$_SESSION['username'] = (string)$user['username'];
header('Location: dashboard.php');
exit;
dashboard.php
<?php
declare(strict_types=1);
require __DIR__ . '/config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: ../html/login.html');
exit;
}
// 输出静态 dashboard.html
$dashboardFile = __DIR__ . '/../html/dashboard.html';
if (!is_file($dashboardFile)) {
http_response_code(500);
exit('dashboard.html 不存在,请创建:/demo/html/dashboard.html');
}
header('Content-Type: text/html; charset=UTF-8');
readfile($dashboardFile);
exit;
logout.php
<?php
declare(strict_types=1);
require __DIR__ . '/config.php';
// 清空会话数据
$_SESSION = [];
// 删除会话 Cookie(如果启用)
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// 销毁会话
session_destroy();
header('Location: ../html/login.html');
exit;