v1.0定版

This commit is contained in:
2026-05-27 17:14:08 +08:00
parent 1b7210de4f
commit 5b19d9fe69
32 changed files with 2074 additions and 2915 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

87
frontend/dist/assets/index-DzAkVrZs.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -7,8 +7,8 @@
<!-- 国内可将 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">
<script type="module" crossorigin src="/assets/index-CEnIijqK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-fkMVII4Y.css">
<script type="module" crossorigin src="/assets/index-DzAkVrZs.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DuApBvfc.css">
</head>
<body>
<div id="app"></div>

View File

View File

@@ -0,0 +1,63 @@
> pharma-intel-frontend@1.0.0 dev
> vite --host 0.0.0.0
Port 5173 is in use, trying another one...
VITE v6.4.2 ready in 360 ms
➜ Local: http://localhost:5174/
➜ Network: http://198.18.0.1:5174/
➜ Network: http://100.106.241.41:5174/
➜ Network: http://172.19.112.1:5174/
➜ Network: http://10.119.18.252:5174/
6:31:24 PM [vite] (client) hmr update /src/views/NewsReader.vue
6:31:31 PM [vite] (client) hmr update /src/views/NewsReader.vue
6:31:37 PM [vite] (client) hmr update /src/views/NewsReader.vue
6:32:13 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
6:32:21 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
6:42:38 PM [vite] (client) hmr update /src/views/NewsReader.vue
6:42:50 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
6:42:54 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
6:55:03 PM [vite] (client) hmr update /src/views/NewsReader.vue
6:55:23 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
6:55:33 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:15:11 PM [vite] (client) page reload index.html
8:16:10 PM [vite] (client) hmr update /src/styles/theme.css
8:16:21 PM [vite] (client) hmr update /src/components/ThemeControls.vue, /src/components/ThemeControls.vue?vue&type=style&index=0&scoped=c6bc2c52&lang.css
8:16:42 PM [vite] (client) hmr update /src/components/NewsCard.vue, /src/components/NewsCard.vue?vue&type=style&index=0&scoped=1d5294f0&lang.css
8:17:18 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:17:59 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:18:14 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:18:47 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:18:59 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:19:18 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:19:43 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:20:10 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:20:31 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:40:02 PM [vite] (client) hmr update /src/views/NewsReader.vue
8:40:53 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:41:13 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
8:59:15 PM [vite] (client) hmr update /src/views/NewsReader.vue
8:59:28 PM [vite] (client) hmr update /src/views/NewsReader.vue
9:00:23 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
9:00:35 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:33:14 PM [vite] (client) hmr update /src/styles/theme.css
10:33:20 PM [vite] (client) hmr update /src/styles/theme.css
10:33:26 PM [vite] (client) hmr update /src/styles/theme.css
10:33:31 PM [vite] (client) hmr update /src/styles/theme.css
10:33:36 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:33:43 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:33:48 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:33:54 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:34:00 PM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
10:34:08 PM [vite] (client) hmr update /src/components/NewsCard.vue?vue&type=style&index=0&scoped=1d5294f0&lang.css
12:42:07 AM [vite] (client) hmr update /src/components/NewsCard.vue
12:42:13 AM [vite] (client) hmr update /src/components/NewsCard.vue
12:42:19 AM [vite] (client) hmr update /src/components/NewsCard.vue?vue&type=style&index=0&scoped=1d5294f0&lang.css
12:42:25 AM [vite] (client) hmr update /src/views/NewsReader.vue
12:42:31 AM [vite] (client) hmr update /src/views/NewsReader.vue
12:42:39 AM [vite] (client) hmr update /src/views/NewsReader.vue
12:42:46 AM [vite] (client) hmr update /src/views/NewsReader.vue
12:42:54 AM [vite] (client) hmr update /src/views/NewsReader.vue?vue&type=style&index=0&scoped=94478ee1&lang.css
12:43:13 AM [vite] (client) hmr update /src/views/NewsReader.vue

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,19 @@
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- Light blue background diamond -->
<polygon points="100,20 180,100 100,180 20,100" fill="#D0E8F8"/>
<!-- Top triangle (light cyan) -->
<polygon points="100,50 140,85 60,85" fill="#A0D8F0"/>
<!-- Right triangle (cyan) -->
<polygon points="140,85 150,130 100,110" fill="#3DD9C6"/>
<!-- Bottom triangle (blue) -->
<polygon points="60,85 100,110 70,150" fill="#1E5F8F"/>
<!-- Left triangle (purple) -->
<polygon points="60,85 55,130 100,110" fill="#7B3FF2"/>
<!-- Center diamond (magenta) -->
<polygon points="100,85 115,100 100,115 85,100" fill="#D946EF"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -1,7 +1,11 @@
<template>
<div class="news-card" :class="{ featured: news.is_featured }" @click="$emit('open', news)">
<!-- cover image (shown only when available) -->
<div v-if="news.image_url" class="card-cover">
<img :src="news.image_url" :alt="news.title_zh" class="cover-img" @error="onImgError" />
</div>
<div class="card-top">
<span class="score-badge" :class="badgeClass">{{ news.importance_score?.toFixed(1) }}</span>
<span class="score-badge" :class="badgeClass">{{ Math.round(news.importance_score) }}</span>
<span class="cat-label">{{ news.category }}</span>
<span class="spacer"></span>
<span class="source-meta">{{ news.source_name }} · {{ timeAgo }}</span>
@@ -28,12 +32,18 @@ defineEmits(['open'])
const badgeClass = computed(() => {
const s = props.news.importance_score
if (s >= 9) return 'badge-red'
if (s >= 7) return 'badge-amber'
if (s >= 5) return 'badge-blue'
if (s >= 90) return 'badge-red'
if (s >= 70) return 'badge-amber'
if (s >= 50) return 'badge-blue'
return 'badge-gray'
})
function onImgError(e) {
// hide the whole cover wrapper on broken image
const el = e.target?.closest('.card-cover')
if (el) el.style.display = 'none'
}
const timeAgo = computed(() => {
if (!props.news.published_at) return ''
const diff = Date.now() - new Date(props.news.published_at).getTime()
@@ -48,9 +58,13 @@ const timeAgo = computed(() => {
.news-card {
background: var(--bg-card);
border: 0.5px solid var(--rule2);
border-top: 0.5px solid var(--glass-border);
border-left: 0.5px solid var(--glass-border);
border-radius: var(--r);
padding: 16px 18px;
cursor: pointer;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: border-color 0.15s, background 0.15s;
}
.news-card:hover {
@@ -61,6 +75,22 @@ const timeAgo = computed(() => {
border-left: 2px solid var(--blue);
}
/* cover image */
.card-cover {
margin: -16px -18px 12px;
border-radius: var(--r) var(--r) 0 0;
overflow: hidden;
height: 140px;
}
.cover-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.3s ease;
}
.news-card:hover .cover-img { transform: scale(1.03); }
/* top row */
.card-top {
display: flex;

View File

@@ -16,7 +16,7 @@
>
<span class="rank">{{ String(i + 1).padStart(2, '0') }}</span>
<div class="item-body">
<span class="item-score" :class="badgeClass(n.importance_score)">{{ n.importance_score?.toFixed(1) }}</span>
<span class="item-score" :class="badgeClass(n.importance_score)">{{ Math.round(n.importance_score) }}</span>
<span class="item-title">{{ n.title_zh }}</span>
</div>
</div>
@@ -47,8 +47,8 @@ defineProps({
defineEmits(['select', 'date-change'])
function badgeClass(score) {
if (score >= 9) return 'badge-red'
if (score >= 7) return 'badge-amber'
if (score >= 90) return 'badge-red'
if (score >= 70) return 'badge-amber'
return 'badge-blue'
}
</script>

View File

@@ -5,12 +5,15 @@
/* ── Light ────────────────────────────────────────────────────── */
:root {
--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);
--bg: #EDF3F8;
--bg-grad-a: #DAE9F5;
--bg-grad-b: #E3F2EC;
--bg-card: rgba(255, 255, 255, 0.68);
--bg-hover: rgba(13,155,142,0.06);
--bg-1: rgba(240, 246, 250, 0.82);
--bg-2: #F5F9FC;
--bg-hi: rgba(13,155,142,0.06);
--glass-border: rgba(255, 255, 255, 0.52);
/* primary accent — clinical teal */
--blue: #0D9B8E;
@@ -63,12 +66,15 @@
/* ── 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);
--bg: #050D12;
--bg-grad-a: #01080E;
--bg-grad-b: #010E09;
--bg-card: rgba(11, 24, 32, 0.52);
--bg-hover: rgba(61,217,198,0.07);
--bg-1: rgba(5, 13, 18, 0.78);
--bg-2: #071018;
--bg-hi: rgba(61,217,198,0.06);
--glass-border: rgba(61, 217, 198, 0.14);
--blue: #3DD9C6;
--blue-2: #3DD9C6;
@@ -113,7 +119,8 @@ html { font-size: 16px; }
body {
font-family: var(--sans);
background: var(--bg);
background: linear-gradient(135deg, var(--bg-grad-a) 0%, var(--bg) 45%, var(--bg-grad-b) 100%) fixed;
background-attachment: fixed;
color: var(--t1);
line-height: 1.5;
font-size: 14px;
@@ -128,7 +135,7 @@ input, select, textarea { font: inherit; }
/* 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: var(--bg-card); border-color: var(--rule2); }
.el-select-dropdown { background: var(--bg-2); 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); }

View File

@@ -6,7 +6,7 @@
<div class="header-inner">
<div class="hd-left">
<div class="logo">
<div class="logo-icon">PI</div>
<img src="@/assets/log_picture.png" alt="PI Logo" class="logo-icon" />
<span class="logo-text">医药情报</span>
</div>
<div class="hd-sep"></div>
@@ -69,7 +69,7 @@
<span class="tl-source">{{ n.source_name }}</span>
<span class="tl-rank">#{{ String(i + 1).padStart(2, '0') }}</span>
<span class="score-badge" :class="badgeClass(n.importance_score)">
{{ n.importance_score?.toFixed(1) }}
{{ Math.round(n.importance_score) }}
</span>
<span class="tl-featured">精选</span>
</div>
@@ -189,7 +189,7 @@
<span class="dig-chip">{{ n.source_name }}</span>
<span class="dig-chip dig-chip-time">{{ formatTime(n.published_at) }}</span>
<span class="dig-chip" :class="badgeClass(n.importance_score)">
{{ n.importance_score?.toFixed(1) }}
{{ Math.round(n.importance_score) }}
</span>
</div>
<p v-if="n.summary" class="dig-card-sum">{{ n.summary }}</p>
@@ -224,19 +224,25 @@
<div class="sh-tags">
<span class="cat-chip">{{ selectedNews.category }}</span>
<span class="score-badge" :class="badgeClass(selectedNews.importance_score)">
{{ selectedNews.importance_score?.toFixed(1) }}
{{ Math.round(selectedNews.importance_score) }}
</span>
</div>
<h2 class="sh-title">{{ selectedNews.title_zh }}</h2>
<p class="sh-meta">{{ selectedNews.source_name }} · {{ selectedNews.published_at?.slice(0, 10) }}</p>
</div>
<!-- cover image in detail sheet -->
<div v-if="selectedNews.image_url" class="sh-cover">
<img :src="selectedNews.image_url" :alt="selectedNews.title_zh" class="sh-cover-img"
@error="e => e.target.closest('.sh-cover').style.display='none'" />
</div>
<div class="sheet-bd">
<p class="sh-summary">{{ selectedNews.summary }}</p>
<div v-if="selectedNews.opinion" class="sh-block sh-blue">
<b>核心观点</b>{{ selectedNews.opinion }}
</div>
<div v-if="selectedNews.importance_reason" class="sh-block sh-green">
<b>评分依据 · {{ selectedNews.importance_score?.toFixed(1) }} </b>
<b>评分依据 · {{ Math.round(selectedNews.importance_score) }} </b>
{{ selectedNews.importance_reason }}
</div>
<div v-if="selectedNews.keywords?.length" class="sh-kws">
@@ -319,9 +325,9 @@ const digestCategoryCount = computed(() =>
)
function badgeClass(score) {
if (score >= 9) return 'badge-red'
if (score >= 7) return 'badge-amber'
if (score >= 5) return 'badge-blue'
if (score >= 90) return 'badge-red'
if (score >= 70) return 'badge-amber'
if (score >= 50) return 'badge-blue'
return 'badge-gray'
}
@@ -466,6 +472,8 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
z-index: 100;
background: var(--bg-1);
border-bottom: 1px solid var(--rule2);
backdrop-filter: blur(18px) saturate(1.5);
-webkit-backdrop-filter: blur(18px) saturate(1.5);
}
.header-inner {
max-width: 1280px;
@@ -483,11 +491,8 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
.logo { display: flex; align-items: center; gap: 9px; margin-right: 20px; flex-shrink: 0; }
.logo-icon {
width: 28px; height: 28px;
background: var(--blue);
border-radius: 5px;
display: flex; align-items: center; justify-content: center;
font-size: 10px; font-weight: 700; color: var(--bg);
font-family: var(--mono); letter-spacing: -0.02em;
flex-shrink: 0;
}
.logo-text {
font-size: 13px; font-weight: 700; color: var(--t1);
@@ -694,8 +699,12 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
.tl-card {
padding: 14px 16px;
border: 0.5px solid var(--rule2);
border-top: 0.5px solid var(--glass-border);
border-left: 0.5px solid var(--glass-border);
border-radius: var(--r);
background: var(--bg-card);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
@@ -817,6 +826,9 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
flex-direction: column;
border-right: 1px solid var(--rule2);
padding-right: 14px;
background: var(--bg-1);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.dig-arc-label {
font-size: 9px;
@@ -989,9 +1001,13 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
display: block;
background: var(--bg-card);
border: 0.5px solid var(--rule2);
border-top: 0.5px solid var(--glass-border);
border-left: 0.5px solid var(--glass-border);
border-radius: 12px;
padding: 16px 18px;
text-decoration: none;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: border-color 0.15s, background 0.15s;
}
.dig-card:hover {
@@ -1072,9 +1088,11 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
.sheet {
width: 100%; max-width: 660px;
background: var(--bg-1);
border-top: 1px solid var(--rule2);
border-top: 1px solid var(--glass-border);
border-radius: 12px 12px 0 0;
max-height: 88vh; overflow-y: auto; scrollbar-width: none;
backdrop-filter: blur(24px) saturate(1.4);
-webkit-backdrop-filter: blur(24px) saturate(1.4);
}
.sheet::-webkit-scrollbar { display: none; }
.sheet-handle {
@@ -1101,6 +1119,18 @@ onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
letter-spacing: 0.06em; text-transform: uppercase;
}
.sh-cover {
margin: 0;
overflow: hidden;
max-height: 220px;
}
.sh-cover-img {
width: 100%;
height: 220px;
object-fit: cover;
display: block;
}
.sheet-bd { padding: 16px 24px 0; }
.sh-summary { font-size: 13.5px; color: var(--t2); line-height: 1.75; margin-bottom: 14px; }
.sh-block {