feat: 管理员跨租户管理 + 注册开关
- 管理员可管理任意用户笔记(读取/修改/删除/回收站/标签/图谱/FTS 全平台) - 普通用户仍数据隔离, 越权返回404 - 新增注册开关: 管理员后台⚙设置可开/关, 支持REGISTRATION_ENABLED环境变量 - 注册关闭时前台/登录页隐藏注册入口, 注册接口返回400 - 冒烟测试扩展到91用例全过
This commit is contained in:
@@ -634,6 +634,21 @@
|
||||
body.dark .tag-row .tag-name { color: #e6e6e6; }
|
||||
body.dark .tag-row .tag-count { background: #20242e; color: #9aa0aa; }
|
||||
body.dark .tags-empty { color: #9aa0aa; }
|
||||
/* 平台设置:注册开关 */
|
||||
.switch { position: relative; display: inline-block; width: 48px; height: 26px; }
|
||||
.switch input { opacity: 0; width: 0; height: 0; }
|
||||
.slider {
|
||||
position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: #ccc; transition: .3s; border-radius: 26px;
|
||||
}
|
||||
.slider:before {
|
||||
position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px;
|
||||
background: #fff; transition: .3s; border-radius: 50%;
|
||||
}
|
||||
.switch input:checked + .slider { background: #1976d2; }
|
||||
.switch input:checked + .slider:before { transform: translateX(22px); }
|
||||
body.dark .slider { background: #2a3040; }
|
||||
body.dark .setting-row { border-bottom-color: #232733; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -649,6 +664,7 @@
|
||||
<button class="btn btn-secondary" onclick="toggleDarkMode()" title="切换深色模式" id="darkModeBtn">🌙 深色</button>
|
||||
<button class="btn btn-secondary" onclick="exportAll()" title="批量导出全部笔记为 zip">📦 批量导出</button>
|
||||
<button class="btn btn-secondary" onclick="openTrash()" title="回收站">🗑 回收站</button>
|
||||
<button class="btn btn-secondary" id="settingsBtn" onclick="openSettings()" title="平台设置(仅管理员)" style="display:none;">⚙ 设置</button>
|
||||
<a href="/" class="btn btn-secondary" target="_blank">前台预览</a>
|
||||
<button class="btn btn-secondary" onclick="logout()">退出登录</button>
|
||||
</div>
|
||||
@@ -805,6 +821,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 平台设置弹窗(仅管理员) -->
|
||||
<div class="modal" id="settingsModal">
|
||||
<div class="modal-content" style="min-width: 460px;">
|
||||
<h3 style="margin-bottom: 16px;">⚙ 平台设置</h3>
|
||||
<div class="setting-row" style="display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid #eee;">
|
||||
<div>
|
||||
<div style="font-weight:600;">开放注册</div>
|
||||
<div style="font-size:12px;color:#999;">关闭后新用户无法注册,仅已有账号可登录</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="regSwitch" onchange="toggleRegistration(this)">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="modal-actions" style="margin-top:16px;">
|
||||
<button class="btn btn-secondary" onclick="closeSettings()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分享弹窗 -->
|
||||
<div class="modal" id="shareModal">
|
||||
<div class="modal-content" style="min-width: 520px;">
|
||||
@@ -875,10 +912,61 @@
|
||||
|
||||
// 初始化
|
||||
async function init() {
|
||||
await checkAdmin();
|
||||
await loadTree();
|
||||
setupResize();
|
||||
setupImagePaste();
|
||||
}
|
||||
|
||||
// 检查当前用户是否为管理员(决定是否显示「设置」按钮)
|
||||
async function checkAdmin() {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me');
|
||||
const data = await res.json();
|
||||
const me = data.data;
|
||||
if (me && me.role === 'admin') {
|
||||
const btn = document.getElementById('settingsBtn');
|
||||
if (btn) btn.style.display = '';
|
||||
}
|
||||
} catch (e) { /* 非管理员不显示 */ }
|
||||
}
|
||||
|
||||
// ── 平台设置(注册开关,管理员) ──
|
||||
async function openSettings() {
|
||||
document.getElementById('settingsModal').style.display = 'flex';
|
||||
try {
|
||||
const res = await fetch(`${ADMIN_API}/settings/registration`);
|
||||
const data = await res.json();
|
||||
if (data.code === 0) {
|
||||
document.getElementById('regSwitch').checked = !!data.data.registration_enabled;
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('读取设置失败', 'error');
|
||||
}
|
||||
}
|
||||
function closeSettings() {
|
||||
document.getElementById('settingsModal').style.display = 'none';
|
||||
}
|
||||
async function toggleRegistration(el) {
|
||||
const enabled = el.checked;
|
||||
try {
|
||||
const res = await fetch(`${ADMIN_API}/settings/registration`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled: enabled })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.code === 0) {
|
||||
showToast(enabled ? '已开放注册' : '已关闭注册', 'success');
|
||||
} else {
|
||||
el.checked = !enabled;
|
||||
showToast(data.message || '操作失败', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
el.checked = !enabled;
|
||||
showToast('操作失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置粘贴图片监听
|
||||
function setupImagePaste() {
|
||||
|
||||
@@ -166,6 +166,17 @@
|
||||
}
|
||||
switchLink.addEventListener('click', () => setMode(!isRegister));
|
||||
|
||||
// 若注册已关闭,隐藏「去注册」链接(从 /api/auth/me 读取)
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/me');
|
||||
const data = await res.json();
|
||||
if (data.data && data.data.registration_enabled === false) {
|
||||
switchLink.style.display = 'none';
|
||||
}
|
||||
} catch (e) { /* 保留默认 */ }
|
||||
})();
|
||||
|
||||
loginForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
errorMsg.style.display = 'none';
|
||||
|
||||
Reference in New Issue
Block a user