fix(size): 修复轻量包占用显示为 0 MB 的精度问题

后端 fmtMb 用 toFixed(1) 保留 1 位小数,<0.05MB 的包(几 KB~几十 KB
的轻量组件)被截断为 0。改为保留 KB 级精度;前端统一使用 store 的
fmtSize/fmtBytes(新增 KB 档位),所有包大小显示点(检索/忽略/概览/
卡片/抽屉/清理对话框/Top20)统一替换,消除各组件重复的本地格式化。
This commit is contained in:
陈银军
2026-09-19 22:04:14 +08:00
parent 1793888900
commit a7dc1b7a17
9 changed files with 25 additions and 19 deletions
+2 -1
View File
@@ -65,7 +65,8 @@ function readJson(file) {
} }
function fmtMb(bytes) { function fmtMb(bytes) {
return Number((bytes / 1024 / 1024).toFixed(1)) // 保留到 KB 级精度:toFixed(1) 会把 <0.05MB 的轻量包(几 KB~几十 KB)截成 0
return Math.round((bytes / 1024 / 1024) * 1024) / 1024
} }
// 目录名 → 包名(支持 scoped:@scope/name) // 目录名 → 包名(支持 scoped:@scope/name)
+3 -3
View File
@@ -1,6 +1,6 @@
<script setup> <script setup>
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useStore } from '../store' import { useStore, fmtSize } from '../store'
import api from '../api' import api from '../api'
const props = defineProps({ const props = defineProps({
@@ -81,7 +81,7 @@ async function confirm() {
<div class="cd-body"> <div class="cd-body">
<div class="cd-pkg"> <div class="cd-pkg">
<span class="cd-pkg-name">{{ pkg.name }}</span> <span class="cd-pkg-name">{{ pkg.name }}</span>
<span class="cd-pkg-meta">当前 {{ pkg.versions }} 个版本 · {{ pkg.size }} MB</span> <span class="cd-pkg-meta">当前 {{ pkg.versions }} 个版本 · {{ fmtSize(pkg.size) }}</span>
</div> </div>
<div class="cd-row"> <div class="cd-row">
@@ -105,7 +105,7 @@ async function confirm() {
</div> </div>
<div class="cd-foot"> <div class="cd-foot">
<span class="cd-freed">预计释放 {{ freedSpace.toFixed(1) }} MB</span> <span class="cd-freed">预计释放 {{ fmtSize(freedSpace) }}</span>
<button class="btn unignore" @click="emit('close')">取消</button> <button class="btn unignore" @click="emit('close')">取消</button>
<button class="btn clean" :class="{ disabled: !toDelete.length }" :disabled="cleaning || !toDelete.length" @click="confirm"> <button class="btn clean" :class="{ disabled: !toDelete.length }" :disabled="cleaning || !toDelete.length" @click="confirm">
{{ cleaning ? '清理中…' : `确认清理 ${toDelete.length} 个` }} {{ cleaning ? '清理中…' : `确认清理 ${toDelete.length} 个` }}
+2 -1
View File
@@ -1,6 +1,7 @@
<script setup> <script setup>
import StatusTag from './StatusTag.vue' import StatusTag from './StatusTag.vue'
import PkgLogo from './PkgLogo.vue' import PkgLogo from './PkgLogo.vue'
import { fmtSize } from '../store'
defineProps({ defineProps({
pkg: { type: Object, required: true }, pkg: { type: Object, required: true },
@@ -41,7 +42,7 @@ function splitName(name) {
<div class="m-l">版本数</div> <div class="m-l">版本数</div>
</div> </div>
<div class="m-item"> <div class="m-item">
<div class="m-v">{{ pkg.size }} MB</div> <div class="m-v">{{ fmtSize(pkg.size) }}</div>
<div class="m-l">占用空间</div> <div class="m-l">占用空间</div>
</div> </div>
</div> </div>
+3 -2
View File
@@ -3,6 +3,7 @@ import { ref, computed, watch } from 'vue'
import StatusTag from './StatusTag.vue' import StatusTag from './StatusTag.vue'
import PkgLogo from './PkgLogo.vue' import PkgLogo from './PkgLogo.vue'
import api from '../api' import api from '../api'
import { fmtSize } from '../store'
const props = defineProps({ const props = defineProps({
visible: Boolean, visible: Boolean,
@@ -90,7 +91,7 @@ function delVersion(v) {
</div> </div>
<div class="kv"> <div class="kv">
<div class="k">占用空间</div> <div class="k">占用空间</div>
<div class="v">{{ pkg.size }} MB</div> <div class="v">{{ fmtSize(pkg.size) }}</div>
</div> </div>
<div class="kv"> <div class="kv">
<div class="k">最近更新</div> <div class="k">最近更新</div>
@@ -127,7 +128,7 @@ function delVersion(v) {
</div> </div>
<div v-for="v in verList" :key="v.version" class="ver-row"> <div v-for="v in verList" :key="v.version" class="ver-row">
<span class="ver-no">{{ v.version }}</span> <span class="ver-no">{{ v.version }}</span>
<span class="ver-size">{{ v.size }} MB</span> <span class="ver-size">{{ fmtSize(v.size) }}</span>
<span class="ver-date">{{ fmtDate(v.date) }}</span> <span class="ver-date">{{ fmtDate(v.date) }}</span>
<button class="del-btn" title="删除此版本" @click="delVersion(v)">🗑</button> <button class="del-btn" title="删除此版本" @click="delVersion(v)">🗑</button>
</div> </div>
+1 -4
View File
@@ -1,16 +1,13 @@
<script setup> <script setup>
// 排行 Top 30:有访问统计按热度,否则降级为缓存占用(真实环境无访问统计) // 排行 Top 30:有访问统计按热度,否则降级为缓存占用(真实环境无访问统计)
import PkgLogo from './PkgLogo.vue' import PkgLogo from './PkgLogo.vue'
import { fmtSize } from '../store'
const props = defineProps({ const props = defineProps({
top30: { type: Array, default: () => [] }, top30: { type: Array, default: () => [] },
top30By: { type: String, default: 'visits' }, top30By: { type: String, default: 'visits' },
}) })
const emit = defineEmits(['open']) const emit = defineEmits(['open'])
const isVisits = props.top30By === 'visits' const isVisits = props.top30By === 'visits'
function fmtSize(mb) {
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
return `${mb.toFixed(1)} MB`
}
</script> </script>
<template> <template>
+8 -2
View File
@@ -64,16 +64,22 @@ const stats = computed(() => {
function fmtSize(mb) { function fmtSize(mb) {
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB` if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
return `${mb.toFixed(1)} MB` if (mb >= 0.05) return `${mb.toFixed(1)} MB`
if (mb > 0) return `${Math.max(1, Math.round(mb * 1024))} KB`
return '0 MB'
} }
/** 字节 → 可读文本 */ /** 字节 → 可读文本 */
function fmtBytes(b) { function fmtBytes(b) {
const mb = b / 1024 / 1024 const mb = b / 1024 / 1024
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB` if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
return `${mb.toFixed(1)} MB` if (mb >= 0.05) return `${mb.toFixed(1)} MB`
if (mb > 0) return `${Math.max(1, Math.round(mb * 1024))} KB`
return '0 MB'
} }
export { fmtSize, fmtBytes }
const fmtSizeFn = fmtSize const fmtSizeFn = fmtSize
async function refreshPackages(keyword = '') { async function refreshPackages(keyword = '') {
+2 -2
View File
@@ -1,6 +1,6 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useStore } from '../store' import { useStore, fmtSize } from '../store'
const { state, unignorePkg, refreshPackages } = useStore() const { state, unignorePkg, refreshPackages } = useStore()
@@ -42,7 +42,7 @@ async function doUnignore(pkg) {
<tr v-for="p in ignoredList" :key="p.name"> <tr v-for="p in ignoredList" :key="p.name">
<td><span class="pkg-cell">{{ p.name }}</span></td> <td><span class="pkg-cell">{{ p.name }}</span></td>
<td><span class="num-cell">{{ p.versions }}</span></td> <td><span class="num-cell">{{ p.versions }}</span></td>
<td><span class="num-cell">{{ p.size }} MB</span></td> <td><span class="num-cell">{{ fmtSize(p.size) }}</span></td>
<td><span class="ign-time">{{ p.ignoredAt || '—' }}</span></td> <td><span class="ign-time">{{ p.ignoredAt || '—' }}</span></td>
<td> <td>
<button class="btn unignore" @click="doUnignore(p)">↩️ 移出忽略</button> <button class="btn unignore" @click="doUnignore(p)">↩️ 移出忽略</button>
+2 -2
View File
@@ -7,7 +7,7 @@ import Top30Panel from '../components/Top30Panel.vue'
import PackageCard from '../components/PackageCard.vue' import PackageCard from '../components/PackageCard.vue'
import PackageDrawer from '../components/PackageDrawer.vue' import PackageDrawer from '../components/PackageDrawer.vue'
import CleanDialog from '../components/CleanDialog.vue' import CleanDialog from '../components/CleanDialog.vue'
import { useStore } from '../store' import { useStore, fmtSize } from '../store'
const router = useRouter() const router = useRouter()
const { state, stats, refreshAll, ignorePkg, unignorePkg, cleanPkg } = useStore() const { state, stats, refreshAll, ignorePkg, unignorePkg, cleanPkg } = useStore()
@@ -86,7 +86,7 @@ function quickClean() {
<div class="stats-2"> <div class="stats-2">
<StatMini tone="blue" label="今日更新包数" ico="🆕" :main="String(stats.todayUpdated)" sub="今日有版本更新" link="查看" @link="router.push('/search')" /> <StatMini tone="blue" label="今日更新包数" ico="🆕" :main="String(stats.todayUpdated)" sub="今日有版本更新" link="查看" @link="router.push('/search')" />
<StatMini tone="green" label="缓存版本总数" ico="🧩" :main="stats.totalVersions.toLocaleString()" sub="全部缓存包的版本数" link="查看" @link="router.push('/search')" /> <StatMini tone="green" label="缓存版本总数" ico="🧩" :main="stats.totalVersions.toLocaleString()" sub="全部缓存包的版本数" link="查看" @link="router.push('/search')" />
<StatMini tone="orange" label="最大包" ico="🐘" :main="stats.maxPkg?.name || '—'" :unit="stats.maxPkg ? stats.maxPkg.size + ' MB' : ''" :sub="`${stats.maxPkg?.versions || 0} 个版本`" link="详情" @link="stats.maxPkg && openDrawer(stats.maxPkg)" /> <StatMini tone="orange" label="最大包" ico="🐘" :main="stats.maxPkg?.name || '—'" :unit="stats.maxPkg ? fmtSize(stats.maxPkg.size) : ''" :sub="`${stats.maxPkg?.versions || 0} 个版本`" link="详情" @link="stats.maxPkg && openDrawer(stats.maxPkg)" />
<StatMini tone="purple" label="最老包时间" ico="⏳" :main="stats.oldestPkg?.lastUpdated || '—'" unit="⏰" :sub="stats.oldestPkg ? `${stats.oldestPkg.name} · 未更新` : ''" link="详情" @link="stats.oldestPkg && openDrawer(stats.oldestPkg)" /> <StatMini tone="purple" label="最老包时间" ico="⏳" :main="stats.oldestPkg?.lastUpdated || '—'" unit="⏰" :sub="stats.oldestPkg ? `${stats.oldestPkg.name} · 未更新` : ''" link="详情" @link="stats.oldestPkg && openDrawer(stats.oldestPkg)" />
<StatMini tone="pink" label="版本数分布" ico="🧩" main=">15 版本" :unit="`${stats.warnCount} 个`" :sub="`超阈值共 ${warningExcess} 个版本`" link="查看" @link="router.push({ path: '/search', query: { status: 'warn' } })" /> <StatMini tone="pink" label="版本数分布" ico="🧩" main=">15 版本" :unit="`${stats.warnCount} 个`" :sub="`超阈值共 ${warningExcess} 个版本`" link="查看" @link="router.push({ path: '/search', query: { status: 'warn' } })" />
</div> </div>
+2 -2
View File
@@ -5,7 +5,7 @@ import PackageDrawer from '../components/PackageDrawer.vue'
import CleanDialog from '../components/CleanDialog.vue' import CleanDialog from '../components/CleanDialog.vue'
import StatusTag from '../components/StatusTag.vue' import StatusTag from '../components/StatusTag.vue'
import PkgLogo from '../components/PkgLogo.vue' import PkgLogo from '../components/PkgLogo.vue'
import { useStore } from '../store' import { useStore, fmtSize } from '../store'
const { state, stats, refreshPackages, ignorePkg, unignorePkg, cleanPkg, deleteVersion } = useStore() const { state, stats, refreshPackages, ignorePkg, unignorePkg, cleanPkg, deleteVersion } = useStore()
const route = useRoute() const route = useRoute()
@@ -138,7 +138,7 @@ async function onCleanConfirmed(res) {
</span> </span>
</td> </td>
<td><span class="num-cell" :class="{ over: p.versions > state.threshold.versionThreshold }">{{ p.versions }}</span></td> <td><span class="num-cell" :class="{ over: p.versions > state.threshold.versionThreshold }">{{ p.versions }}</span></td>
<td><span class="num-cell">{{ p.size }} MB</span></td> <td><span class="num-cell">{{ fmtSize(p.size) }}</span></td>
<td><span class="date-cell">{{ p.lastUpdated || '—' }}</span></td> <td><span class="date-cell">{{ p.lastUpdated || '—' }}</span></td>
<td><StatusTag :status="p.status" /></td> <td><StatusTag :status="p.status" /></td>
<td @click.stop> <td @click.stop>