fix(fam-ui): 未登录自动跳转 OIDC + 登录/退出入口,打通 smart-camera 登录

This commit is contained in:
ericwyuan
2026-09-01 12:23:48 +08:00
parent db4f46140b
commit 2ad0472578
3 changed files with 77 additions and 4 deletions

View File

@@ -6,6 +6,16 @@ import { navItems } from './router.js'
const route = useRoute()
const sync = ref(null)
const authed = ref(false)
async function checkAuth() {
try {
const r = await api.authCheck()
authed.value = !!r.authed
} catch {
authed.value = false
}
}
async function refreshStatus() {
try {
@@ -16,8 +26,19 @@ async function refreshStatus() {
}
}
async function doLogout() {
try {
await fetch('/api/logout', { method: 'POST' })
} catch {
// 忽略网络错误,下面强制跳转即可
}
// 清掉 cookie 后回根路径,未登录态会让后端 401 -> 自动跳 /login 重新登录
window.location.href = '/'
}
let timer = null
onMounted(() => {
checkAuth()
refreshStatus()
timer = setInterval(refreshStatus, 30000)
})
@@ -40,14 +61,34 @@ onUnmounted(() => clearInterval(timer))
游标 {{ sync.cursor ? fmtDateTime(sync.cursor) : '(全量)' }}
<div v-if="sync.last_error" class="font-semibold text-danger"> {{ sync.last_error }}</div>
</div>
<div class="mt-4">
<a v-if="!authed" href="/login"
class="block rounded-xl bg-gradient-to-br from-accent to-accent-2 px-4 py-2.5 text-center text-sm font-semibold text-white shadow-[0_4px_16px_-4px_rgba(91,140,255,.45)] transition-opacity hover:opacity-90">
🔑 登录
</a>
<button v-else @click="doLogout"
class="block w-full rounded-xl border border-border bg-panel-2 px-4 py-2.5 text-center text-sm font-medium text-text-dim transition-colors hover:text-text">
👋 退出登录
</button>
</div>
</aside>
<!-- 移动端顶栏品牌 + 简要同步指示灯lg 以上隐藏用左侧栏代替 -->
<!-- 移动端顶栏品牌 + 登录/退出 + 简要同步指示灯lg 以上隐藏用左侧栏代替 -->
<header class="flex items-center justify-between border-b border-border bg-[#090c12] px-4 py-3 lg:hidden">
<div class="text-[15px] font-bold text-[#f7f9fc]">🏠 家庭智能监控</div>
<span v-if="sync" class="flex items-center gap-1.5 text-xs font-medium" :class="sync.running ? 'text-ok' : 'text-danger'">
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>{{ sync.running ? '同步中' : '未运行' }}
</span>
<div class="flex items-center gap-2">
<a v-if="!authed" href="/login"
class="rounded-lg bg-gradient-to-br from-accent to-accent-2 px-3 py-1.5 text-xs font-semibold text-white shadow-[0_4px_16px_-4px_rgba(91,140,255,.45)]">
🔑 登录
</a>
<button v-else @click="doLogout"
class="rounded-lg border border-border bg-panel-2 px-3 py-1.5 text-xs font-medium text-text-dim">
退出
</button>
<span v-if="sync" class="flex items-center gap-1.5 text-xs font-medium" :class="sync.running ? 'text-ok' : 'text-danger'">
<span class="h-1.5 w-1.5 rounded-full bg-current"></span>{{ sync.running ? '同步中' : '未运行' }}
</span>
</div>
</header>
<div class="min-w-0 flex-1 px-4 py-4 sm:px-8 sm:py-6">

View File

@@ -1,5 +1,9 @@
// API 薄封装:生产环境同源相对路径;开发环境走 vite.config.js 的 /api 代理。
// 全局:未登录时首个 401 直接跳转到 /login 走 OIDC/PKCE 登录流程。
// 用一个模块级开关保证整次会话只触发一次跳转,避免多个并发 401 重复导航。
let _redirectingToLogin = false
async function request(path, options = {}) {
const res = await fetch(path, {
headers: { 'Content-Type': 'application/json' },
@@ -12,6 +16,14 @@ async function request(path, options = {}) {
// 非 JSON 响应(如 404 空 body保持 data=null
}
if (!res.ok) {
// 401 未登录:交给后端 /login302 到 auth-hub发起统一登录。
// /api/auth/check 等白名单接口不会返回 401所以这里只会命中真正的鉴权失败。
if (res.status === 401 && !_redirectingToLogin) {
_redirectingToLogin = true
window.location.href = '/login'
// 返回永挂起的 promise阻止调用方继续渲染"未登录"错误态(页面即将跳转)
return new Promise(() => {})
}
const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`
throw new Error(msg)
}
@@ -52,6 +64,7 @@ export const api = {
request('/api/member/merge', { method: 'POST', body: JSON.stringify({ source, target }) }),
status: () => request('/api/status'),
authCheck: () => request('/api/auth/check'),
syncTrigger: () => request('/api/sync/trigger', { method: 'POST' }),
identityCorrect: (video_id, current_name, new_name) =>