feat: 官网配品牌蓝——顶栏 0073B7、页脚 0070B2、详情页头部块 006DAD
顶栏改常驻蓝底白字(滚动仅加阴影,不再白底透明);页脚底色改 0070B2 配白字;详情页按 PyPI banner--project 版式把包名+摘要+安装命令做成 006DAD 蓝底头部块,COPY 按钮白底蓝字。
This commit is contained in:
1 parent
34fa817a31
commit
63ad1eb965
3 files changed
+186
-150
No files matched your search
@@ -31,15 +31,12 @@ const year = new Date().getFullYear()
|
||||
|
||||
<style scoped>
|
||||
.footer {
|
||||
--text: #e8f1fa;
|
||||
--text-dim: #9db8cf;
|
||||
--text-faint: #6f88a0;
|
||||
--line-soft: rgba(255, 255, 255, 0.09);
|
||||
--accent: #7cc4ff;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: var(--bg-deep);
|
||||
--foot-fg: #ffffff;
|
||||
--foot-fg-dim: rgba(255, 255, 255, 0.82);
|
||||
--foot-fg-faint: rgba(255, 255, 255, 0.68);
|
||||
background: #0070b2;
|
||||
padding: 34px 0 30px;
|
||||
color: #6f88a0;
|
||||
color: var(--foot-fg-dim);
|
||||
}
|
||||
|
||||
.footer-inner {
|
||||
@@ -60,11 +57,11 @@ const year = new Date().getFullYear()
|
||||
.f-brand-line svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
fill: var(--accent);
|
||||
fill: var(--foot-fg);
|
||||
}
|
||||
|
||||
.f-name {
|
||||
color: #e8f1fa;
|
||||
color: var(--foot-fg);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 750;
|
||||
}
|
||||
@@ -80,16 +77,17 @@ const year = new Date().getFullYear()
|
||||
}
|
||||
|
||||
.foot-links a {
|
||||
color: #9db8cf;
|
||||
color: var(--foot-fg-dim);
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.foot-links a:hover {
|
||||
color: #7cc4ff;
|
||||
color: var(--foot-fg);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.f-copy {
|
||||
color: #6f88a0;
|
||||
color: var(--foot-fg-faint);
|
||||
font-size: 0.76rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
@@ -53,23 +53,23 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 常驻品牌蓝顶栏(#0073B7 底 + 白字),滚动时只加一层阴影 */
|
||||
.site-header {
|
||||
--hdr-fg: #ffffff;
|
||||
--hdr-fg-dim: rgba(255, 255, 255, 0.82);
|
||||
--hdr-line: rgba(255, 255, 255, 0.45);
|
||||
position: fixed;
|
||||
inset: 0 0 auto;
|
||||
z-index: 60;
|
||||
height: var(--nav-h);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: background 0.35s var(--ease-out), border-color 0.35s var(--ease-out),
|
||||
backdrop-filter 0.35s var(--ease-out);
|
||||
background: #0073b7;
|
||||
transition: box-shadow 0.3s var(--ease-out);
|
||||
}
|
||||
|
||||
.site-header.scrolled {
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
border-bottom-color: var(--line-soft);
|
||||
box-shadow: 0 2px 12px -6px rgba(10, 26, 47, 0.45);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
@@ -88,17 +88,18 @@ onBeforeUnmount(() => {
|
||||
.brand svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: var(--accent);
|
||||
fill: var(--hdr-fg);
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-weight: 750;
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--hdr-fg);
|
||||
}
|
||||
|
||||
.brand-sub {
|
||||
color: var(--accent);
|
||||
color: var(--hdr-fg-dim);
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.22em;
|
||||
transform: translateY(1px);
|
||||
@@ -111,8 +112,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.lang-btn {
|
||||
border: 1px solid var(--line-soft);
|
||||
color: var(--text-dim);
|
||||
border: 1px solid var(--hdr-line);
|
||||
color: var(--hdr-fg);
|
||||
width: 42px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
@@ -122,15 +123,14 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.lang-btn:hover {
|
||||
border-color: var(--line);
|
||||
color: var(--accent-strong);
|
||||
background: rgba(var(--accent-rgb), 0.06);
|
||||
border-color: #fff;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.nav-pill {
|
||||
color: var(--text-dim);
|
||||
color: var(--hdr-fg);
|
||||
font-size: 0.86rem;
|
||||
border: 1px solid var(--line-soft);
|
||||
border: 1px solid var(--hdr-line);
|
||||
border-radius: 999px;
|
||||
padding: 7px 15px;
|
||||
transition: color 0.22s var(--ease-out), border-color 0.22s var(--ease-out),
|
||||
@@ -138,9 +138,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.nav-pill:hover {
|
||||
color: var(--accent-strong);
|
||||
border-color: var(--line);
|
||||
background: rgba(var(--accent-rgb), 0.05);
|
||||
border-color: #fff;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
|
||||
+157
-118
@@ -3,8 +3,9 @@
|
||||
* 包详情页 `/project/<包名>`(PyPI 风格版式)
|
||||
*
|
||||
* 数据来自 `/API/Venom/Project/<包名>`(主服务按域名反查产品并回源子服务器)。
|
||||
* 左侧为 README(Markdown → 消毒 HTML),右侧为元数据 / 依赖 / 版本历史卡片。
|
||||
* 数据到达前展示骨架 loading 层,避免首屏空白。
|
||||
* 顶部为 PyPI `banner--project` 式蓝色头部块(包名 + 摘要 + 安装命令),
|
||||
* 下方左栏为 README(Markdown → 消毒 HTML),右栏为元数据 / 依赖 / 版本历史卡片。
|
||||
* 数据到达前头部与正文都展示骨架 loading,避免首屏空白。
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
@@ -78,19 +79,41 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
|
||||
<template>
|
||||
<section class="project">
|
||||
<div class="container">
|
||||
<RouterLink class="back" to="/">{{ t.project.back }}</RouterLink>
|
||||
<!-- ==================== 顶部头部块(banner) ==================== -->
|
||||
<div v-if="!notFound && !error" class="banner">
|
||||
<div class="container banner-inner">
|
||||
<RouterLink class="back" to="/">{{ t.project.back }}</RouterLink>
|
||||
|
||||
<!-- ==================== loading 层(骨架) ==================== -->
|
||||
<template v-if="project">
|
||||
<h1 class="banner-name">
|
||||
{{ project.PackageName }}
|
||||
<span v-if="project.LatestVersion" class="banner-ver">{{ project.LatestVersion }}</span>
|
||||
</h1>
|
||||
<p class="banner-summary">{{ project.PackageDesc || t.project.noDesc }}</p>
|
||||
<div class="install">
|
||||
<code class="install-cmd">{{ installCommand }}</code>
|
||||
<button class="copy" @click="copyInstall">{{ copied ? 'OK' : 'COPY' }}</button>
|
||||
</div>
|
||||
<p class="banner-hint">{{ t.project.installHint }}</p>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="sk sk-name on-blue"></div>
|
||||
<div class="sk sk-line on-blue"></div>
|
||||
<div class="sk sk-bar on-blue"></div>
|
||||
<p class="banner-hint">{{ t.project.loading }}</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 正文 ==================== -->
|
||||
<div class="container body">
|
||||
<!-- loading 层(骨架) -->
|
||||
<div v-if="loading" class="layout" aria-busy="true" aria-live="polite">
|
||||
<div class="main">
|
||||
<div class="sk sk-title"></div>
|
||||
<div class="sk sk-line"></div>
|
||||
<div class="sk sk-line short"></div>
|
||||
<div class="sk sk-bar"></div>
|
||||
<div class="sk sk-block"></div>
|
||||
<div class="sk sk-block"></div>
|
||||
<p class="sk-hint">{{ t.project.loading }}</p>
|
||||
<div class="sk sk-block short"></div>
|
||||
</div>
|
||||
<aside class="side">
|
||||
<div class="sk sk-card"></div>
|
||||
@@ -98,7 +121,7 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 不存在 / 失败 ==================== -->
|
||||
<!-- 不存在 / 失败 -->
|
||||
<div v-else-if="notFound" class="state">
|
||||
<p class="state-title">{{ t.project.notFound }}</p>
|
||||
<p class="state-sub">{{ route.params.name }}</p>
|
||||
@@ -111,20 +134,9 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
<button class="btn btn-ghost" @click="load">{{ t.project.retry }}</button>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 包详情 ==================== -->
|
||||
<!-- 包详情 -->
|
||||
<div v-else-if="project" class="layout">
|
||||
<div class="main">
|
||||
<h1 class="pkg-name">{{ project.PackageName }}</h1>
|
||||
<p class="pkg-desc">{{ project.PackageDesc || t.project.noDesc }}</p>
|
||||
|
||||
<div class="install">
|
||||
<code class="install-cmd">{{ installCommand }}</code>
|
||||
<button class="copy" @click="copyInstall">{{ copied ? 'OK' : 'COPY' }}</button>
|
||||
</div>
|
||||
<p class="hint">{{ t.project.installHint }}</p>
|
||||
|
||||
<hr class="rule" />
|
||||
|
||||
<article class="readme">
|
||||
<div v-if="readmeHtml" class="md" v-html="readmeHtml"></div>
|
||||
</article>
|
||||
@@ -195,21 +207,12 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
<style scoped>
|
||||
/* ==================== 页面骨架 ==================== */
|
||||
.project {
|
||||
padding-top: calc(var(--nav-h) + 28px);
|
||||
padding-bottom: clamp(3rem, 6vw, 5rem);
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
.back {
|
||||
display: inline-block;
|
||||
font-size: 0.85rem;
|
||||
color: var(--accent);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.back:hover {
|
||||
color: var(--accent-strong);
|
||||
text-decoration: underline;
|
||||
.body {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.layout {
|
||||
@@ -230,6 +233,98 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ==================== 顶部头部块(PyPI banner--project) ==================== */
|
||||
.banner {
|
||||
background: #006dad;
|
||||
color: #fff;
|
||||
padding: calc(var(--nav-h) + 22px) 0 30px;
|
||||
}
|
||||
|
||||
.banner-inner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.banner .back {
|
||||
display: inline-block;
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.banner .back:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.banner-name {
|
||||
font-size: clamp(1.7rem, 3.2vw, 2.2rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.banner-ver {
|
||||
margin-left: 10px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.banner-summary {
|
||||
margin-top: 10px;
|
||||
font-size: 1rem;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
max-width: 76ch;
|
||||
}
|
||||
|
||||
.install {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: 620px;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: var(--radius);
|
||||
padding: 9px 11px;
|
||||
}
|
||||
|
||||
.install-cmd {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: var(--font-code);
|
||||
font-size: 0.86rem;
|
||||
color: #fff;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
color: #006dad;
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 5px 11px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.copy:hover {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
.banner-hint {
|
||||
margin-top: 9px;
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
/* ==================== loading 骨架 ==================== */
|
||||
.sk {
|
||||
background: linear-gradient(90deg, #eef0f2 25%, #f6f7f8 37%, #eef0f2 63%);
|
||||
@@ -238,6 +333,17 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
animation: skShimmer 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 蓝色头部块上的骨架:半透明白 */
|
||||
.sk.on-blue {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 255, 255, 0.2) 25%,
|
||||
rgba(255, 255, 255, 0.34) 37%,
|
||||
rgba(255, 255, 255, 0.2) 63%
|
||||
);
|
||||
background-size: 400% 100%;
|
||||
}
|
||||
|
||||
@keyframes skShimmer {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
@@ -253,42 +359,43 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
}
|
||||
}
|
||||
|
||||
.sk-title {
|
||||
width: 42%;
|
||||
height: 34px;
|
||||
.sk-name {
|
||||
width: 38%;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.sk-line {
|
||||
margin-top: 14px;
|
||||
width: 78%;
|
||||
width: 62%;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.sk-line.short {
|
||||
width: 52%;
|
||||
}
|
||||
|
||||
.sk-bar {
|
||||
margin-top: 22px;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
max-width: 620px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.sk-block {
|
||||
margin-top: 26px;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.sk-block + .sk-block {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.sk-block.short {
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.sk-card {
|
||||
height: 150px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.sk-hint {
|
||||
.sk-card + .sk-card {
|
||||
margin-top: 18px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
/* ==================== 状态(不存在 / 失败) ==================== */
|
||||
@@ -297,7 +404,7 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 40px 0;
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.state-title {
|
||||
@@ -312,74 +419,6 @@ watch(() => route.params.name, load, { immediate: true })
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* ==================== 包信息 ==================== */
|
||||
.pkg-name {
|
||||
font-size: clamp(1.75rem, 3.2vw, 2.25rem);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.pkg-desc {
|
||||
margin-top: 10px;
|
||||
font-size: 1rem;
|
||||
color: var(--text-dim);
|
||||
max-width: 72ch;
|
||||
}
|
||||
|
||||
.install {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: var(--radius);
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.install-cmd {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: var(--font-code);
|
||||
font-size: 0.86rem;
|
||||
color: var(--text);
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.copy {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--accent);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 5px 10px;
|
||||
background: #fff;
|
||||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||||
}
|
||||
|
||||
.copy:hover {
|
||||
color: var(--accent-strong);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 8px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.rule {
|
||||
margin: 28px 0 24px;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line-soft);
|
||||
}
|
||||
|
||||
/* ==================== README ==================== */
|
||||
.md {
|
||||
color: var(--text);
|
||||
|
||||
Reference in new issue
Block a user