优化首页基本信息布局
移除首页宣传横幅,新增用户、域名、状态、到期时间和部署服务器概览,并补充布局回归测试。
This commit is contained in:
parent
0a9172f639
commit
475058ed4b
@ -65,28 +65,6 @@ h2 {
|
||||
cursor: wait;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.hero-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 250px;
|
||||
margin: 36px 0 18px;
|
||||
padding: 36px 42px;
|
||||
border-radius: 28px;
|
||||
color: #fff;
|
||||
background: linear-gradient(120deg, #182b5b, #315aaa);
|
||||
box-shadow: 0 20px 45px rgba(42, 75, 145, 0.2);
|
||||
}
|
||||
.hero-card p {
|
||||
max-width: 650px;
|
||||
margin-bottom: 0;
|
||||
color: #d5e1ff;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
@ -99,13 +77,6 @@ h2 {
|
||||
background: #5ee0a0;
|
||||
box-shadow: 0 0 0 5px rgba(94, 224, 160, 0.15);
|
||||
}
|
||||
.hero-mark {
|
||||
display: block;
|
||||
width: 138px;
|
||||
height: 138px;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 16px 24px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
.error-banner,
|
||||
.notice-banner {
|
||||
margin: 18px 0;
|
||||
@ -122,6 +93,84 @@ h2 {
|
||||
color: #24734b;
|
||||
background: #effbf4;
|
||||
}
|
||||
.overview-panel {
|
||||
margin: 28px 0 18px;
|
||||
}
|
||||
.overview-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.overview-summary-card,
|
||||
.overview-domain-row {
|
||||
border: 1px solid #e2e8f2;
|
||||
border-radius: 14px;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
.overview-summary-card {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
padding: 16px;
|
||||
}
|
||||
.overview-summary-card > span,
|
||||
.overview-domain-field > span,
|
||||
.overview-summary-card small,
|
||||
.overview-domain-field small {
|
||||
color: #7a879d;
|
||||
font-size: 12px;
|
||||
}
|
||||
.overview-summary-card strong {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 20px;
|
||||
}
|
||||
.overview-summary-card small,
|
||||
.overview-domain-field small {
|
||||
line-height: 1.45;
|
||||
}
|
||||
.overview-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
white-space: nowrap;
|
||||
font-size: 17px !important;
|
||||
}
|
||||
.overview-status .status-dot {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
.overview-domain-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.overview-domain-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.3fr) minmax(130px, 0.8fr) minmax(180px, 1fr);
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
padding: 15px 16px;
|
||||
}
|
||||
.overview-domain-main,
|
||||
.overview-domain-field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.overview-domain-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 9px;
|
||||
}
|
||||
.overview-domain-main strong,
|
||||
.overview-domain-field strong {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.overview-domain-field strong {
|
||||
font-size: 14px;
|
||||
}
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
@ -562,12 +611,9 @@ code {
|
||||
.topbar-actions button {
|
||||
flex: 1 1 calc(50% - 10px);
|
||||
}
|
||||
.hero-card {
|
||||
align-items: flex-start;
|
||||
padding: 28px;
|
||||
}
|
||||
.hero-mark {
|
||||
display: none;
|
||||
.overview-summary-grid,
|
||||
.overview-domain-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.metrics-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
@ -11,9 +11,8 @@ function dnsVerificationTimeoutFor(providerID) {
|
||||
}
|
||||
|
||||
export function useDashboard() {
|
||||
const scertLogo = "/Scert.png";
|
||||
|
||||
const status = ref(null);
|
||||
const currentUser = ref(null);
|
||||
const domains = ref([]);
|
||||
const servers = ref([]);
|
||||
const sshKeys = ref([]);
|
||||
@ -1079,6 +1078,7 @@ export function useDashboard() {
|
||||
try {
|
||||
const [
|
||||
statusResponse,
|
||||
authResponse,
|
||||
domainResponse,
|
||||
serverResponse,
|
||||
sshKeyResponse,
|
||||
@ -1089,6 +1089,7 @@ export function useDashboard() {
|
||||
dnsProviderResponse,
|
||||
] = await Promise.all([
|
||||
fetchAdmin("/api/v1/status"),
|
||||
fetchAdmin("/api/v1/auth/session"),
|
||||
fetchAdmin("/api/v1/domains"),
|
||||
fetchAdmin("/api/v1/servers"),
|
||||
fetchAdmin("/api/v1/ssh-keys"),
|
||||
@ -1113,6 +1114,7 @@ export function useDashboard() {
|
||||
throw new Error(await responseError(failedResponse, "读取管理数据失败"));
|
||||
}
|
||||
status.value = await statusResponse.json();
|
||||
currentUser.value = await authResponse.json();
|
||||
domains.value = (await domainResponse.json()).items || [];
|
||||
servers.value = (await serverResponse.json()).items || [];
|
||||
sshKeys.value = (await sshKeyResponse.json()).items || [];
|
||||
@ -1160,8 +1162,8 @@ export function useDashboard() {
|
||||
});
|
||||
|
||||
return {
|
||||
scertLogo,
|
||||
status,
|
||||
currentUser,
|
||||
domains,
|
||||
servers,
|
||||
sshKeys,
|
||||
|
||||
@ -576,10 +576,23 @@ test("dashboardLayout.keepsActivityListsBoundedAndKeyboardAccessible", async ()
|
||||
assert.match(stylesheet, /\.record-list:focus-visible/);
|
||||
});
|
||||
|
||||
test("dashboardLayout.replacesHeroWithBasicInformationSummary", async () => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const template = await readFile(new URL("./Dashboard.vue", import.meta.url), "utf8");
|
||||
const stylesheet = await readFile(new URL("./Dashboard.css", import.meta.url), "utf8");
|
||||
assert.doesNotMatch(template, /class="hero-card"/);
|
||||
assert.match(template, /class="panel overview-panel"/);
|
||||
assert.match(template, /当前用户/);
|
||||
assert.match(template, /到期时间/);
|
||||
assert.match(template, /部署服务器/);
|
||||
assert.match(stylesheet, /\.overview-summary-grid\s*\{[^}]*grid-template-columns: repeat\(3/);
|
||||
assert.match(stylesheet, /\.overview-domain-row\s*\{[^}]*grid-template-columns:/);
|
||||
});
|
||||
|
||||
test("dashboardLayout.usesSharedTooltipForUserManagementDescription", async () => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const template = await readFile(new URL("../UserManagement/UserManagement.vue", import.meta.url), "utf8");
|
||||
assert.match(template, /import HelpTip from "\.\.\/components\/HelpTip\/HelpTip.vue";/);
|
||||
assert.match(template, /<HelpTip>[\s\S]*?<\/HelpTip>/);
|
||||
assert.doesNotMatch(template, /<p class="user-management-description">/);
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,8 +9,8 @@ import { useDashboard } from "./Dashboard.js";
|
||||
|
||||
const dashboard = useDashboard();
|
||||
const {
|
||||
scertLogo,
|
||||
status,
|
||||
currentUser,
|
||||
domains,
|
||||
servers,
|
||||
sshKeys,
|
||||
@ -153,24 +153,70 @@ const {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="hero-card">
|
||||
<div>
|
||||
<span class="status-dot" :class="{ online: status }"></span>
|
||||
<span class="status-label">{{
|
||||
status ? "核心服务运行中" : "等待连接核心服务"
|
||||
}}</span>
|
||||
<h2>让证书续期和部署变得可追踪</h2>
|
||||
<p>
|
||||
统一管理单域名、泛域名和 SAN 证书,当前已接入 lego、DNS
|
||||
验证、自动续期、远程部署与部署后校验流程。
|
||||
</p>
|
||||
</div>
|
||||
<img class="hero-mark" :src="scertLogo" alt="Scert 标志" />
|
||||
</section>
|
||||
|
||||
<p v-if="error" class="error-banner">{{ error }}</p>
|
||||
<p v-if="notice" class="notice-banner">{{ notice }}</p>
|
||||
|
||||
<section class="panel overview-panel">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<p class="eyebrow">首页概览</p>
|
||||
<h2>基本信息</h2>
|
||||
</div>
|
||||
<span class="muted">当前登录与托管资源</span>
|
||||
</div>
|
||||
<div class="overview-summary-grid">
|
||||
<article class="overview-summary-card">
|
||||
<span>当前用户</span>
|
||||
<strong>{{ currentUser?.account || "—" }}</strong>
|
||||
<small>{{ currentUser?.email || "未绑定邮箱" }}</small>
|
||||
</article>
|
||||
<article class="overview-summary-card">
|
||||
<span>服务状态</span>
|
||||
<strong class="overview-status">
|
||||
<i class="status-dot" :class="{ online: status }"></i>
|
||||
{{ status ? "核心服务运行中" : "等待连接核心服务" }}
|
||||
</strong>
|
||||
<small>版本 {{ status?.version || "—" }}</small>
|
||||
</article>
|
||||
<article class="overview-summary-card">
|
||||
<span>托管域名</span>
|
||||
<strong>{{ domains.length }} 个</strong>
|
||||
<small>已配置的证书管理对象</small>
|
||||
</article>
|
||||
</div>
|
||||
<div v-if="loading" class="empty-state compact-empty">正在读取域名状态……</div>
|
||||
<div v-else-if="domains.length === 0" class="empty-state compact-empty">
|
||||
<strong>还没有托管域名</strong>
|
||||
<span>添加第一个域名,开始管理证书申请和自动续期。</span>
|
||||
</div>
|
||||
<div v-else class="overview-domain-list">
|
||||
<article v-for="item in domains" :key="item.id" class="overview-domain-row">
|
||||
<div class="overview-domain-main">
|
||||
<strong>{{ item.name }}</strong>
|
||||
<span class="tag" :class="statusClass(item.status)">
|
||||
{{ displayLabel(domainStatusLabels, item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="overview-domain-field">
|
||||
<span>到期时间</span>
|
||||
<strong>{{ formatDate(item.expires_at) }}</strong>
|
||||
<small>{{ daysRemaining(item.expires_at) }}</small>
|
||||
</div>
|
||||
<div class="overview-domain-field">
|
||||
<span>部署服务器</span>
|
||||
<strong>
|
||||
{{
|
||||
(domainServers[item.id] || [])
|
||||
.map((server) => server.name)
|
||||
.join("、") || "未配置"
|
||||
}}
|
||||
</strong>
|
||||
<small>{{ item.auto_renew ? "自动续期已开启" : "自动续期已关闭" }}</small>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="metrics-grid">
|
||||
<article class="metric-card">
|
||||
<span>服务版本</span><strong>{{ status?.version || "—" }}</strong>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user