Includes search engine improvements, Alembic migrations, new services (pubmed_daily_update, query_expansion), frontend updates, and documentation sync.
238 lines
7.6 KiB
Vue
238 lines
7.6 KiB
Vue
<script setup lang="ts">
|
||
import { watchEffect } from 'vue'
|
||
import { NConfigProvider, NMessageProvider, NDialogProvider, darkTheme, zhCN, dateZhCN } from 'naive-ui'
|
||
import { themeOverrides } from './theme'
|
||
import { useUiStore } from './stores/ui'
|
||
import NetworkStatus from './components/common/NetworkStatus.vue'
|
||
|
||
const ui = useUiStore()
|
||
|
||
// 暗色模式:在 html 上加 class,CSS 变量驱动自定义样式
|
||
watchEffect(() => {
|
||
document.documentElement.classList.toggle('dark', ui.isDark)
|
||
})
|
||
|
||
// 会话初始化在 router.beforeEach 中触发(确保路由守卫前完成)
|
||
</script>
|
||
|
||
<template>
|
||
<NConfigProvider :theme="ui.isDark ? darkTheme : null" :theme-overrides="themeOverrides" :locale="zhCN" :date-locale="dateZhCN">
|
||
<NMessageProvider>
|
||
<NDialogProvider>
|
||
<NetworkStatus />
|
||
<RouterView />
|
||
</NDialogProvider>
|
||
</NMessageProvider>
|
||
</NConfigProvider>
|
||
</template>
|
||
|
||
<style>
|
||
/* 暗色模式 CSS 变量 — 所有自定义样式使用这些变量即可自动适配 */
|
||
:root {
|
||
--bg-card: #fff;
|
||
--bg-page: #f5f7fa;
|
||
--bg-hover: #fafafa;
|
||
--text-primary: #333;
|
||
--text-secondary: #666;
|
||
--text-muted: #999;
|
||
--border-color: #eee;
|
||
--shadow: rgba(0,0,0,0.06);
|
||
--preview-ai-bg: #f8f9fb;
|
||
--kw-pill-bg: #f0f7ff;
|
||
--kw-pill-color: #1a5276;
|
||
--diff-delete-bg: #ffeef0;
|
||
--diff-delete-text: #cf1322;
|
||
--diff-insert-bg: #e6ffed;
|
||
--diff-insert-text: #389e0d;
|
||
}
|
||
|
||
html.dark {
|
||
--bg-card: #1e1e1e;
|
||
--bg-page: #141414;
|
||
--bg-hover: #2a2a2a;
|
||
--text-primary: #e0e0e0;
|
||
--text-secondary: #b0b0b0;
|
||
--text-muted: #808080;
|
||
--border-color: #333;
|
||
--shadow: rgba(0,0,0,0.3);
|
||
--preview-ai-bg: #252525;
|
||
--kw-pill-bg: #1a2a3a;
|
||
--kw-pill-color: #6ab0e0;
|
||
--diff-delete-bg: #4a2020;
|
||
--diff-delete-text: #ff8a80;
|
||
--diff-insert-bg: #1a3a1a;
|
||
--diff-insert-text: #a5d6a7;
|
||
--hl-toolbar-bg: #2a2a2a;
|
||
--hl-tb-btn-color: #e0e0e0;
|
||
--hl-tb-btn-hover: #444;
|
||
--badge-warning-bg: #4a3520;
|
||
--badge-warning-text: #ffb74d;
|
||
--badge-info-bg: #1a2a4a;
|
||
--badge-info-text: #64b5f6;
|
||
--badge-error-bg: #4a2020;
|
||
--badge-error-text: #ef9a9a;
|
||
--badge-success-bg: #1a3a1a;
|
||
--badge-success-text: #a5d6a7;
|
||
--accent-border: #8a6a30;
|
||
}
|
||
|
||
/* ═══ 全局按钮统一样式(class .sky-btn) ═══ */
|
||
/* 使用 class 而非全局 .n-button,避免影响 text 按钮(naive-ui text 按钮无 --text class 可排除) */
|
||
.sky-btn {
|
||
--n-color: #e6f4ff !important;
|
||
--n-color-hover: #d0ebff !important;
|
||
--n-color-pressed: #b7dfff !important;
|
||
--n-color-focus: #d0ebff !important;
|
||
--n-text-color: var(--text-primary) !important;
|
||
--n-text-color-hover: #1a1a1a !important;
|
||
--n-text-color-pressed: #1a1a1a !important;
|
||
--n-border: 1px solid #b3d8ff !important;
|
||
--n-border-hover: 1px solid #8ac0ff !important;
|
||
--n-border-pressed: 1px solid #6ba8ff !important;
|
||
}
|
||
/* ghost */
|
||
.sky-btn.n-button--ghost {
|
||
--n-text-color: #2a6a9a !important;
|
||
--n-border: 1px solid #b3d8ff !important;
|
||
--n-border-hover: 1px solid #8ac0ff !important;
|
||
--n-border-pressed: 1px solid #6ba8ff !important;
|
||
--n-color: transparent !important;
|
||
}
|
||
/* error */
|
||
.sky-btn.n-button--error-type {
|
||
--n-color: #ffeef0 !important;
|
||
--n-color-hover: #ffdbdd !important;
|
||
--n-color-pressed: #ffc8cb !important;
|
||
--n-color-focus: #ffdbdd !important;
|
||
--n-border: 1px solid #e8b4b4 !important;
|
||
--n-border-hover: 1px solid #d08080 !important;
|
||
--n-border-pressed: 1px solid #c06060 !important;
|
||
}
|
||
.sky-btn.n-button--ghost.n-button--error-type {
|
||
--n-text-color: #d03050 !important;
|
||
--n-border: 1px solid #e8b4b4 !important;
|
||
--n-border-hover: 1px solid #d08080 !important;
|
||
--n-border-pressed: 1px solid #c06060 !important;
|
||
}
|
||
/* warning */
|
||
.sky-btn.n-button--warning-type {
|
||
--n-color: #fff7e6 !important;
|
||
--n-color-hover: #ffefd0 !important;
|
||
--n-color-pressed: #ffe6b8 !important;
|
||
--n-color-focus: #ffefd0 !important;
|
||
--n-border: 1px solid #e8c888 !important;
|
||
--n-border-hover: 1px solid #d0a850 !important;
|
||
--n-border-pressed: 1px solid #c09030 !important;
|
||
}
|
||
.sky-btn.n-button--ghost.n-button--warning-type {
|
||
--n-text-color: #d48806 !important;
|
||
--n-border: 1px solid #e8c888 !important;
|
||
--n-border-hover: 1px solid #d0a850 !important;
|
||
--n-border-pressed: 1px solid #c09030 !important;
|
||
}
|
||
/* dark mode */
|
||
html.dark .sky-btn {
|
||
--n-color: #1a3a5a !important;
|
||
--n-color-hover: #2471a3 !important;
|
||
--n-color-pressed: #154360 !important;
|
||
--n-color-focus: #2471a3 !important;
|
||
--n-text-color: #fff !important;
|
||
--n-text-color-hover: #fff !important;
|
||
--n-text-color-pressed: #fff !important;
|
||
--n-text-color-focus: #fff !important;
|
||
--n-border: 1px solid #4a7a9a !important;
|
||
--n-border-hover: 1px solid #6ab0e0 !important;
|
||
--n-border-pressed: 1px solid #8ac0ff !important;
|
||
--n-border-focus: 1px solid #8ac0ff !important;
|
||
}
|
||
html.dark .sky-btn.n-button--ghost {
|
||
--n-text-color: #6ab0e0 !important;
|
||
--n-text-color-hover: #8ac8ff !important;
|
||
--n-text-color-pressed: #8ac8ff !important;
|
||
--n-border: 1px solid #4a7a9a !important;
|
||
--n-border-hover: 1px solid #6ab0e0 !important;
|
||
--n-border-pressed: 1px solid #8ac0ff !important;
|
||
}
|
||
|
||
/* Dark mode NSelect text visibility */
|
||
html.dark .n-select .n-base-selection-label {
|
||
--n-text-color: #e0e0e0 !important;
|
||
}
|
||
|
||
/* Dark mode NSelect popup — hover/selected 选项文字颜色 */
|
||
html.dark .n-base-select-menu .n-base-select-option--hover {
|
||
color: #fff !important;
|
||
}
|
||
html.dark .n-base-select-menu .n-base-select-option--selected {
|
||
color: #fff !important;
|
||
}
|
||
html.dark .n-base-select-menu .n-base-select-option--hover .n-base-select-option__check,
|
||
html.dark .n-base-select-menu .n-base-select-option--selected .n-base-select-option__check {
|
||
color: #fff !important;
|
||
}
|
||
html.dark .sky-btn:focus,
|
||
html.dark .sky-btn:focus-visible,
|
||
html.dark .sky-btn:active {
|
||
color: #fff !important;
|
||
}
|
||
html.dark ::selection {
|
||
background: #1a6bb0;
|
||
color: #fff;
|
||
}
|
||
html.dark .sky-btn.n-button--error-type {
|
||
--n-color: #4a2020 !important;
|
||
--n-color-hover: #6a3030 !important;
|
||
--n-color-pressed: #3a1010 !important;
|
||
--n-color-focus: #6a3030 !important;
|
||
--n-text-color: #ef9a9a !important;
|
||
--n-text-color-hover: #fff !important;
|
||
--n-text-color-pressed: #fff !important;
|
||
--n-border: 1px solid #c06060 !important;
|
||
--n-border-hover: 1px solid #ef9a9a !important;
|
||
--n-border-pressed: 1px solid #ffb0b0 !important;
|
||
}
|
||
html.dark .sky-btn.n-button--ghost.n-button--error-type {
|
||
--n-text-color: #ef9a9a !important;
|
||
--n-border: 1px solid #c06060 !important;
|
||
--n-border-hover: 1px solid #ef9a9a !important;
|
||
--n-border-pressed: 1px solid #ffb0b0 !important;
|
||
}
|
||
html.dark .sky-btn.n-button--warning-type {
|
||
--n-color: #4a3520 !important;
|
||
--n-color-hover: #6a4530 !important;
|
||
--n-color-pressed: #3a2510 !important;
|
||
--n-color-focus: #6a4530 !important;
|
||
--n-text-color: #ffb74d !important;
|
||
--n-text-color-hover: #fff !important;
|
||
--n-text-color-pressed: #fff !important;
|
||
--n-border: 1px solid #c08030 !important;
|
||
--n-border-hover: 1px solid #ffb74d !important;
|
||
--n-border-pressed: 1px solid #ffcc80 !important;
|
||
}
|
||
html.dark .sky-btn.n-button--ghost.n-button--warning-type {
|
||
--n-text-color: #ffb74d !important;
|
||
--n-border: 1px solid #c08030 !important;
|
||
--n-border-hover: 1px solid #ffb74d !important;
|
||
--n-border-pressed: 1px solid #ffcc80 !important;
|
||
}
|
||
|
||
/* 移动端:防止任何水平溢出(html + body 双保险) */
|
||
@media (max-width: 768px) {
|
||
html, body { overflow-x: hidden; width: 100%; max-width: 100vw; }
|
||
#app { overflow-x: hidden; max-width: 100vw; }
|
||
}
|
||
|
||
/* SEO: 视觉隐藏但搜索引擎可读 */
|
||
.visually-hidden {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
padding: 0;
|
||
margin: -1px;
|
||
overflow: hidden;
|
||
clip: rect(0, 0, 0, 0);
|
||
white-space: nowrap;
|
||
border: 0;
|
||
}
|
||
</style>
|