重构前端:临床仪表盘主题 + AI日报编辑版式
- 全面采用临床数据仪表盘风格:IBM Plex Mono/Sans、teal 主色、无阴影设计 - theme.css 完整重写设计 token,支持亮色/暗色/跟随系统三挡切换 - ThemeControls 简化为图标三键切换组件 - NewsCard 临床风格重排:ALL CAPS 分类标签、INSIGHT 观点块 - NewsReader 精选栏:时间轴布局,蓝点 + 垂直轨道线 - AI日报全新排版:左侧 ARCHIVE 侧边栏 + 主区编辑版式 - 报头:中文数字日期、星期、PHARMA INTEL tagline - 分节:52px teal 编号 + 22px 中文分类 + 英文副标题 - 内容卡片:圆角12px,teal 标题,chips 行(来源/时间/评分) - 页脚统计:STORIES / PROCESSED / SOURCES - 全响应式:≤900px 平板、≤640px 移动端自适应 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>医药情报</title>
|
||||
<!-- 国内可将 Google Fonts 替换为 fonts.loli.net 镜像 -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,3 +1,49 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<router-view v-slot="{ Component }">
|
||||
<component
|
||||
:is="Component"
|
||||
:theme-mode="themeMode"
|
||||
:resolved-theme="resolvedTheme"
|
||||
@set-theme-mode="setThemeMode"
|
||||
/>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const STORAGE_KEY = 'theme-mode'
|
||||
const themeMode = ref(localStorage.getItem(STORAGE_KEY) || 'system')
|
||||
const resolvedTheme = ref('light')
|
||||
let mediaQuery
|
||||
|
||||
function getSystemTheme() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
const nextTheme = themeMode.value === 'system' ? getSystemTheme() : themeMode.value
|
||||
resolvedTheme.value = nextTheme
|
||||
document.documentElement.dataset.theme = nextTheme
|
||||
document.documentElement.style.colorScheme = nextTheme
|
||||
}
|
||||
|
||||
function setThemeMode(mode) {
|
||||
themeMode.value = mode
|
||||
}
|
||||
|
||||
watch(themeMode, (mode) => {
|
||||
localStorage.setItem(STORAGE_KEY, mode)
|
||||
applyTheme()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
mediaQuery.addEventListener('change', applyTheme)
|
||||
applyTheme()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
mediaQuery?.removeEventListener('change', applyTheme)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<h3 class="card-title">{{ news.title_zh }}</h3>
|
||||
<p class="card-summary">{{ news.summary }}</p>
|
||||
<div v-if="news.opinion" class="opinion-block">
|
||||
<b>核心观点</b>{{ news.opinion }}
|
||||
<b>INSIGHT</b>{{ news.opinion }}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="kw-row">
|
||||
<span v-for="kw in (news.keywords || []).slice(0, 5)" :key="kw" class="kw-chip">{{ kw }}</span>
|
||||
<span v-for="kw in (news.keywords || []).slice(0, 4)" :key="kw" class="kw-chip">{{ kw }}</span>
|
||||
</div>
|
||||
<a :href="news.source_url" target="_blank" @click.stop class="orig-link">查看原文 →</a>
|
||||
<a :href="news.source_url" target="_blank" @click.stop class="orig-link">原文 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,56 +39,76 @@ const timeAgo = computed(() => {
|
||||
const diff = Date.now() - new Date(props.news.published_at).getTime()
|
||||
const h = Math.floor(diff / 3600000)
|
||||
if (h < 1) return '刚刚'
|
||||
if (h < 24) return `${h}小时前`
|
||||
return `${Math.floor(h / 24)}天前`
|
||||
if (h < 24) return `${h}h前`
|
||||
return `${Math.floor(h / 24)}d前`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.news-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--rule2);
|
||||
background: var(--bg-card);
|
||||
border: 0.5px solid var(--rule2);
|
||||
border-radius: var(--r);
|
||||
padding: 18px;
|
||||
padding: 16px 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.news-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow);
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--blue-bd);
|
||||
}
|
||||
.news-card.featured { border-left: 3px solid var(--blue); }
|
||||
.news-card.featured {
|
||||
border-left: 2px solid var(--blue);
|
||||
}
|
||||
|
||||
.card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
|
||||
/* top row */
|
||||
.card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
.spacer { flex: 1; }
|
||||
|
||||
.score-badge {
|
||||
flex-shrink: 0;
|
||||
padding: 2px 7px;
|
||||
border-radius: 5px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.badge-red { background: #fef2f2; color: #dc2626; }
|
||||
.badge-amber { background: #fffbeb; color: #d97706; }
|
||||
.badge-blue { background: #eff6ff; color: #2563eb; }
|
||||
.badge-gray { background: #f9fafb; color: #6b7280; }
|
||||
.badge-red { background: var(--badge-red-bg); color: var(--badge-red-text); }
|
||||
.badge-amber { background: var(--badge-amber-bg); color: var(--badge-amber-text); }
|
||||
.badge-blue { background: var(--badge-blue-bg); color: var(--badge-blue-text); }
|
||||
.badge-gray { background: var(--badge-gray-bg); color: var(--badge-gray-text); }
|
||||
|
||||
.cat-label { font-size: 12px; color: var(--t3); }
|
||||
.spacer { flex: 1; }
|
||||
.source-meta { font-size: 11px; color: var(--t4); font-family: var(--mono); }
|
||||
.cat-label {
|
||||
font-size: 10px;
|
||||
font-family: var(--mono);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--t4);
|
||||
}
|
||||
.source-meta {
|
||||
font-size: 10px;
|
||||
font-family: var(--mono);
|
||||
color: var(--t4);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
/* content */
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--t1);
|
||||
line-height: 1.45;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.card-summary {
|
||||
font-size: 13px;
|
||||
font-size: 12.5px;
|
||||
color: var(--t3);
|
||||
line-height: 1.65;
|
||||
margin-bottom: 10px;
|
||||
@@ -99,41 +119,45 @@ const timeAgo = computed(() => {
|
||||
}
|
||||
|
||||
.opinion-block {
|
||||
background: #eff6ff;
|
||||
border-left: 2px solid rgba(37, 99, 235, 0.35);
|
||||
border-radius: 0 6px 6px 0;
|
||||
padding: 8px 12px;
|
||||
background: var(--soft-blue);
|
||||
border-left: 2px solid var(--blue-bd);
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding: 7px 11px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 12.5px;
|
||||
font-size: 12px;
|
||||
color: var(--t2);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.opinion-block b {
|
||||
display: block;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 3px;
|
||||
color: var(--blue);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* footer */
|
||||
.card-footer { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.kw-row { display: flex; gap: 5px; flex-wrap: wrap; flex: 1; }
|
||||
.kw-row { display: flex; gap: 4px; flex-wrap: wrap; flex: 1; }
|
||||
.kw-chip {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-family: var(--mono);
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
background: #eff6ff;
|
||||
color: #3b82f6;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--soft-blue);
|
||||
color: var(--blue-2);
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.orig-link {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-family: var(--mono);
|
||||
color: var(--blue);
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.orig-link:hover { color: #1d4ed8; }
|
||||
.orig-link:hover { color: var(--link-hover); }
|
||||
</style>
|
||||
|
||||
59
frontend/src/components/ThemeControls.vue
Normal file
59
frontend/src/components/ThemeControls.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="theme-toggle" role="group" aria-label="主题">
|
||||
<button
|
||||
v-for="item in modes"
|
||||
:key="item.value"
|
||||
:class="['t-btn', { active: themeMode === item.value }]"
|
||||
:title="item.title"
|
||||
@click="$emit('set-theme-mode', item.value)"
|
||||
>
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
themeMode: { type: String, required: true },
|
||||
resolvedTheme: { type: String, required: true },
|
||||
})
|
||||
defineEmits(['set-theme-mode'])
|
||||
|
||||
const modes = [
|
||||
{ value: 'light', title: '浅色', icon: 'Sunny' },
|
||||
{ value: 'dark', title: '深色', icon: 'Moon' },
|
||||
{ value: 'system', title: '系统', icon: 'Monitor' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 2px;
|
||||
gap: 1px;
|
||||
border: 1px solid var(--rule2);
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--bg-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.t-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 4px;
|
||||
color: var(--t4);
|
||||
font-size: 13px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.t-btn:hover { color: var(--t1); }
|
||||
.t-btn.active { background: var(--blue-gl); color: var(--blue); border: 1px solid var(--blue-bd); }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.theme-toggle { order: 10; }
|
||||
}
|
||||
</style>
|
||||
@@ -1,64 +1,147 @@
|
||||
/* Light theme design tokens */
|
||||
/* ─────────────────────────────────────────────────────────────────
|
||||
Clinical design-system — IBM Plex Mono + IBM Plex Sans
|
||||
Light = clinical white | Dark = deep navy #050D12
|
||||
───────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* ── Light ────────────────────────────────────────────────────── */
|
||||
:root {
|
||||
--bg: #f0f2f5;
|
||||
--bg-card: #ffffff;
|
||||
--bg-hover: #f0f4ff;
|
||||
--bg-1: #ffffff;
|
||||
--bg-2: #f8f9fc;
|
||||
--bg-hi: #f3f4f6;
|
||||
--bg: #F0F6FA;
|
||||
--bg-card: #FFFFFF;
|
||||
--bg-hover: rgba(13,155,142,0.05);
|
||||
--bg-1: #FFFFFF;
|
||||
--bg-2: #F5F9FC;
|
||||
--bg-hi: rgba(13,155,142,0.06);
|
||||
|
||||
--blue: #2563eb;
|
||||
--blue-2: #3b82f6;
|
||||
--blue-gl: rgba(37, 99, 235, 0.06);
|
||||
--blue-bd: rgba(37, 99, 235, 0.2);
|
||||
/* primary accent — clinical teal */
|
||||
--blue: #0D9B8E;
|
||||
--blue-2: #13B5A7;
|
||||
--blue-gl: rgba(13,155,142,0.06);
|
||||
--blue-bd: rgba(13,155,142,0.22);
|
||||
|
||||
--violet: #7c3aed;
|
||||
--cyan: #0ea5e9;
|
||||
--mint: #10b981;
|
||||
--amber: #f59e0b;
|
||||
--red: #ef4444;
|
||||
--violet: #7C3AED;
|
||||
--cyan: #06B6D4;
|
||||
--mint: #0D9B8E;
|
||||
--amber: #B07A0A;
|
||||
--red: #CC3333;
|
||||
--link-hover:#0A7D72;
|
||||
|
||||
--t1: #111827;
|
||||
--t2: #374151;
|
||||
--t3: #6b7280;
|
||||
--t4: #9ca3af;
|
||||
--ok-bg: rgba(13,155,142,0.08);
|
||||
--ok-bd: rgba(13,155,142,0.22);
|
||||
--ok-text: #0D9B8E;
|
||||
--soft-blue: rgba(13,155,142,0.07);
|
||||
--soft-green: rgba(22,163,74,0.07);
|
||||
|
||||
--rule: #f3f4f6;
|
||||
--rule2: #e5e7eb;
|
||||
--badge-red-bg: rgba(204,51,51,0.10);
|
||||
--badge-red-text: #CC3333;
|
||||
--badge-amber-bg: rgba(176,122,10,0.10);
|
||||
--badge-amber-text:#B07A0A;
|
||||
--badge-blue-bg: rgba(13,155,142,0.10);
|
||||
--badge-blue-text: #0D9B8E;
|
||||
--badge-gray-bg: rgba(5,13,18,0.06);
|
||||
--badge-gray-text: rgba(5,13,18,0.46);
|
||||
|
||||
--sans: 'Noto Sans SC', system-ui, sans-serif;
|
||||
--mono: 'JetBrains Mono', 'Courier New', monospace;
|
||||
--t1: #050D12;
|
||||
--t2: rgba(5,13,18,0.76);
|
||||
--t3: rgba(5,13,18,0.52);
|
||||
--t4: rgba(5,13,18,0.36);
|
||||
|
||||
--r: 12px;
|
||||
--r-sm: 8px;
|
||||
--rule: rgba(13,155,142,0.10);
|
||||
--rule2: rgba(13,155,142,0.20);
|
||||
|
||||
--sans: 'IBM Plex Sans', system-ui, sans-serif;
|
||||
--mono: 'IBM Plex Mono', 'Courier New', monospace;
|
||||
|
||||
--r: 8px;
|
||||
--r-sm: 5px;
|
||||
--r-pill: 9999px;
|
||||
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
|
||||
--shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
--shadow-lg: 0 8px 24px rgba(37,99,235,0.12);
|
||||
/* clinical: no decorative shadows */
|
||||
--shadow-sm: none;
|
||||
--shadow: none;
|
||||
--shadow-lg: none;
|
||||
}
|
||||
|
||||
/* ── Dark ─────────────────────────────────────────────────────── */
|
||||
:root[data-theme="dark"] {
|
||||
--bg: #050D12;
|
||||
--bg-card: #0B1820;
|
||||
--bg-hover: rgba(61,217,198,0.06);
|
||||
--bg-1: #0B1820;
|
||||
--bg-2: #071018;
|
||||
--bg-hi: rgba(61,217,198,0.06);
|
||||
|
||||
--blue: #3DD9C6;
|
||||
--blue-2: #3DD9C6;
|
||||
--blue-gl: rgba(61,217,198,0.06);
|
||||
--blue-bd: rgba(61,217,198,0.20);
|
||||
|
||||
--violet: #A78BFA;
|
||||
--cyan: #22D3EE;
|
||||
--mint: #3DD9C6;
|
||||
--amber: #F5C97A;
|
||||
--red: #FF6B6B;
|
||||
--link-hover:#5DE8D5;
|
||||
|
||||
--ok-bg: rgba(61,217,198,0.10);
|
||||
--ok-bd: rgba(61,217,198,0.20);
|
||||
--ok-text: #3DD9C6;
|
||||
--soft-blue: rgba(61,217,198,0.06);
|
||||
--soft-green: rgba(61,217,198,0.06);
|
||||
|
||||
--badge-red-bg: rgba(255,107,107,0.15);
|
||||
--badge-red-text: #FF6B6B;
|
||||
--badge-amber-bg: rgba(245,201,122,0.15);
|
||||
--badge-amber-text:#F5C97A;
|
||||
--badge-blue-bg: rgba(61,217,198,0.12);
|
||||
--badge-blue-text: #3DD9C6;
|
||||
--badge-gray-bg: rgba(255,255,255,0.08);
|
||||
--badge-gray-text: rgba(255,255,255,0.40);
|
||||
|
||||
--t1: #FFFFFF;
|
||||
--t2: rgba(255,255,255,0.76);
|
||||
--t3: rgba(255,255,255,0.50);
|
||||
--t4: rgba(255,255,255,0.30);
|
||||
|
||||
--rule: rgba(61,217,198,0.08);
|
||||
--rule2: rgba(61,217,198,0.15);
|
||||
}
|
||||
|
||||
/* ── Global base ──────────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
html { font-size: 16px; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--bg);
|
||||
color: var(--t1);
|
||||
font-family: var(--sans);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
transition: background 0.25s, color 0.25s;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
button { cursor: pointer; font-family: inherit; border: none; background: none; }
|
||||
input, select, textarea { font-family: inherit; }
|
||||
button { background: none; border: none; cursor: pointer; font: inherit; }
|
||||
input, select, textarea { font: inherit; }
|
||||
|
||||
/* Element Plus light theme */
|
||||
.el-input__wrapper { background: #fff !important; box-shadow: 0 0 0 1px var(--rule2) !important; }
|
||||
/* Element Plus overrides */
|
||||
.el-input__wrapper { background: var(--bg-card) !important; box-shadow: 0 0 0 1px var(--rule2) !important; }
|
||||
.el-input__inner { color: var(--t1) !important; }
|
||||
.el-select-dropdown { background: #fff; border-color: var(--rule2); }
|
||||
.el-select-dropdown { background: var(--bg-card); border-color: var(--rule2); }
|
||||
.el-select-dropdown__item { color: var(--t2); }
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover { background: var(--bg-hover); }
|
||||
.el-table { --el-table-bg-color: #fff; --el-table-header-bg-color: var(--bg-2); }
|
||||
.el-table {
|
||||
--el-table-bg-color: var(--bg-card);
|
||||
--el-table-tr-bg-color: var(--bg-card);
|
||||
--el-table-header-bg-color: var(--bg-2);
|
||||
--el-table-text-color: var(--t2);
|
||||
--el-table-header-text-color: var(--t1);
|
||||
--el-table-border-color: var(--rule);
|
||||
--el-table-row-hover-bg-color: var(--bg-hover);
|
||||
}
|
||||
:root[data-theme="dark"] .el-popper,
|
||||
:root[data-theme="dark"] .el-select__popper.el-popper {
|
||||
background: var(--bg-card); border-color: var(--rule2); color: var(--t2);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
<div class="admin-layout">
|
||||
<!-- ── 登录 ──────────────────────────────────────────────── -->
|
||||
<div v-if="!authed" class="login-screen">
|
||||
<div class="login-theme">
|
||||
<ThemeControls
|
||||
:theme-mode="themeMode"
|
||||
:resolved-theme="resolvedTheme"
|
||||
@set-theme-mode="$emit('set-theme-mode', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="login-card">
|
||||
<div class="login-logo">PI</div>
|
||||
<div class="login-title">管理后台</div>
|
||||
@@ -33,6 +40,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="admin-right">
|
||||
<ThemeControls
|
||||
:theme-mode="themeMode"
|
||||
:resolved-theme="resolvedTheme"
|
||||
@set-theme-mode="$emit('set-theme-mode', $event)"
|
||||
/>
|
||||
<router-link to="/" class="exit-link">← 返回首页</router-link>
|
||||
<button class="logout-btn" @click="logout">退出</button>
|
||||
</div>
|
||||
@@ -169,12 +181,19 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import ThemeControls from '../components/ThemeControls.vue'
|
||||
import {
|
||||
getLLMConfig, saveLLMConfig, testLLMConfig,
|
||||
getSources, addSource, toggleSource, deleteSource,
|
||||
triggerCrawl as apiTrigger, getStats, getLogs,
|
||||
} from '../api/index.js'
|
||||
|
||||
defineProps({
|
||||
themeMode: { type: String, required: true },
|
||||
resolvedTheme: { type: String, required: true },
|
||||
})
|
||||
defineEmits(['set-theme-mode'])
|
||||
|
||||
// ── Auth ──────────────────────────────────────────────────────────────────────
|
||||
const authed = ref(!!localStorage.getItem('admin_token'))
|
||||
const tokenInput = ref('')
|
||||
@@ -321,7 +340,9 @@ onUnmounted(() => stopPolling())
|
||||
.login-screen {
|
||||
min-height: 100vh;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
.login-theme { position: absolute; top: 20px; right: 24px; }
|
||||
.login-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--rule2);
|
||||
@@ -463,4 +484,15 @@ onUnmounted(() => stopPolling())
|
||||
.log-info .log-level { color: var(--blue-2); }
|
||||
.log-error .log-level { color: var(--red); }
|
||||
.log-warn .log-level { color: var(--amber); }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-theme { top: 16px; right: 16px; left: 16px; }
|
||||
.login-card { width: calc(100% - 32px); padding: 32px 24px; }
|
||||
.admin-topbar { height: auto; min-height: 56px; padding: 10px 16px; flex-wrap: wrap; gap: 10px; }
|
||||
.admin-logo { flex: 1 1 auto; }
|
||||
.admin-nav { order: 3; width: 100%; overflow-x: auto; }
|
||||
.admin-right { width: 100%; flex-wrap: wrap; }
|
||||
.admin-body { padding: 16px; }
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,8 @@ export default defineConfig({
|
||||
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
|
||||
},
|
||||
server: {
|
||||
allowedHosts: ['ddc.chenwuzhu.cn'],
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: ['localhost', '127.0.0.1', 'ddc.chenwuzhu.cn'],
|
||||
proxy: {
|
||||
'/api': { target: 'http://127.0.0.1:8000', changeOrigin: true },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user