123 lines
3.1 KiB
JavaScript
123 lines
3.1 KiB
JavaScript
/**
|
|||
|
|
* 全局数据状态(模块级 reactive 单例,多页面共享)
|
||
|
|
* - packages:包列表(已排序:预警置顶 → 正常 → 忽略)
|
||
|
|
* - thresholds:全局阈值 + 单包例外
|
||
|
|
* - logs:操作日志
|
||
|
|
*/
|
||
|
|
import { reactive, computed } from 'vue'
|
||
|
|
import api from '../api'
|
||
|
|
|
||
|
|
const state = reactive({
|
||
|
|
packages: [],
|
||
|
|
threshold: { versionThreshold: 15, sizeThreshold: 500, exceptions: [] },
|
||
|
|
logs: [],
|
||
|
|
loading: false,
|
||
|
|
storageDir: localStorage.getItem('vcm-storage-dir') || '',
|
||
|
|
})
|
||
|
|
|
||
|
|
// —— 派生统计 ——
|
||
|
|
const stats = computed(() => {
|
||
|
|
const valid = state.packages.filter(p => p.status !== 'broken')
|
||
|
|
const totalSize = valid.reduce((s, p) => s + p.size, 0)
|
||
|
|
const warnCount = valid.filter(p => p.status === 'warn').length
|
||
|
|
const cleanable = valid.filter(p => p.status === 'warn').reduce((s, p) => s + p.size, 0)
|
||
|
|
const maxPkg = [...valid].sort((a, b) => b.size - a.size)[0] || null
|
||
|
|
const oldestPkg = [...valid].sort((a, b) => (a.lastUpdated || '').localeCompare(b.lastUpdated || ''))[0] || null
|
||
|
|
const top30 = [...valid].sort((a, b) => (b.visits || 0) - (a.visits || 0)).slice(0, 30)
|
||
|
|
const maxVisit = Math.max(...top30.map(p => p.visits || 0), 1)
|
||
|
|
return {
|
||
|
|
pkgCount: valid.length,
|
||
|
|
totalSize,
|
||
|
|
totalSizeText: fmtSize(totalSize),
|
||
|
|
warnCount,
|
||
|
|
cleanable,
|
||
|
|
cleanableText: fmtSize(cleanable),
|
||
|
|
maxPkg,
|
||
|
|
oldestPkg,
|
||
|
|
top30: top30.map(p => ({ ...p, heat: Math.round(((p.visits || 0) / maxVisit) * 100) })),
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
function fmtSize(mb) {
|
||
|
|
if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`
|
||
|
|
return `${mb.toFixed(1)} MB`
|
||
|
|
}
|
||
|
|
|
||
|
|
const fmtSizeFn = fmtSize
|
||
|
|
|
||
|
|
async function refreshPackages(keyword = '') {
|
||
|
|
state.packages = await api.getPackages(keyword)
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshThresholds() {
|
||
|
|
const t = await api.getThresholds()
|
||
|
|
state.threshold.versionThreshold = t.versionThreshold
|
||
|
|
state.threshold.sizeThreshold = t.sizeThreshold
|
||
|
|
state.threshold.exceptions = t.exceptions || []
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshLogs() {
|
||
|
|
state.logs = await api.getLogs()
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshAll() {
|
||
|
|
state.loading = true
|
||
|
|
try {
|
||
|
|
await Promise.all([refreshPackages(), refreshThresholds(), refreshLogs()])
|
||
|
|
} finally {
|
||
|
|
state.loading = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function saveThresholds(payload) {
|
||
|
|
const ok = await api.saveThresholds(payload)
|
||
|
|
if (ok) {
|
||
|
|
await refreshThresholds()
|
||
|
|
await refreshPackages()
|
||
|
|
}
|
||
|
|
return ok
|
||
|
|
}
|
||
|
|
|
||
|
|
async function ignorePkg(name) {
|
||
|
|
await api.ignore(name)
|
||
|
|
await refreshPackages()
|
||
|
|
}
|
||
|
|
|
||
|
|
async function unignorePkg(name) {
|
||
|
|
await api.unignore(name)
|
||
|
|
await refreshPackages()
|
||
|
|
}
|
||
|
|
|
||
|
|
async function cleanPkg(name, keep) {
|
||
|
|
const res = await api.clean(name, keep)
|
||
|
|
if (res.ok) {
|
||
|
|
await Promise.all([refreshPackages(), refreshLogs()])
|
||
|
|
}
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
async function deleteVersion(name, version) {
|
||
|
|
const res = await api.deleteVersion(name, version)
|
||
|
|
if (res.ok) {
|
||
|
|
await Promise.all([refreshPackages(), refreshLogs()])
|
||
|
|
}
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useStore() {
|
||
|
|
return {
|
||
|
|
state,
|
||
|
|
stats,
|
||
|
|
fmtSize: fmtSizeFn,
|
||
|
|
refreshAll,
|
||
|
|
refreshPackages,
|
||
|
|
refreshThresholds,
|
||
|
|
refreshLogs,
|
||
|
|
saveThresholds,
|
||
|
|
ignorePkg,
|
||
|
|
unignorePkg,
|
||
|
|
cleanPkg,
|
||
|
|
deleteVersion,
|
||
|
|
}
|
||
|
|
}
|