更新了update门户站点界面和部分功能
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
const routes = [
|
||||
{ path: "/api/client/bootstrap", label: "新版客户端 Bootstrap" },
|
||||
{ path: "/api/client/releases", label: "新版发布信息" },
|
||||
{ path: "/api/client/sources", label: "新版接口源目录" },
|
||||
{ path: "/update-info.json", label: "旧版更新 JSON" },
|
||||
{ path: "/media-types.json", label: "旧版媒体源 JSON" },
|
||||
{ path: "/tool-status.json", label: "旧版工具状态" },
|
||||
{ path: "/modules.json", label: "旧版模块清单" },
|
||||
const items = [
|
||||
{ title: "旧版更新能力", body: "旧客户端继续按原有方式读取更新信息、工具状态、模块清单和下载包。" },
|
||||
{ title: "旧版媒体源能力", body: "媒体源结构保持旧字段兼容,后台保存后会同步到旧客户端可读结构。" },
|
||||
{ title: "新版动态配置", body: "新版客户端优先从服务端读取发布、接口源、健康状态和缓存策略,失败时回退旧路径。" },
|
||||
{ title: "反馈兼容", body: "旧反馈提交和状态查询入口继续保留,后台统一沉淀为反馈工单。" },
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -14,16 +11,13 @@ const routes = [
|
||||
<section class="page-heading">
|
||||
<p class="eyebrow">Compatibility</p>
|
||||
<h1>兼容说明</h1>
|
||||
<p>新旧客户端共用 update.ymhut.cn,旧路径和旧 JSON 字段继续保留。</p>
|
||||
<p>新旧客户端共用 update.ymhut.cn。门户只展示能力说明,具体接口由客户端自动选择。</p>
|
||||
</section>
|
||||
|
||||
<section class="panel wide">
|
||||
<h2>公开路径</h2>
|
||||
<div class="route-list">
|
||||
<a v-for="item in routes" :key="item.path" :href="item.path">
|
||||
<strong>{{ item.path }}</strong>
|
||||
<span>{{ item.label }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<section class="content-grid">
|
||||
<article v-for="item in items" :key="item.title" class="panel compat-card">
|
||||
<h2>{{ item.title }}</h2>
|
||||
<p>{{ item.body }}</p>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Activity, ArrowDownToLine, Database, ExternalLink, HeartPulse, Network, ShieldCheck } from "lucide-vue-next";
|
||||
import { Activity, ArrowDownToLine, Database, HeartPulse, Network, ShieldCheck } from "lucide-vue-next";
|
||||
import { usePortalState } from "../state";
|
||||
|
||||
const state = usePortalState();
|
||||
@@ -10,14 +10,12 @@ const state = usePortalState();
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow">update.ymhut.cn</p>
|
||||
<h1>统一发布、反馈与接口源状态门户</h1>
|
||||
<p>
|
||||
新版客户端通过 bootstrap 动态获取发布信息、版本日志、媒体/数据源目录和接口健康状态。旧客户端仍可继续访问
|
||||
update-info.json、media-types.json、下载路径和反馈根路径。
|
||||
</p>
|
||||
<p>统一展示 YMhut Box 的发布状态、反馈入口、接口源可用性与版本日志。新版客户端动态读取服务配置,旧客户端兼容能力继续保留。</p>
|
||||
<div class="actions">
|
||||
<a class="button primary" :href="state.downloadUrl.value"><ArrowDownToLine :size="18" />下载最新版本</a>
|
||||
<a class="button" href="/api/client/bootstrap"><ShieldCheck :size="18" />客户端配置</a>
|
||||
<RouterLink class="button" to="/compatibility"><ExternalLink :size="18" />兼容路径</RouterLink>
|
||||
<a v-if="state.downloadUrl.value" class="button primary" :href="state.downloadUrl.value"><ArrowDownToLine :size="18" />下载最新版本</a>
|
||||
<RouterLink v-else class="button primary" to="/releases"><ArrowDownToLine :size="18" />查看发布状态</RouterLink>
|
||||
<RouterLink class="button" to="/sources"><ShieldCheck :size="18" />查看接口状态</RouterLink>
|
||||
<RouterLink class="button" to="/compatibility">兼容说明</RouterLink>
|
||||
</div>
|
||||
<div class="hero-tags">
|
||||
<span>Legacy JSON 兼容</span>
|
||||
@@ -47,7 +45,7 @@ const state = usePortalState();
|
||||
|
||||
<section class="content-grid">
|
||||
<article class="panel">
|
||||
<div class="section-head"><h2>服务入口</h2><a href="/api/client/bootstrap">Bootstrap <ExternalLink :size="14" /></a></div>
|
||||
<div class="section-head"><h2>服务入口</h2><span class="badge good">运行中</span></div>
|
||||
<div class="route-list">
|
||||
<RouterLink to="/releases"><strong>发布版本</strong><span>下载包、版本公告和 update-notice 日志</span></RouterLink>
|
||||
<RouterLink to="/sources"><strong>接口源健康</strong><span>媒体源、数据源和动态客户端接口状态</span></RouterLink>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { BookOpenText, ExternalLink } from "lucide-vue-next";
|
||||
import { BookOpenText } from "lucide-vue-next";
|
||||
import { usePortalState } from "../state";
|
||||
|
||||
const state = usePortalState();
|
||||
@@ -14,7 +14,7 @@ const state = usePortalState();
|
||||
|
||||
<section class="content-grid">
|
||||
<article class="panel wide">
|
||||
<div class="section-head"><h2>发布包</h2><a href="/update-info.json">旧版 update-info.json <ExternalLink :size="14" /></a></div>
|
||||
<div class="section-head"><h2>发布包</h2><span class="badge">{{ state.packages.value.length }} 个可用包</span></div>
|
||||
<table>
|
||||
<thead><tr><th>文件</th><th>版本</th><th>平台</th><th>大小</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@@ -31,7 +31,7 @@ const state = usePortalState();
|
||||
</article>
|
||||
|
||||
<article class="panel wide">
|
||||
<div class="section-head"><h2>版本日志</h2><a href="/api/client/notices">Notices API <ExternalLink :size="14" /></a></div>
|
||||
<div class="section-head"><h2>版本日志</h2><span class="badge good">自动同步</span></div>
|
||||
<div class="notice-list">
|
||||
<section v-for="notice in state.notices.value" :key="notice.version" class="notice-card">
|
||||
<BookOpenText :size="22" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckCircle2, ExternalLink } from "lucide-vue-next";
|
||||
import { CheckCircle2 } from "lucide-vue-next";
|
||||
import { usePortalState } from "../state";
|
||||
|
||||
const state = usePortalState();
|
||||
@@ -13,7 +13,7 @@ const state = usePortalState();
|
||||
</section>
|
||||
|
||||
<section class="panel wide">
|
||||
<div class="section-head"><h2>接口源可用性</h2><a href="/api/client/sources">Sources API <ExternalLink :size="14" /></a></div>
|
||||
<div class="section-head"><h2>接口源可用性</h2><span class="badge">{{ state.sourceCount.value }} 个接口源</span></div>
|
||||
<div v-if="state.categories.value.length" class="source-board">
|
||||
<section v-for="cat in state.categories.value" :key="cat.id || cat.name" class="source-group">
|
||||
<div>
|
||||
@@ -22,11 +22,11 @@ const state = usePortalState();
|
||||
</div>
|
||||
<div class="source-list">
|
||||
<span v-for="src in cat.subcategories || []" :key="src.id || src.sourceId" :class="['badge', state.statusTone(state.sourceStatus(src))]">
|
||||
<CheckCircle2 :size="13" />{{ src.name }}
|
||||
<CheckCircle2 :size="13" />{{ src.name }}<small v-if="state.sourceStatus(src) === 'redirected'">重定向</small>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<p v-else class="empty">暂无接口源数据。后台同步旧 media-types.json 或手动添加后会显示在这里。</p>
|
||||
<p v-else class="empty">暂无接口源数据。后台同步旧媒体源配置或手动添加后会显示在这里。</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +23,7 @@ export function usePortalState() {
|
||||
return total + (cat.subcategories || []).filter((item: any) => sourceStatus(item) === "ok").length;
|
||||
}, 0));
|
||||
const availability = computed(() => sourceCount.value ? Math.round((healthyCount.value / sourceCount.value) * 100) : 0);
|
||||
const downloadUrl = computed(() => releases.value?.download_url || bootstrap.value?.release?.download_url || "/update-info.json");
|
||||
const downloadUrl = computed(() => releases.value?.download_url || bootstrap.value?.release?.download_url || packages.value[0]?.url || "");
|
||||
const appVersion = computed(() => releases.value?.app_version || bootstrap.value?.release?.app_version || latestNotice.value?.version || "未发布");
|
||||
const databaseStatus = computed(() => bootstrap.value?.health?.database?.activeProvider || bootstrap.value?.health?.database?.configProvider || "-");
|
||||
const serviceVersion = computed(() => bootstrap.value?.serviceVersion || "-");
|
||||
@@ -83,7 +83,7 @@ export function sourceStatus(item: any) {
|
||||
|
||||
export function statusTone(status: string) {
|
||||
const value = String(status || "").toLowerCase();
|
||||
if (["ok", "sqlite", "mysql", "online", "ready"].includes(value)) return "good";
|
||||
if (["ok", "redirected", "sqlite", "mysql", "online", "ready"].includes(value)) return "good";
|
||||
if (["degraded", "pending", "missing"].includes(value)) return "warn";
|
||||
if (["error", "offline", "failed"].includes(value)) return "bad";
|
||||
return "neutral";
|
||||
|
||||
@@ -2,23 +2,22 @@
|
||||
color-scheme: light;
|
||||
font-family: "Microsoft YaHei UI", "Segoe UI", Arial, sans-serif;
|
||||
color: #172033;
|
||||
background: #f7f9ff;
|
||||
background: #f5f7f4;
|
||||
--ink: #172033;
|
||||
--muted: #63718a;
|
||||
--soft: #f7f9ff;
|
||||
--soft: #f5f7f4;
|
||||
--panel: rgba(255, 255, 255, 0.82);
|
||||
--panel-strong: #ffffff;
|
||||
--line: rgba(112, 132, 170, 0.18);
|
||||
--line-strong: rgba(94, 114, 158, 0.28);
|
||||
--primary: #3b82f6;
|
||||
--primary-dark: #2563eb;
|
||||
--cyan: #06b6d4;
|
||||
--violet: #8b5cf6;
|
||||
--pink: #f472b6;
|
||||
--primary: #1f6f5b;
|
||||
--primary-dark: #155241;
|
||||
--accent: #d99227;
|
||||
--good: #059669;
|
||||
--warn: #b7791f;
|
||||
--bad: #dc2626;
|
||||
--shadow: 0 22px 65px rgba(65, 88, 140, 0.16);
|
||||
--shadow: 0 22px 65px rgba(31, 48, 40, 0.12);
|
||||
--ease: cubic-bezier(.2,.8,.2,1);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
@@ -27,9 +26,9 @@ body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
background:
|
||||
radial-gradient(circle at 8% 6%, rgba(96, 165, 250, 0.30), transparent 28%),
|
||||
radial-gradient(circle at 88% 8%, rgba(244, 114, 182, 0.20), transparent 30%),
|
||||
linear-gradient(180deg, #eef6ff 0%, #f8fbff 42%, #ffffff 100%);
|
||||
radial-gradient(circle at 8% 6%, rgba(31, 111, 91, 0.10), transparent 30%),
|
||||
radial-gradient(circle at 90% 8%, rgba(217, 146, 39, 0.10), transparent 30%),
|
||||
linear-gradient(180deg, #f2f5ef 0%, #f8faf6 42%, #ffffff 100%);
|
||||
}
|
||||
body::before {
|
||||
content: "";
|
||||
@@ -37,8 +36,8 @@ body::before {
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(59, 130, 246, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(59, 130, 246, 0.05) 1px, transparent 1px);
|
||||
linear-gradient(rgba(31, 111, 91, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(31, 111, 91, 0.045) 1px, transparent 1px);
|
||||
background-size: 42px 42px;
|
||||
mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.55), transparent 70%);
|
||||
}
|
||||
@@ -87,8 +86,8 @@ button { cursor: pointer; }
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, var(--primary), var(--cyan));
|
||||
box-shadow: 0 12px 26px rgba(37, 99, 235, 0.26);
|
||||
background: linear-gradient(135deg, #10231d, var(--primary));
|
||||
box-shadow: 0 12px 26px rgba(31, 111, 91, 0.22);
|
||||
}
|
||||
.brand strong { letter-spacing: 0; }
|
||||
.nav-links {
|
||||
@@ -108,18 +107,19 @@ button { cursor: pointer; }
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
|
||||
transition: transform 0.18s var(--ease), background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
.nav-links a:hover, .nav-links a.active {
|
||||
color: var(--primary-dark);
|
||||
background: rgba(59, 130, 246, 0.12);
|
||||
background: rgba(31, 111, 91, 0.10);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.admin-link {
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #2563eb, #7c3aed);
|
||||
box-shadow: 0 12px 28px rgba(59, 130, 246, 0.24);
|
||||
background: linear-gradient(135deg, #10231d, #1f6f5b);
|
||||
box-shadow: 0 12px 28px rgba(31, 111, 91, 0.22);
|
||||
}
|
||||
.admin-link:hover { box-shadow: 0 16px 36px rgba(59, 130, 246, 0.32); }
|
||||
.admin-link:hover { transform: translateY(-1px); box-shadow: 0 16px 36px rgba(31, 111, 91, 0.28); }
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
@@ -134,8 +134,8 @@ button { cursor: pointer; }
|
||||
border-radius: 32px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.62)),
|
||||
radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.26), transparent 34%),
|
||||
radial-gradient(circle at 18% 82%, rgba(139, 92, 246, 0.18), transparent 30%);
|
||||
radial-gradient(circle at 88% 18%, rgba(31, 111, 91, 0.14), transparent 34%),
|
||||
radial-gradient(circle at 18% 82%, rgba(217, 146, 39, 0.13), transparent 30%);
|
||||
box-shadow: var(--shadow);
|
||||
padding: clamp(28px, 5vw, 58px);
|
||||
overflow: hidden;
|
||||
@@ -148,7 +148,7 @@ button { cursor: pointer; }
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(59, 130, 246, 0.20), transparent 68%);
|
||||
background: radial-gradient(circle, rgba(31, 111, 91, 0.13), transparent 68%);
|
||||
}
|
||||
.hero-copy {
|
||||
position: relative;
|
||||
@@ -193,7 +193,7 @@ p {
|
||||
margin-top: 22px;
|
||||
}
|
||||
.hero-tags span {
|
||||
border: 1px solid rgba(59, 130, 246, 0.18);
|
||||
border: 1px solid rgba(31, 111, 91, 0.16);
|
||||
border-radius: 999px;
|
||||
padding: 7px 11px;
|
||||
color: #355075;
|
||||
@@ -215,7 +215,7 @@ p {
|
||||
color: #263856;
|
||||
font-weight: 900;
|
||||
box-shadow: 0 10px 26px rgba(65, 88, 140, 0.10);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease;
|
||||
transition: transform 0.18s var(--ease), box-shadow 0.18s ease, background-color 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
@@ -225,8 +225,8 @@ p {
|
||||
.button.primary {
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
background: linear-gradient(135deg, #2563eb, #06b6d4);
|
||||
box-shadow: 0 16px 34px rgba(37, 99, 235, 0.26);
|
||||
background: linear-gradient(135deg, #10231d, #1f6f5b);
|
||||
box-shadow: 0 16px 34px rgba(31, 111, 91, 0.24);
|
||||
}
|
||||
|
||||
.release-card, .panel, .metric {
|
||||
@@ -236,6 +236,13 @@ p {
|
||||
box-shadow: 0 14px 42px rgba(65, 88, 140, 0.11);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
.release-card, .panel, .metric, .source-group, .notice-card, .route-list a {
|
||||
transition: transform 0.22s var(--ease), border-color 0.22s ease, box-shadow 0.22s ease, background-color 0.22s ease;
|
||||
}
|
||||
.release-card:hover, .panel:hover, .metric:hover, .source-group:hover, .notice-card:hover, .route-list a:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 18px 46px rgba(31, 48, 40, 0.13);
|
||||
}
|
||||
.release-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@@ -396,8 +403,8 @@ input {
|
||||
outline: none;
|
||||
}
|
||||
input:focus {
|
||||
border-color: rgba(59, 130, 246, 0.65);
|
||||
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.14);
|
||||
border-color: rgba(31, 111, 91, 0.58);
|
||||
box-shadow: 0 0 0 4px rgba(31, 111, 91, 0.12);
|
||||
}
|
||||
.source-board {
|
||||
display: grid;
|
||||
@@ -434,7 +441,7 @@ input:focus {
|
||||
}
|
||||
.route-list a:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(59, 130, 246, 0.36);
|
||||
border-color: rgba(31, 111, 91, 0.30);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
.route-list span { color: var(--muted); font-size: 13px; font-weight: 700; }
|
||||
@@ -476,5 +483,5 @@ input:focus {
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { transition: none !important; scroll-behavior: auto !important; }
|
||||
*, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user