refactor(admin): 把单文件 admin.html (1075 行) 拆成多页 SPA,URL 路由切换
原 admin.html 是 1075 行的单文件 SPA:登录/仪表盘/音色/设置 4 个面板
挤在一个 Vue 实例里 tab 切换(login -> app shell -> 3 个 section)。
文件太大,样式 + script + template 混在一起,改一个面板要 scroll 整页找位置。
拆分后:
router/admin.html 5.4 KB Dashboard (重定向目标,默认页)
router/admin-login.html 2.1 KB 登录页 (无 sidebar)
router/admin-voices.html 13.3 KB 音色管理 + Add modal
router/admin-settings.html 14.1 KB 系统设置
router/admin.css 19.8 KB 共享样式 (4 个页面 + admin-shell.js 共用)
router/admin-shell.js 6.9 KB 共享:http client (401 自动清 key)、toast 系统、
login helper、sidebar 渲染、format helpers
最大的页面 14 KB,平均 10 KB,各页只负责自己逻辑。
技术细节:
- 栈保持: Vue 3 + Axios 走 CDN,纯静态 HTML,零构建
- URL 路由: /admin /admin/login /admin/voices /admin/settings,
router.go 加 4 条 ServeFile 路由 + 2 条静态资源路由
(/admin/admin.css /admin/admin-shell.js)
- 抽 serveAdmin(body []byte) http.HandlerFunc helper 避免 4 段重复
- 鉴权: 各页 onMounted 检 sessionStorage.ttsAdminKey,无则 location.href
跳 /admin/login;http client 401 响应拦截器清 key,下个请求被跳到 login
- Sidebar 一致: shell.js mountShell({activeNav, overview, voiceCount, ...})
渲染 <aside> + 高亮 active 项,各页只传 activeNav 不重复维护 DOM
- 静态资源 Content-Type 显式设置:text/css / application/javascript
未变更:
- 不动 /api/* 后端接口
- 不动 /setup /dashboard /health /v1/audio/speech 路由
- 鉴权语义: HTML 公开 / API 鉴权 (与原版一致)
- 不引入构建工具 / 不引入新依赖
验证 (PowerShell 起真 server + setup API + curl):
/admin 200 5433 bytes text/html
/admin/login 200 2082 bytes text/html
/admin/voices 200 13282 bytes text/html
/admin/settings 200 14110 bytes text/html
/admin/admin.css 200 19815 bytes text/css
/admin/admin-shell.js 200 6887 bytes application/javascript
/api/admin/overview (无 auth) 401
/api/admin/overview (有 auth) 200
各页内容 marker 命中 (filteredVoices / corsForm / voice_count / login-card)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>管理后台 · 登录</title>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.4.21/vue.global.prod.js"></script>
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.6.7/axios.min.js"></script>
|
||||
<link rel="stylesheet" href="/admin/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="login-screen">
|
||||
<div class="login-card">
|
||||
<div class="login-logo">TTS</div>
|
||||
<h1>管理后台</h1>
|
||||
<div class="sub">输入 OPENAI_TTS_API_KEY 登录</div>
|
||||
<input v-model="keyInput" type="password" placeholder="API Key" @keyup.enter="onLogin" autofocus class="login-input">
|
||||
<button class="btn btn-primary btn-lg" @click="onLogin" :disabled="!keyInput" style="width: 100%;">登 录</button>
|
||||
<div v-if="loginErr" class="login-err">⚠ {{ loginErr }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/admin/admin-shell.js"></script>
|
||||
<script>
|
||||
const { createApp, ref } = Vue;
|
||||
const { http, setKey, clearKey, getKey } = window.admin;
|
||||
createApp({
|
||||
setup() {
|
||||
const keyInput = ref('');
|
||||
const loginErr = ref('');
|
||||
|
||||
// 已登录则直接跳 dashboard
|
||||
if (getKey()) {
|
||||
http.get('/admin/overview').then(() => {
|
||||
location.href = '/admin';
|
||||
}).catch(() => { clearKey(); });
|
||||
}
|
||||
|
||||
const onLogin = async () => {
|
||||
loginErr.value = '';
|
||||
try {
|
||||
await window.admin.login(keyInput.value);
|
||||
location.href = '/admin';
|
||||
} catch (e) {
|
||||
loginErr.value = '鉴权失败: ' + (e.response?.data?.error?.message || e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return { keyInput, loginErr, onLogin };
|
||||
},
|
||||
}).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user