Files
verdaccio-cache-manager/frontend/src/components/PackageCard.vue
T
陈银军 8a54fb2d5c feat: 包信息展示与扫描数据修正
- scanner: 版本数只统计实际存在的 .tgz(修复 playwright-core 5113 版本虚高),解析 description/homepage/repository/author/license 元数据字段
- 前端: 新增 PkgLogo 首字母徽标组件,详情抽屉展示包 logo(主页 favicon 加载失败自动回退)、介绍与主页/仓库/许可胶囊,优化抽屉头部布局
- 全量 UI 改进: 毛玻璃扫描遮罩与进度、头像菜单重新扫描、主题切换、移动端 Tabbar/StatMini/Top30Panel 响应式、列表瘦身与详情按需加载
- compose: TZ=Asia/Shanghai + 命名卷 vcm-data 解决 SQLite 权限
- 更新 amd64 镜像 tar
2026-09-19 21:07:27 +08:00

126 lines
4.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import StatusTag from './StatusTag.vue'
import PkgLogo from './PkgLogo.vue'
defineProps({
pkg: { type: Object, required: true },
threshold: { type: Number, default: 15 },
})
const emit = defineEmits(['open', 'clean', 'ignore', 'unignore'])
function splitName(name) {
const i = name.lastIndexOf('/')
if (i > 0) return { scope: name.slice(0, i + 1), base: name.slice(i + 1) }
return { scope: '', base: name }
}
</script>
<template>
<div
class="pkg-card"
:class="{ warn: pkg.status === 'warn', ignored: pkg.status === 'ignored', broken: pkg.status === 'broken' }"
@click="emit('open', pkg)"
>
<div class="p-top">
<PkgLogo :name="pkg.name" :size="44" />
<div style="flex: 1; min-width: 0">
<div class="pkg-name">
<span class="nm">
<span v-if="splitName(pkg.name).scope" class="scope">{{ splitName(pkg.name).scope }}</span>{{ splitName(pkg.name).base }}
</span>
<StatusTag :status="pkg.status" />
</div>
<div v-if="pkg.status === 'warn'" class="warn-tip">超阈值 {{ pkg.versions - threshold }} 个版本</div>
<div v-else-if="pkg.status === 'broken'" class="warn-tip">元数据异常,不影响其他包</div>
<div v-else class="warn-tip">&nbsp;</div>
</div>
</div>
<div class="p-meta">
<div class="m-item">
<div class="m-v">{{ pkg.versions }}</div>
<div class="m-l">版本数</div>
</div>
<div class="m-item">
<div class="m-v">{{ pkg.size }} MB</div>
<div class="m-l">占用空间</div>
</div>
</div>
<div class="p-ops" @click.stop>
<template v-if="pkg.status === 'warn'">
<button class="btn clean" @click="emit('clean', pkg)">✨ 清理</button>
<button class="btn ignore" @click="emit('ignore', pkg)">💤 忽略</button>
</template>
<template v-else-if="pkg.status === 'ignored'">
<button class="btn unignore" @click="emit('unignore', pkg)">↩️ 取消忽略</button>
</template>
<template v-else-if="pkg.status === 'broken'">
<button class="btn unignore" @click="emit('open', pkg)">🔍 查看</button>
</template>
<template v-else>
<button class="btn ignore" @click="emit('ignore', pkg)">💤 忽略</button>
</template>
</div>
</div>
</template>
<style scoped>
.pkg-card {
position: relative;
background: var(--card);
border: 1.5px solid var(--line);
border-radius: var(--r-lg);
padding: 18px 20px;
box-shadow: var(--shadow);
transition: .2s;
animation: fadeUp .5s both;
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 168px;
cursor: pointer;
}
.pkg-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-hover); }
.pkg-card.warn {
border-color: color-mix(in srgb, var(--red) 50%, transparent);
animation: fadeUp .5s both, breathe 2.6s ease-in-out infinite;
}
.pkg-card.ignored { background: var(--ignored-card); opacity: .82; }
.pkg-card.broken { border-style: dashed; }
.p-top { display: flex; align-items: center; gap: 12px; }
.pkg-name {
font-size: 15px;
font-weight: 700;
letter-spacing: -.2px;
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.pkg-name .nm {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.pkg-name .scope { color: var(--ink-faint); font-weight: 500; }
.warn-tip {
font-size: 11.5px;
color: var(--ink-faint);
margin-top: 3px;
min-height: 15px;
}
.pkg-card.warn .warn-tip { color: var(--red); font-weight: 600; }
.p-meta {
display: flex;
gap: 18px;
margin: 14px 0 15px;
padding: 11px 0;
border-top: 1px dashed var(--line);
border-bottom: 1px dashed var(--line);
}
.m-item .m-v { font-family: var(--num); font-size: 17px; font-weight: 700; }
.m-item .m-l { font-size: 11px; color: var(--ink-faint); margin-top: 2px; }
.pkg-card.warn .m-item .m-v { color: var(--red); }
.p-ops { margin-top: auto; display: flex; align-items: center; gap: 9px; }
</style>