refactor(admin-web): unify page headers with shared title card

Introduce .page-title-card style and migrate Images, Knowledge,
KolDashboard, KolManage, KolProfile views to the shared header layout.
Add subtitle copy for KOL dashboard and manage views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 21:38:45 +08:00
parent 40d9a6cc63
commit 2862aff72b
8 changed files with 111 additions and 110 deletions
@@ -211,6 +211,7 @@ const enUS = {
}, },
manage: { manage: {
title: "KOL Prompt Management", title: "KOL Prompt Management",
subtitle: "Manage refined template packages, prompts, platforms, variables, and publishing status.",
packageTitle: "Packages", packageTitle: "Packages",
promptTitle: "Prompts", promptTitle: "Prompts",
createPackage: "Create package", createPackage: "Create package",
@@ -295,6 +296,7 @@ const enUS = {
}, },
dashboard: { dashboard: {
title: "KOL Dashboard", title: "KOL Dashboard",
subtitle: "Track subscriptions and generation metrics in real time.",
totalSubs: "Total subscribed tenants", totalSubs: "Total subscribed tenants",
monthSubs: "New this month", monthSubs: "New this month",
totalUsage: "Total usage", totalUsage: "Total usage",
@@ -211,6 +211,7 @@ const zhCN = {
}, },
manage: { manage: {
title: "KOL 提示词管理", title: "KOL 提示词管理",
subtitle: "管理精调模版订阅包与提示词,配置平台、变量和上下线状态。",
packageTitle: "订阅包", packageTitle: "订阅包",
promptTitle: "提示词", promptTitle: "提示词",
createPackage: "创建订阅包", createPackage: "创建订阅包",
@@ -295,6 +296,7 @@ const zhCN = {
}, },
dashboard: { dashboard: {
title: "KOL 数据看板", title: "KOL 数据看板",
subtitle: "实时查看订阅情况与生成数据统计。",
totalSubs: "总订阅租户数", totalSubs: "总订阅租户数",
monthSubs: "本月新增", monthSubs: "本月新增",
totalUsage: "总使用次数", totalUsage: "总使用次数",
+41
View File
@@ -54,6 +54,47 @@ html, body, #app {
color: #1a1a1a; color: #1a1a1a;
} }
.page-title-card {
background: #fff;
border: 1px solid #e6edf5;
border-radius: 12px;
overflow: hidden;
}
.page-title-card__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
padding: 24px;
}
.page-title-card__copy {
min-width: 0;
}
.page-title-card__copy h2 {
margin: 0;
color: #1a1a1a;
font-size: 20px;
font-weight: 600;
line-height: 1.4;
}
.page-title-card__copy p {
margin: 6px 0 0;
color: #8c8c8c;
font-size: 13px;
line-height: 1.7;
}
.page-title-card__actions {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 12px;
}
.table-actions-row { .table-actions-row {
display: flex; display: flex;
align-items: center; align-items: center;
+19 -28
View File
@@ -352,22 +352,13 @@ async function confirmDeleteImage(image: ImageAssetItem) {
<template> <template>
<div class="images-page"> <div class="images-page">
<div class="images-page__hero"> <section class="page-title-card">
<h2>{{ t("images.title") }}</h2> <div class="page-title-card__header">
<p>{{ t("images.description") }}</p> <div class="page-title-card__copy">
</div> <h2>{{ t("images.title") }}</h2>
<p>{{ t("images.description") }}</p>
<div class="images-main-card">
<div class="images-toolbar">
<div class="images-toolbar__left">
<a-input-search
v-model:value="searchKeyword"
:placeholder="t('images.searchPlaceholder')"
style="width: 300px"
allow-clear
/>
</div> </div>
<div class="images-toolbar__right"> <div class="page-title-card__actions">
<a-button type="primary" :disabled="imageUploadBusy" @click="triggerUpload"> <a-button type="primary" :disabled="imageUploadBusy" @click="triggerUpload">
<template #icon><UploadOutlined /></template> <template #icon><UploadOutlined /></template>
{{ t("images.uploadBtn") }} {{ t("images.uploadBtn") }}
@@ -383,6 +374,19 @@ async function confirmDeleteImage(image: ImageAssetItem) {
/> />
</div> </div>
</div> </div>
</section>
<div class="images-main-card">
<div class="images-toolbar">
<div class="images-toolbar__left">
<a-input-search
v-model:value="searchKeyword"
:placeholder="t('images.searchPlaceholder')"
style="width: 300px"
allow-clear
/>
</div>
</div>
<div class="images-layout-inner"> <div class="images-layout-inner">
<aside class="images-sidebar"> <aside class="images-sidebar">
@@ -579,19 +583,6 @@ async function confirmDeleteImage(image: ImageAssetItem) {
gap: 20px; gap: 20px;
} }
.images-page__hero h2 {
margin: 0;
color: #111827;
font-size: 18px;
font-weight: 700;
}
.images-page__hero p {
margin: 0;
color: #6b7280;
font-size: 13px;
}
.images-main-card { .images-main-card {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
+16 -36
View File
@@ -505,21 +505,24 @@ function closeItemDetail(): void {
<template> <template>
<div class="knowledge-page"> <div class="knowledge-page">
<div class="knowledge-page__hero"> <section class="page-title-card">
<h2>知识库</h2> <div class="page-title-card__header">
<p> <div class="page-title-card__copy">
通过上传文档网站认证及任何与您的产品相关的包装及品牌故事内容对各种格式的数据和资料信息来进行归纳并结合生成文章 <h2>知识库</h2>
</p> <p>
</div> 通过上传文档网站认证及任何与您的产品相关的包装及品牌故事内容对各种格式的数据和资料信息来进行归纳并结合生成文章
</p>
</div>
<div class="page-title-card__actions">
<a-button type="primary" class="knowledge-add-btn" @click="openItemModal('document')">
<template #icon><PlusOutlined /></template>
添加知识
</a-button>
</div>
</div>
</section>
<div class="knowledge-main-card"> <div class="knowledge-main-card">
<div class="knowledge-toolbar">
<a-button type="primary" class="knowledge-add-btn" @click="openItemModal('document')">
<template #icon><PlusOutlined /></template>
添加知识
</a-button>
</div>
<div class="knowledge-layout-inner"> <div class="knowledge-layout-inner">
<aside class="knowledge-sidebar"> <aside class="knowledge-sidebar">
<div class="knowledge-sidebar__header"> <div class="knowledge-sidebar__header">
@@ -736,25 +739,6 @@ function closeItemDetail(): void {
gap: 20px; gap: 20px;
} }
.knowledge-page__hero {
display: flex;
flex-direction: column;
gap: 8px;
}
.knowledge-page__hero h2 {
margin: 0;
color: #111827;
font-size: 18px;
font-weight: 700;
}
.knowledge-page__hero p {
margin: 0;
color: #6b7280;
font-size: 13px;
}
.knowledge-main-card { .knowledge-main-card {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
@@ -765,10 +749,6 @@ function closeItemDetail(): void {
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03); box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
} }
.knowledge-toolbar {
display: flex;
}
.knowledge-add-btn { .knowledge-add-btn {
width: 120px; width: 120px;
border-radius: 6px; border-radius: 6px;
+13 -26
View File
@@ -208,16 +208,20 @@ function refresh() {
<template> <template>
<div class="kol-dashboard-view"> <div class="kol-dashboard-view">
<div class="page-header"> <section class="page-title-card">
<div class="header-info"> <div class="page-title-card__header">
<h2 class="page-title">{{ t("kol.dashboard.title") }}</h2> <div class="page-title-card__copy">
<p class="page-desc">实时查看订阅情况与生成数据统计</p> <h2>{{ t("kol.dashboard.title") }}</h2>
<p>{{ t("kol.dashboard.subtitle") }}</p>
</div>
<div class="page-title-card__actions">
<a-button @click="refresh">
<template #icon><ReloadOutlined /></template>
刷新
</a-button>
</div>
</div> </div>
<a-button @click="refresh"> </section>
<template #icon><ReloadOutlined /></template>
刷新
</a-button>
</div>
<!-- Stats Row --> <!-- Stats Row -->
<div class="stats-row"> <div class="stats-row">
@@ -366,23 +370,6 @@ function refresh() {
gap: 24px; gap: 24px;
} }
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.page-title {
font-size: 20px;
font-weight: 700;
margin: 0 0 4px 0;
color: #1a1a1a;
}
.page-desc {
font-size: 14px;
color: #8c8c8c;
margin: 0;
}
/* Stats Row */ /* Stats Row */
.stats-row { .stats-row {
display: grid; display: grid;
@@ -224,6 +224,15 @@ function packageStatusLabel(status: string) {
<template> <template>
<div class="kol-manage-view"> <div class="kol-manage-view">
<section v-if="!editingPromptId" class="page-title-card">
<div class="page-title-card__header">
<div class="page-title-card__copy">
<h2>{{ t("kol.manage.title") }}</h2>
<p>{{ t("kol.manage.subtitle") }}</p>
</div>
</div>
</section>
<div v-if="editingPromptId" class="manage-content manage-content--editor"> <div v-if="editingPromptId" class="manage-content manage-content--editor">
<div class="fullscreen-editor"> <div class="fullscreen-editor">
<KolPromptEditor :prompt-id="editingPromptId" @back="handleCloseEditor" /> <KolPromptEditor :prompt-id="editingPromptId" @back="handleCloseEditor" />
@@ -401,6 +410,7 @@ function packageStatusLabel(status: string) {
.kol-manage-view { .kol-manage-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px;
height: calc(100vh - 120px); height: calc(100vh - 120px);
} }
+8 -20
View File
@@ -16,10 +16,14 @@ const profileQuery = useQuery({
<template> <template>
<div class="kol-profile-view"> <div class="kol-profile-view">
<header class="page-header"> <section class="page-title-card">
<h2 class="page-title">{{ t("kol.profile.title") }}</h2> <div class="page-title-card__header">
<p class="page-subtitle">{{ t("kol.profile.subtitle") }}</p> <div class="page-title-card__copy">
</header> <h2>{{ t("kol.profile.title") }}</h2>
<p>{{ t("kol.profile.subtitle") }}</p>
</div>
</div>
</section>
<a-skeleton v-if="profileQuery.isPending.value" active avatar :paragraph="{ rows: 3 }" /> <a-skeleton v-if="profileQuery.isPending.value" active avatar :paragraph="{ rows: 3 }" />
<a-alert <a-alert
@@ -41,20 +45,4 @@ const profileQuery = useQuery({
flex-direction: column; flex-direction: column;
gap: 24px; gap: 24px;
} }
.page-header {
display: flex;
flex-direction: column;
gap: 4px;
}
.page-title {
margin: 0;
font-size: 20px;
font-weight: 700;
color: #1a1a1a;
}
.page-subtitle {
margin: 0;
font-size: 13px;
color: #8c8c8c;
}
</style> </style>