refactor: 基于 upstream/Erotica 重新整合本地功能(来源选择/合并详情/计数/窗口/首页)

将本地在 upstream !408(已安装应用合并+统计)之上的后续改动重新整合到最新 upstream/Erotica:
- 已安装应用:卸载/打开按 APM/Spark 来源选择;计数总数=APM+Spark 包数(双来源计为独立包)
- 查看详情:已安装页复用所有应用页合并详情视图(单来源优先已装类型,双来源自动模式)
- 窗口:恢复阈值改为 1600x900,过大回退默认尺寸并居中,不自动最大化
- 首页推荐:致谢卡片移至其他内容下方,加大标题/卡片/致谢间距
- 隐藏排行榜/荣耀榜侧栏入口(代码保留,v-if=false)
- 新增 scripts/test-build.sh 测试打包脚本(版本末段+1 加 -test)并更新 AGENTS.md

冲突文件(index.ts/App.vue/InstalledAppsModal.vue)均取本地完整实现(为上游版本的超集)。
This commit is contained in:
xiyidaiwa
2026-08-03 11:06:28 +08:00
parent 77b4244f1d
commit b89c001401
25 changed files with 1750 additions and 299 deletions
+56 -5
View File
@@ -1,10 +1,24 @@
<template>
<div
@click="openDetail"
class="group flex cursor-pointer gap-3 rounded-xl border border-slate-200/70 bg-white/90 p-4 shadow-sm transition hover:-translate-y-0.5 hover:border-brand/50 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60"
class="group relative flex cursor-pointer transition hover:border-brand/50"
:class="
compact
? 'gap-2 rounded-lg border border-slate-200/70 bg-white/90 p-2 shadow-none hover:shadow-sm dark:border-slate-800/60 dark:bg-slate-900/60'
: 'gap-3 rounded-xl border border-slate-200/70 bg-white/90 p-4 shadow-sm hover:-translate-y-0.5 hover:shadow-md dark:border-slate-800/60 dark:bg-slate-900/60'
"
>
<!-- 排名徽标排行榜/荣耀榜使用 3 名金// -->
<div
class="flex h-14 w-14 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-gradient-to-b from-slate-100 to-slate-200 shadow-inner dark:from-slate-800 dark:to-slate-700"
v-if="rank"
class="pointer-events-none absolute -left-1.5 -top-1.5 z-10 flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold shadow-md ring-2 ring-white transition-transform duration-200 group-hover:scale-110 dark:ring-slate-900"
:class="rankClass"
>
{{ rank }}
</div>
<div
class="flex shrink-0 items-center justify-center overflow-hidden bg-gradient-to-b from-slate-100 to-slate-200 shadow-inner dark:from-slate-800 dark:to-slate-700"
:class="compact ? 'h-9 w-9 rounded-md' : 'h-14 w-14 rounded-xl'"
>
<img
ref="iconImg"
@@ -17,7 +31,8 @@
<div class="flex flex-1 flex-col gap-1 overflow-hidden">
<div class="flex items-center gap-2">
<div
class="truncate text-base font-semibold text-slate-900 dark:text-white"
class="truncate font-semibold text-slate-900 dark:text-white"
:class="compact ? 'text-xs' : 'text-base'"
>
{{ app.name || "" }}
</div>
@@ -43,12 +58,21 @@
</span>
</template>
</div>
<span
v-if="downloadCount"
class="ml-auto shrink-0 text-[10px] text-slate-400 dark:text-slate-500"
>{{ formatDownloads(downloadCount) }}</span
>
</div>
<div class="text-sm text-slate-500 dark:text-slate-400 leading-tight">
<div
class="leading-tight text-slate-500 dark:text-slate-400"
:class="compact ? 'text-xs' : 'text-sm'"
>
{{ app.pkgname || "" }} · {{ app.version || "" }}
</div>
<div
class="truncate text-xs text-slate-500 dark:text-slate-400 leading-tight"
class="truncate leading-tight text-slate-500 dark:text-slate-400"
:class="compact ? 'text-[11px]' : 'text-xs'"
>
{{ description || "\u00A0" }}
</div>
@@ -65,6 +89,12 @@ const props = defineProps<{
app: App;
// 是否显示来源标识(仅在混合模式下显示)
showOrigin?: boolean;
// 紧凑模式(用于首页精选板块等空间受限区域)
compact?: boolean;
// 下载量(用于下载排行;>=0 时在标题行右侧显示)
downloadCount?: number;
// 排名(用于排行榜/荣耀榜,左上角显示徽标)
rank?: number;
}>();
const emit = defineEmits<{
@@ -111,6 +141,27 @@ const description = computed(() => {
return more.substring(0, 80) + (more.length > 80 ? "..." : "");
});
// 下载量格式化:>=1万 显示 "x.x万"
const formatDownloads = (n?: number): string => {
if (!n || n <= 0) return "0";
if (n >= 10000) {
const wan = n / 10000;
return `${wan.toFixed(1).replace(/\.0$/, "")}`;
}
return n.toLocaleString();
};
// 排名徽标配色(前 3 名金/银/铜,其余灰色)
const rankClass = computed(() => {
if (props.rank === 1)
return "bg-amber-400 text-white shadow-amber-400/40";
if (props.rank === 2)
return "bg-slate-300 text-slate-700 shadow-slate-400/40";
if (props.rank === 3)
return "bg-amber-600 text-white shadow-amber-600/40";
return "bg-slate-500/90 text-white shadow-slate-500/30";
});
const openDetail = () => {
emit("open-detail", props.app);
};