mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-09-21 14:10:11 +08:00
将本地在 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)均取本地完整实现(为上游版本的超集)。
185 lines
6.6 KiB
Vue
185 lines
6.6 KiB
Vue
<template>
|
||
<!-- 空队列:仅显示右下角小按钮 -->
|
||
<button
|
||
v-if="downloads.length === 0"
|
||
type="button"
|
||
class="fixed bottom-4 right-4 z-40 inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-200/70 bg-white text-slate-500 shadow-lg transition hover:-translate-y-0.5 hover:text-brand dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400 dark:hover:text-white sm:bottom-6 sm:right-6"
|
||
title="暂无下载任务"
|
||
aria-label="下载队列(空)"
|
||
>
|
||
<i class="fas fa-download"></i>
|
||
</button>
|
||
|
||
<!-- 有任务:展开为完整面板(默认展开,可点 chevron 折叠列表) -->
|
||
<div
|
||
v-else
|
||
class="fixed inset-x-4 bottom-4 z-40 rounded-3xl border border-slate-200/70 bg-white shadow-2xl transition-all duration-200 dark:border-slate-800/70 dark:bg-slate-900 sm:left-auto sm:right-6 sm:w-96"
|
||
>
|
||
<div
|
||
class="flex items-center justify-between px-5 py-4"
|
||
@click="toggleExpand"
|
||
>
|
||
<div
|
||
class="flex items-center gap-2 text-sm font-semibold text-slate-700 dark:text-slate-200"
|
||
>
|
||
<i class="fas fa-download text-brand"></i>
|
||
<span>下载队列</span>
|
||
<span
|
||
v-if="downloads.length"
|
||
class="rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-500 dark:bg-slate-800/70 dark:text-slate-300"
|
||
>
|
||
({{ completedDownloads }}/{{ downloads.length }})
|
||
</span>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<button
|
||
v-if="downloads.length"
|
||
type="button"
|
||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200/70 text-slate-500 transition hover:text-slate-900 dark:border-slate-700 dark:text-slate-400"
|
||
title="清除已完成"
|
||
@click.stop="clearCompleted"
|
||
>
|
||
<i class="fas fa-broom"></i>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-slate-200/70 text-slate-500 transition hover:text-slate-900 dark:border-slate-700 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white"
|
||
@click.stop="toggleExpand"
|
||
>
|
||
<i
|
||
class="fas"
|
||
:class="isExpanded ? 'fa-chevron-down' : 'fa-chevron-up'"
|
||
></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<Transition
|
||
enter-active-class="duration-200 ease-out"
|
||
enter-from-class="opacity-0 -translate-y-2"
|
||
enter-to-class="opacity-100 translate-y-0"
|
||
leave-active-class="duration-150 ease-in"
|
||
leave-from-class="opacity-100 translate-y-0"
|
||
leave-to-class="opacity-0 -translate-y-2"
|
||
>
|
||
<div
|
||
v-show="isExpanded"
|
||
class="max-h-96 overflow-y-auto overscroll-contain px-3 pb-4"
|
||
>
|
||
<div class="space-y-2">
|
||
<div
|
||
v-for="download in downloads"
|
||
:key="download.id"
|
||
class="flex cursor-pointer items-center gap-3 rounded-2xl border border-slate-200/70 bg-white/90 p-3 shadow-sm transition hover:border-brand/40 hover:shadow-lg dark:border-slate-800/70 dark:bg-slate-900"
|
||
:class="
|
||
download.status === 'failed'
|
||
? 'border-rose-300/70 dark:border-rose-500/40'
|
||
: ''
|
||
"
|
||
@click="showDownloadDetail(download)"
|
||
>
|
||
<div
|
||
class="h-12 w-12 overflow-hidden rounded-xl bg-slate-100 dark:bg-slate-800"
|
||
>
|
||
<img
|
||
:src="download.icon"
|
||
:alt="download.name"
|
||
class="h-full w-full object-cover"
|
||
/>
|
||
</div>
|
||
<div class="flex-1">
|
||
<p
|
||
class="truncate text-sm font-semibold text-slate-800 dark:text-slate-100"
|
||
>
|
||
{{ download.name }}
|
||
</p>
|
||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||
<span v-if="download.status === 'downloading'"
|
||
>下载中 {{ Math.floor(download.progress * 100) }}%</span
|
||
>
|
||
<span v-else-if="download.status === 'installing'"
|
||
>安装中...</span
|
||
>
|
||
<span v-else-if="download.status === 'completed'">已完成</span>
|
||
<span v-else-if="download.status === 'failed'"
|
||
>失败: {{ download.error }}</span
|
||
>
|
||
<span v-else-if="download.status === 'paused'">已暂停</span>
|
||
<span v-else>等待中...</span>
|
||
</p>
|
||
<div
|
||
v-if="download.status === 'downloading'"
|
||
class="mt-2 h-1.5 rounded-full bg-slate-100 dark:bg-slate-800"
|
||
>
|
||
<div
|
||
class="h-full rounded-full bg-brand"
|
||
:style="{ width: `${download.progress * 100}%` }"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<button
|
||
v-if="download.status === 'failed'"
|
||
type="button"
|
||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border border-rose-200/60 text-rose-500 transition hover:bg-rose-50"
|
||
title="重试"
|
||
@click.stop="retryDownload(download)"
|
||
>
|
||
<i class="fas fa-redo"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref, watch } from "vue";
|
||
import type { DownloadItem } from "../global/typedefinition";
|
||
|
||
const props = defineProps<{
|
||
downloads: DownloadItem[];
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
(e: "pause", download: DownloadItem): void;
|
||
(e: "resume", download: DownloadItem): void;
|
||
(e: "cancel", download: DownloadItem): void;
|
||
(e: "retry", download: DownloadItem): void;
|
||
(e: "clear-completed"): void;
|
||
(e: "show-detail", download: DownloadItem): void;
|
||
}>();
|
||
|
||
const isExpanded = ref(true);
|
||
|
||
const completedDownloads = computed(() => {
|
||
return props.downloads.filter((d) => d.status === "completed").length;
|
||
});
|
||
|
||
const toggleExpand = () => {
|
||
isExpanded.value = !isExpanded.value;
|
||
};
|
||
|
||
const retryDownload = (download: DownloadItem) => {
|
||
emit("retry", download);
|
||
};
|
||
|
||
const clearCompleted = () => {
|
||
emit("clear-completed");
|
||
};
|
||
|
||
const showDownloadDetail = (download: DownloadItem) => {
|
||
emit("show-detail", download);
|
||
};
|
||
|
||
// 从无任务到有任务时,自动展开面板("有任务时再向左边伸出")
|
||
watch(
|
||
() => props.downloads.length > 0,
|
||
(hasTasks) => {
|
||
if (hasTasks) isExpanded.value = true;
|
||
},
|
||
);
|
||
</script>
|