Files
FileShare/web/src/App.vue
T
SKYMirror 7f060dd0e4 26.9(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务:

- 安全审计全部修复(docs/security-audit-2026-09-05.md):
  bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、
  全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、
  管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、
  会话默认 7 天、限流缓存故障降级、robots.txt 端点等
- 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固
- Redis 支持库号(FCB_REDIS_DB / redis://…/db URL)
- 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、
  提码 5 位起、chunk 32MiB 上限、admin 审计动作等)

验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
2026-09-05 04:22:41 +08:00

146 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* App 根组件:
* - 需求②:Naive UI 主题(darkTheme 按 resolved 传递)+ 全局主题令牌
* - 需求⑤:背景图(config.background_url → 固定层 + 半透明遮罩)
* - 需求⑦:notify_enabled 时右上角玻璃悬浮通知(localStorage 记忆已读)
* - 需求⑩:i18n 装配后同步 <html lang>
*/
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { NConfigProvider, darkTheme } from 'naive-ui'
import { useConfigStore } from '@/stores/config'
import { useTheme } from '@/theme'
import { currentLocale, i18n } from '@/i18n'
import ToastHost from '@/components/ToastHost.vue'
import NotifyPop from '@/components/NotifyPop.vue'
const config = useConfigStore()
const route = useRoute()
const { resolved } = useTheme()
// 语言切换时同步 <html lang>(初始由 i18n 模块设置一次)
watch(
currentLocale,
(loc) => {
document.documentElement.lang = loc
},
{ immediate: true },
)
// document.title = i18n(路由标题键) · 站点名(需求⑩:语言/路由/站点名三者联动)
watch(
[() => route.fullPath, currentLocale, () => config.displayName] as const,
() => {
const key = route.meta.titleKey
const title = typeof key === 'string' ? i18n.global.t(key) : config.displayName
document.title = `${title} · ${config.displayName}`
},
{ immediate: true },
)
const naiveTheme = computed(() => (resolved.value === 'dark' ? darkTheme : null))
const themeOverrides = computed(() =>
resolved.value === 'dark'
? {
common: {
primaryColor: '#7d95ff',
primaryColorHover: '#98abff',
primaryColorPressed: '#6c86f5',
primaryColorSuppl: '#98abff',
},
}
: {
common: {
primaryColor: '#4f6ef7',
primaryColorHover: '#3d5bf0',
primaryColorPressed: '#4359e0',
primaryColorSuppl: '#3d5bf0',
},
},
)
const hasBackground = computed(() => Boolean(config.backgroundUrl.trim()))
// —— 需求⑦:通知关闭记忆 ——
const NOTIFY_READ_KEY = 'fcb_notify_read'
const notifyOpen = ref(false)
const notifyRead = ref(false)
function notifyFingerprint(): string {
return `${config.notifyEnabled}|${config.notifyTitle}|${config.notifyContent}`
}
function restoreNotifyRead(): void {
try {
notifyRead.value = localStorage.getItem(NOTIFY_READ_KEY) === notifyFingerprint()
} catch {
notifyRead.value = false
}
}
function dismissNotify(): void {
notifyOpen.value = false
notifyRead.value = true
try {
localStorage.setItem(NOTIFY_READ_KEY, notifyFingerprint())
} catch {
/* ignore */
}
}
// 配置加载完成后:内容有更新则重新弹出(指纹变化视为新通知)
watch(
() => [config.loaded, notifyFingerprint()] as const,
() => {
const prev = notifyRead.value
restoreNotifyRead()
if (!prev && notifyRead.value) return // 同一指纹,保持已读
if (config.loaded && config.notifyEnabled && config.notifyContent.trim() && !notifyRead.value) {
notifyOpen.value = true
}
},
)
// 语言/主题切换不重置通知;仅手动关闭
onMounted(() => {
void config.load()
restoreNotifyRead()
if (config.loaded && config.notifyEnabled && config.notifyContent.trim() && !notifyRead.value) {
notifyOpen.value = true
}
})
</script>
<template>
<NConfigProvider
:theme="naiveTheme"
:theme-overrides="themeOverrides"
inline-theme-disabled
>
<div class="app-root">
<!-- 需求⑤环境光影渐变层始终存在+ 背景图固定层 + 半透明遮罩 -->
<div class="app-ambient" aria-hidden="true"></div>
<div
v-if="hasBackground"
class="app-bg"
aria-hidden="true"
:style="{ backgroundImage: `url(${config.backgroundUrl})` }"
></div>
<div v-if="hasBackground" class="app-bg-tint" aria-hidden="true"></div>
<NotifyPop v-if="notifyOpen" :title="config.notifyTitle" :content="config.notifyContent" @close="dismissNotify" />
<ToastHost />
<RouterView />
</div>
</NConfigProvider>
</template>
<style scoped>
.app-root {
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>