新增 OIDC/SSO、邮件验证码与 Gitea 项目同步,并强化 Feed 与管理后台。

作为 OIDC Provider 对接 Gitea;注册支持邮件验证码/验证码;侧栏同步公开仓库;Feed 分页、文章大纲、标签云与站点品牌设置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-31 16:58:22 +08:00
parent 9487c8ab02
commit 822eef96be
83 changed files with 8578 additions and 1222 deletions

View File

@@ -53,7 +53,9 @@ func IsSPARoute(path string) bool {
strings.HasPrefix(path, "/admin") ||
strings.HasPrefix(path, "/uploads") ||
strings.HasPrefix(path, "/legacy") ||
strings.HasPrefix(path, "/assets") {
strings.HasPrefix(path, "/assets") ||
strings.HasPrefix(path, "/oauth") ||
strings.HasPrefix(path, "/.well-known") {
return false
}
return true

View File

@@ -36,13 +36,6 @@
<div class="label">评论总数</div>
</div>
</div>
<div class="admin-stat-card admin-stat-online">
<div class="admin-stat-icon">线</div>
<div class="admin-stat-info">
<div class="value">{{.OnlineCount}}</div>
<div class="label">当前浏览</div>
</div>
</div>
</div>
<div class="admin-card">

View File

@@ -11,7 +11,7 @@
<div class="table-responsive">
<table class="table admin-table mb-0">
<thead>
<tr><th>ID</th><th>用户名</th><th>昵称</th><th>角色</th><th>状态</th><th>注册时间</th><th width="120">操作</th></tr>
<tr><th>ID</th><th>用户名</th><th>昵称</th><th>邮箱</th><th>角色</th><th>状态</th><th>上次登录</th><th>登录 IP</th><th>注册时间</th><th width="120">操作</th></tr>
</thead>
<tbody>
{{range .Users}}
@@ -19,6 +19,7 @@
<td>{{.ID}}</td>
<td>{{.Username}}</td>
<td>{{.Nickname}}</td>
<td>{{if .Email}}{{.Email}}{{else}}—{{end}}</td>
<td>
{{if eq .Role "admin"}}
<span class="badge badge-admin">管理员</span>
@@ -27,6 +28,8 @@
<td>
{{if .Banned}}<span class="badge badge-banned">已禁言</span>{{else}}<span class="text-success">正常</span>{{end}}
</td>
<td>{{if .LastLoginAt}}{{.LastLoginAt.Format "2006-01-02 15:04"}}{{else}}—{{end}}</td>
<td>{{if .LastLoginIP}}{{.LastLoginIP}}{{else}}—{{end}}</td>
<td>{{.CreatedAt.Format "2006-01-02"}}</td>
<td>
{{if eq .Role "admin"}}
@@ -39,7 +42,7 @@
</td>
</tr>
{{else}}
<tr><td colspan="7" class="admin-empty">暂无用户</td></tr>
<tr><td colspan="10" class="admin-empty">暂无用户</td></tr>
{{end}}
</tbody>
</table>

View File

@@ -5,6 +5,7 @@
<img src="{{if .ProfileUser.Avatar}}{{.ProfileUser.Avatar}}{{else}}https://via.placeholder.com/128{{end}}" class="avatar-md mb-2" alt="avatar">
<h4>{{.ProfileUser.Nickname}}</h4>
<p class="text-muted">@{{.ProfileUser.Username}}</p>
<p class="text-muted small">{{if .ProfileUser.Email}}{{.ProfileUser.Email}}{{else}}未设置邮箱{{end}}</p>
<form id="avatarForm" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*" class="form-control form-control-sm mb-2">
<button type="submit" class="btn btn-outline-primary btn-sm">更换头像</button>

View File

@@ -5,20 +5,31 @@
<div class="card shadow-sm">
<div class="card-body p-4">
<h3 class="text-center mb-4">注册账号</h3>
<p class="text-center text-muted small mb-3">本站首个注册用户将自动成为管理员</p>
<p id="regTip" class="text-center text-muted small mb-3"></p>
<form id="regForm">
<div class="mb-3">
<label class="form-label">用户名(3-32位字母数字下划线</label>
<label class="form-label">用户名(2-32位,支持中文</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">昵称</label>
<input type="text" name="nickname" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">邮箱</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">密码至少6位</label>
<input type="password" name="password" class="form-control" required>
</div>
<div class="mb-3" id="emailCodeWrap" hidden>
<label class="form-label">邮箱验证码</label>
<div class="d-flex gap-2">
<input type="text" name="email_code" class="form-control" autocomplete="one-time-code">
<button type="button" class="btn btn-outline-secondary text-nowrap" id="sendCodeBtn">发送验证码</button>
</div>
</div>
<button type="submit" class="btn btn-success w-100">注册</button>
</form>
<p class="text-center mt-3 mb-0">已有账号?<a href="/login">去登录</a></p>
@@ -29,6 +40,26 @@
{{end}}
{{define "scripts"}}
<script>
var regCfg = { require_email_code: false, register_open: true, is_first_user: false };
fetch('/api/register/config').then(r=>r.json()).then(d=>{
regCfg = d;
var tip = document.getElementById('regTip');
if (d.is_first_user) tip.textContent = '本站首个注册用户将自动成为管理员';
else if (!d.register_open) tip.textContent = '注册暂未开放,请等待管理员配置邮件服务';
document.getElementById('emailCodeWrap').hidden = !d.require_email_code;
if (!d.register_open) document.getElementById('regForm').hidden = true;
});
document.getElementById('sendCodeBtn').addEventListener('click', function(){
var email = document.querySelector('[name=email]').value;
fetch('/api/register/email-code', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({email: email})
}).then(r=>r.json()).then(d=>{
if(d.error){ showToast(d.error,'danger'); return; }
showToast(d.message || '已发送');
});
});
document.getElementById('regForm').addEventListener('submit', function(e){
e.preventDefault();
apiPost('/api/register', this, true).then(d=>{