style(Venom): 优化包管理器定制页 UI

- 新增品牌/概览区(软件包数、版本发布数)与工具条,列表改用卡片 + 包头像/说明双行单元格

- 配置视图改为 包信息头 + 配置/发布双栏卡片 + 发布历史卡片,表单改 label 顶置

- 全部配色走 Element Plus CSS 变量,自动适配宿主明/暗主题;引入 @element-plus/icons-vue
This commit is contained in:
TermiNexus committed 2026-10-05 18:24:37 +08:00
1 parent 993d142bdd
commit 8beb280aad
3 files changed
+336 -93

No files matched your search

+1
View File
@@ -8,6 +8,7 @@
"name": "venom-ui",
"version": "1.0.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.14.3",
"vue": "^3.5.40"
},
+1
View File
@@ -8,6 +8,7 @@
"build": "vite build"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.14.3",
"vue": "^3.5.40"
},
+334 -93
View File
@@ -6,9 +6,22 @@
* 配置视图:编辑包说明/配置内容、发布新版本、查看发布历史
*
* 视图切换在组件内部完成(不占用宿主路由),符合「远端页面自理」的微前端约定。
* 样式仅依赖 Element Plus 的 CSS 变量,自动适配宿主明/暗主题。
*/
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import {
ArrowLeft,
Box,
Clock,
Collection,
Finished,
Plus,
Promotion,
Refresh,
Search,
User,
} from '@element-plus/icons-vue'
import {
venomApi,
type RemoteContext,
@@ -37,6 +50,11 @@ const createForm = reactive({ PackageName: '', PackageDesc: '', ConfigText: '' }
const configForm = reactive({ PackageDesc: '', ConfigText: '' })
const releaseForm = reactive({ Version: '', Notes: '' })
/** 全部软件包的发布次数合计(列表概览用) */
const totalReleases = computed(() =>
packages.value.reduce((sum, p) => sum + (p.ReleaseCount || 0), 0),
)
async function loadPackages(): Promise<void> {
loading.value = true
try {
@@ -158,119 +176,204 @@ onMounted(loadPackages)
<div class="venom-page">
<!-- ==================== 列表视图 ==================== -->
<template v-if="!openedUUID">
<div class="head">
<div class="brand">
<span class="brand-name">这是Venom</span>
<span class="brand-sub">Venom 包管理器 · 软件包列表</span>
<!-- 品牌 / 概览 -->
<div class="venom-hero">
<div class="hero-badge"><el-icon :size="26"><Box /></el-icon></div>
<div class="hero-text">
<div class="hero-title">这是Venom</div>
<div class="hero-sub">Venom 包管理器 · 软件包仓库(包名全局唯一)</div>
</div>
<div class="spacer" />
<div class="hero-stats">
<div class="stat">
<div class="stat-value">{{ packages.length }}</div>
<div class="stat-label">软件包</div>
</div>
<div class="stat-divider" />
<div class="stat">
<div class="stat-value">{{ totalReleases }}</div>
<div class="stat-label">版本发布</div>
</div>
</div>
</div>
<!-- 工具条 -->
<div class="toolbar">
<el-input
v-model="keyword"
placeholder="按包名搜索"
clearable
class="search"
:prefix-icon="Search"
@keyup.enter="loadPackages"
@clear="loadPackages"
/>
<el-button @click="loadPackages">刷新</el-button>
<el-button type="primary" @click="openCreate">新建软件包</el-button>
<div class="spacer" />
<el-button :icon="Refresh" round @click="loadPackages">刷新</el-button>
<el-button type="primary" :icon="Plus" round @click="openCreate">新建软件包</el-button>
</div>
<el-table v-loading="loading" :data="packages" border size="default" class="table">
<el-table-column prop="PackageName" label="包名" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<span class="pkg-name">{{ row.PackageName }}</span>
<!-- 包列表 -->
<el-card shadow="never" class="panel">
<el-table v-loading="loading" :data="packages" class="table" row-key="PackageUUID">
<el-table-column label="软件包" min-width="260">
<template #default="{ row }">
<div class="pkg-cell">
<div class="pkg-avatar"><el-icon><Collection /></el-icon></div>
<div class="pkg-meta">
<div class="pkg-name">{{ row.PackageName }}</div>
<div class="pkg-desc">{{ row.PackageDesc || '—' }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="创建人" width="150">
<template #default="{ row }">
<span class="muted">{{ row.CreatorName || '—' }}</span>
</template>
</el-table-column>
<el-table-column prop="CreateTime" label="创建时间" width="180" />
<el-table-column label="版本" width="110" align="center">
<template #default="{ row }">
<el-tag :type="row.ReleaseCount ? 'success' : 'info'" effect="plain" round size="small">
{{ row.ReleaseCount || 0 }} 个
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="130" fixed="right" align="right">
<template #default="{ row }">
<el-button link type="primary" @click="openConfig(row)">打开配置</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="还没有软件包" :image-size="90">
<el-button type="primary" :icon="Plus" round @click="openCreate">新建软件包</el-button>
</el-empty>
</template>
</el-table-column>
<el-table-column prop="PackageDesc" label="说明" min-width="220" show-overflow-tooltip />
<el-table-column prop="CreatorName" label="创建人" width="140" show-overflow-tooltip />
<el-table-column prop="CreateTime" label="创建时间" width="180" />
<el-table-column prop="ReleaseCount" label="发布数" width="90" align="center" />
<el-table-column label="操作" width="130" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openConfig(row)">打开配置</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="还没有软件包,点右上角「新建软件包」开始" :image-size="80" />
</template>
</el-table>
</el-table>
</el-card>
</template>
<!-- ==================== 配置视图 ==================== -->
<template v-else>
<div class="head">
<el-button link @click="backToList">← 返回列表</el-button>
<div class="brand">
<span class="brand-name">{{ detail?.PackageName || '' }}</span>
<span class="brand-sub">包配置</span>
<!-- 包信息头 -->
<div class="detail-head">
<el-button :icon="ArrowLeft" text @click="backToList">返回列表</el-button>
<div class="detail-title">
<div class="pkg-avatar lg"><el-icon :size="18"><Box /></el-icon></div>
<span class="detail-name">{{ detail?.PackageName || '' }}</span>
</div>
<div class="detail-tags">
<el-tag type="success" effect="plain" round size="small">
<el-icon><Finished /></el-icon>发布 {{ detail?.ReleaseCount || 0 }} 次
</el-tag>
<el-tag v-if="detail?.CreatorName" effect="plain" round size="small">
<el-icon><User /></el-icon>{{ detail?.CreatorName }}
</el-tag>
<el-tag v-if="detail?.UpdateTime" effect="plain" round size="small">
<el-icon><Clock /></el-icon>{{ detail?.UpdateTime }}
</el-tag>
</div>
<div class="spacer" />
<el-tag size="small" effect="plain">发布 {{ detail?.ReleaseCount || 0 }} 次</el-tag>
</div>
<el-divider content-position="left">包配置</el-divider>
<el-form label-width="96px" class="form">
<el-form-item label="包名">
<el-input :model-value="detail?.PackageName || ''" disabled />
</el-form-item>
<el-form-item label="包说明">
<el-input v-model="configForm.PackageDesc" maxlength="512" placeholder="可选" />
</el-form-item>
<el-form-item label="配置内容">
<el-input
v-model="configForm.ConfigText"
type="textarea"
:rows="6"
placeholder="包配置内容(如依赖、构建参数等)"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="saveConfig">保存配置</el-button>
</el-form-item>
</el-form>
<div class="detail-grid">
<!-- 包配置 -->
<el-card shadow="never" class="panel">
<template #header>
<div class="panel-head">
<el-icon><Collection /></el-icon>
<span>包配置</span>
</div>
</template>
<el-form label-position="top" class="form">
<el-form-item label="包名">
<el-input :model-value="detail?.PackageName || ''" disabled />
</el-form-item>
<el-form-item label="包说明">
<el-input v-model="configForm.PackageDesc" maxlength="512" placeholder="可选" show-word-limit />
</el-form-item>
<el-form-item label="配置内容">
<el-input
v-model="configForm.ConfigText"
type="textarea"
:rows="7"
placeholder="包配置内容(如依赖、构建参数等)"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" round :loading="saving" @click="saveConfig">保存配置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-divider content-position="left">发布新版本</el-divider>
<el-form label-width="96px" class="form">
<el-form-item label="版本号">
<el-input v-model="releaseForm.Version" maxlength="64" placeholder="如 1.0.0" class="version" />
</el-form-item>
<el-form-item label="发布说明">
<el-input v-model="releaseForm.Notes" type="textarea" :rows="3" placeholder="可选" />
</el-form-item>
<el-form-item>
<el-button type="success" :loading="publishing" @click="publishRelease">发布</el-button>
</el-form-item>
</el-form>
<!-- 发布新版本 -->
<el-card shadow="never" class="panel">
<template #header>
<div class="panel-head">
<el-icon><Promotion /></el-icon>
<span>发布新版本</span>
</div>
</template>
<el-form label-position="top" class="form">
<el-form-item label="版本号">
<el-input v-model="releaseForm.Version" maxlength="64" placeholder="如 1.0.0" />
</el-form-item>
<el-form-item label="发布说明">
<el-input v-model="releaseForm.Notes" type="textarea" :rows="7" placeholder="可选" />
</el-form-item>
<el-form-item>
<el-button type="success" round :icon="Promotion" :loading="publishing" @click="publishRelease">
发布
</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-divider content-position="left">发布历史</el-divider>
<el-table :data="releases" border size="small" class="table">
<el-table-column prop="Version" label="版本" width="140" />
<el-table-column prop="PublisherName" label="发布人" width="150" show-overflow-tooltip />
<el-table-column prop="CreateTime" label="发布时间" width="180" />
<el-table-column prop="Notes" label="发布说明" min-width="200" show-overflow-tooltip />
<template #empty>
<el-empty description="暂无发布记录" :image-size="60" />
<!-- 发布历史 -->
<el-card shadow="never" class="panel">
<template #header>
<div class="panel-head">
<el-icon><Clock /></el-icon>
<span>发布历史</span>
<el-tag class="panel-count" effect="plain" round size="small">{{ releases.length }}</el-tag>
</div>
</template>
</el-table>
<el-table :data="releases" class="table" row-key="ReleaseUUID">
<el-table-column label="版本" width="150">
<template #default="{ row }">
<el-tag effect="plain" round size="small">{{ row.Version }}</el-tag>
</template>
</el-table-column>
<el-table-column label="发布人" width="160">
<template #default="{ row }">
<span class="muted">{{ row.PublisherName || '—' }}</span>
</template>
</el-table-column>
<el-table-column prop="CreateTime" label="发布时间" width="180" />
<el-table-column prop="Notes" label="发布说明" min-width="200" show-overflow-tooltip />
<template #empty>
<el-empty description="暂无发布记录" :image-size="70" />
</template>
</el-table>
</el-card>
</template>
<!-- ==================== 新建软件包 ==================== -->
<el-dialog v-model="dialogVisible" title="新建软件包" width="620px" :close-on-click-modal="false">
<el-form label-width="96px">
<el-form label-position="top" class="form">
<el-form-item label="包名" required>
<el-input v-model="createForm.PackageName" maxlength="128" placeholder="包名必须全局唯一" />
</el-form-item>
<el-form-item label="包说明">
<el-input v-model="createForm.PackageDesc" maxlength="512" placeholder="可选" />
<el-input v-model="createForm.PackageDesc" maxlength="512" placeholder="可选" show-word-limit />
</el-form-item>
<el-form-item label="配置内容">
<el-input v-model="createForm.ConfigText" type="textarea" :rows="5" placeholder="可选" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submitCreate">创建</el-button>
<el-button round @click="dialogVisible = false">取消</el-button>
<el-button type="primary" round :loading="saving" @click="submitCreate">创建</el-button>
</template>
</el-dialog>
</div>
@@ -280,43 +383,181 @@ onMounted(loadPackages)
.venom-page {
padding: 4px;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 14px;
}
.head {
/* ==== 品牌 / 概览 ==== */
.venom-hero {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
gap: 14px;
padding: 16px 18px;
border-radius: 12px;
background: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-7);
}
.brand {
.hero-badge {
width: 46px;
height: 46px;
flex-shrink: 0;
display: flex;
align-items: baseline;
gap: 8px;
align-items: center;
justify-content: center;
border-radius: 12px;
color: #fff;
background: linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3));
box-shadow: 0 4px 12px var(--el-color-primary-light-5);
}
.brand-name {
font-size: 16px;
.hero-text {
min-width: 0;
}
.hero-title {
font-size: 18px;
font-weight: 700;
color: var(--el-text-color-primary);
letter-spacing: 0.5px;
}
.brand-sub {
.hero-sub {
margin-top: 2px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.hero-stats {
margin-left: auto;
display: flex;
align-items: center;
gap: 18px;
}
.stat {
text-align: center;
}
.stat-value {
font-size: 20px;
font-weight: 700;
line-height: 1.1;
color: var(--el-color-primary);
}
.stat-label {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.stat-divider {
width: 1px;
height: 28px;
background: var(--el-border-color);
}
/* ==== 工具条 ==== */
.toolbar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.search {
width: 260px;
}
.spacer {
flex: 1;
}
.search {
width: 220px;
/* ==== 面板 ==== */
.panel {
border-radius: 12px;
}
.panel-head {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.panel-count {
margin-left: 4px;
}
.table {
width: 100%;
}
.form {
max-width: 720px;
/* ==== 包单元格 ==== */
.pkg-cell {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.version {
width: 220px;
.pkg-avatar {
width: 34px;
height: 34px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 9px;
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border: 1px solid var(--el-color-primary-light-8);
}
.pkg-avatar.lg {
width: 30px;
height: 30px;
border-radius: 8px;
}
.pkg-meta {
min-width: 0;
}
.pkg-name {
font-weight: 600;
color: var(--el-text-color-primary);
}
.pkg-desc {
font-size: 12px;
color: var(--el-text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.muted {
color: var(--el-text-color-secondary);
}
/* ==== 配置视图 ==== */
.detail-head {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.detail-title {
display: flex;
align-items: center;
gap: 8px;
}
.detail-name {
font-size: 16px;
font-weight: 700;
color: var(--el-text-color-primary);
}
.detail-tags {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.detail-tags .el-tag {
display: inline-flex;
align-items: center;
gap: 4px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 14px;
}
.form {
max-width: 100%;
}
</style>